LUMIERA.clone/tests/library/scoped-collection-test.cpp
2025-06-07 23:59:57 +02:00

492 lines
15 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
ScopedCollection(Test) - holding and owning a fixed collection of noncopyable objects
Copyright (C)
2012, Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
* *****************************************************************/
/** @file scoped-collection-test.cpp
** unit test \ref ScopedCollection_test
*/
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/util.hpp"
#include "lib/scoped-collection.hpp"
#include "lib/test/tracking-dummy.hpp"
namespace lib {
namespace test{
namespace error = lumiera::error;
namespace { // our explosive special Dummy
LUMIERA_ERROR_DEFINE(SUBVERSIVE, "undercover action");
class SubDummy
: public Dummy
{
int trigger_;
/** special variant of the dummy API operation:
* @param i when zero, the trigger value will be revealed
*/
virtual long
calc (int i)
{
if (!i)
return getVal() + trigger_;
else
return Dummy::calc(i);
}
public:
SubDummy (int id, int trigger)
: Dummy(id)
, trigger_(trigger)
{
if (trigger == getVal())
throw error::Fatal ("Subversive Bomb", LUMIERA_ERROR_SUBVERSIVE);
}
SubDummy()
: Dummy()
, trigger_(-1)
{ }
};
inline uint
sum (uint n)
{
return n*(n+1) / 2;
}
}//(End) subversive test data
using util::isnil;
using LERR_(ITER_EXHAUST);
typedef ScopedCollection<Dummy, sizeof(SubDummy)> CollD;
/****************************************************************//**
* @test ScopedCollection manages a fixed set of objects, but these
* child objects are noncopyable, may be polymorphic, an can
* be created either all at once or chunk wise. The API is
* similar to a vector and allows for element access
* and iteration.
*/
class ScopedCollection_test : public Test
{
virtual void
run (Arg)
{
seedRand();
simpleUsage();
building_RAII_Style();
building_StackStyle();
iterating();
verify_defaultPopulator();
verify_iteratorPopulator();
verify_embeddedCollection();
}
void
simpleUsage()
{
CHECK (0 == Dummy::checksum());
{
CollD container(5);
CHECK (isnil (container));
CHECK (0 == container.size());
CHECK (0 == Dummy::checksum());
container.populate();
CHECK (!isnil (container));
CHECK (5 == container.size());
CHECK (0 != Dummy::checksum());
container.clear();
CHECK (isnil (container));
CHECK (0 == container.size());
CHECK (0 == Dummy::checksum());
container.populate();
CHECK (Dummy::checksum() == container[0].getVal()
+ container[1].getVal()
+ container[2].getVal()
+ container[3].getVal()
+ container[4].getVal());
}
CHECK (0 == Dummy::checksum());
}
void
iterating()
{
CHECK (0 == Dummy::checksum());
{
CollD coll(50);
for (uint i=0; i<coll.capacity(); ++i)
coll.emplace<Dummy>(i);
int check=0;
CollD::iterator ii = coll.begin();
while (ii)
{
CHECK (check == ii->getVal());
CHECK (check == ii->calc(+5) - 5);
++check;
++ii;
}
// Test the const iterator
CollD const& const_coll (coll);
check = 0;
CollD::const_iterator cii = const_coll.begin();
while (cii)
{
CHECK (check == cii->getVal());
++check;
++cii;
}
// Test c++11 foreach iteration
check = 0;
for (auto& entry : coll)
{
CHECK (check == entry.getVal());
++check;
}
check = 0;
for (auto const& entry : const_coll)
{
CHECK (check == entry.getVal());
++check;
}
// Verify correct behaviour of iteration end
CHECK (! (coll.end()));
CHECK (isnil (coll.end()));
VERIFY_ERROR (ITER_EXHAUST, *coll.end() );
VERIFY_ERROR (ITER_EXHAUST, ++coll.end() );
CHECK (ii == coll.end());
CHECK (cii == coll.end());
VERIFY_ERROR (ITER_EXHAUST, ++ii );
VERIFY_ERROR (ITER_EXHAUST, ++cii );
}
CHECK (0 == Dummy::checksum());
}
/** @test using the ScopedCollection to hold a variable
* and possibly increasing number of elements, within the
* fixed limits of the maximum capacity defined by the
* ctor parameter. Any new elements will be created
* behind the already existing objects. In case
* of failure while creating an element, only
* this element gets destroyed, the rest of
* the container remains intact.
*/
void
building_StackStyle()
{
CHECK (0 == Dummy::checksum());
{
int rr = rani(100);
CollD coll(3);
CHECK (0 == coll.size());
CHECK (0 == Dummy::checksum());
Dummy& d0 = coll.emplaceElement();
CHECK (1 == coll.size());
Dummy& d1 = coll.emplace<Dummy> (rr);
CHECK (2 == coll.size());
int sum = Dummy::checksum();
// trigger the bomb
VERIFY_ERROR (SUBVERSIVE, coll.emplace<SubDummy>(rr,rr) );
CHECK ( 2 == coll.size()); // the other objects survived
CHECK (sum == Dummy::checksum());
Dummy& d2 = coll.emplace<SubDummy> (rr, rr+1);
CHECK (3 == coll.size());
CHECK (sum + rr == Dummy::checksum());
VERIFY_ERROR (CAPACITY, coll.emplaceElement());
VERIFY_ERROR (CAPACITY, coll.emplaceElement());
VERIFY_ERROR (CAPACITY, coll.emplaceElement());
CHECK (3 == coll.size());
CHECK (sum + rr == Dummy::checksum());
CHECK (d0.calc(11) == coll[0].getVal() + 11 );
CHECK (d1.calc(22) == rr + 22);
CHECK (d2.calc(33) == rr + 33);
CHECK (d2.calc(0) == rr + (rr+1) ); // SubDummy's special implementation of the acc()-function
// returns the trigger value, when the argument is zero
coll.clear();
coll.emplace<SubDummy> (11,22);
CHECK ( 1 == coll.size());
CHECK (11 == Dummy::checksum());
// NOTE DANGEROUS:
// The previously obtained references just point into the object storage.
// Thus we're now accessing a different object, even a different type!
CHECK (d0.calc(0) == 11 + 22);
// The others even point into obsoleted storage holding zombie objects
CHECK (d1.getVal() == Dummy::DEAD);
}
CHECK (0 == Dummy::checksum());
}
/** @test using the ScopedCollection according to the RAII pattern.
* For this usage style, the collection is filled right away, during
* construction. If anything goes wrong, the whole collection is
* cleared and invalidated. Consequently there is no tangible "lifecycle"
* at the usage site. Either the collection is fully usable, or not at all.
* This requires the client to provide a functor (callback) to define
* the actual objects to be created within the ScopedCollection. These
* may as well be subclasses of the base type I, provided the general
* element storage size #siz was chosen sufficiently large to hold
* those subclass instances.
*
* This test demonstrates the most elaborate usage pattern, where
* the client provides a full blown functor object #Populator,
* which even has embedded state. Generally speaking, anything
* exposing a suitable function call operator is acceptable.
*/
void
building_RAII_Style()
{
CHECK (0 == Dummy::checksum());
{
int rr = rani(100);
int trigger = 100 + 5 + 1; // prevents the bomb from exploding (since rr < 100)
CollD coll (6, Populator(rr, trigger));
CHECK (!isnil (coll));
CHECK (6 == coll.size());
CHECK (0 != Dummy::checksum());
CHECK (coll[0].calc(0) == 0 + rr);
CHECK (coll[1].calc(0) == 1 + rr + trigger);
CHECK (coll[2].calc(0) == 2 + rr);
CHECK (coll[3].calc(0) == 3 + rr + trigger);
CHECK (coll[4].calc(0) == 4 + rr);
CHECK (coll[5].calc(0) == 5 + rr + trigger);
// what does this check prove?
// - the container was indeed populated with DubDummy objects
// since the overridden version of Dummy::acc() did run and
// reveal the trigger value
// - the population was indeed done with the anonymous Populator
// instance fed to the ctor, since this object was "marked" with
// the random value rr, and adds this mark to the built values.
coll.clear();
CHECK (0 == Dummy::checksum());
// Verify Error handling while in creation:
// SubDummy explodes on equal ctor parameters
// which here happens for i==7
VERIFY_ERROR (SUBVERSIVE, CollD(10, Populator(0, 7)) );
// any already created object was properly destroyed
CHECK (0 == Dummy::checksum());
}
CHECK (0 == Dummy::checksum());
}
/** Functor to populate the Collection */
class Populator
{
uint i_;
int off_;
int trigg_;
public:
Populator (int baseOffset, int triggerCode)
: i_(0)
, off_(baseOffset)
, trigg_(triggerCode)
{ }
void
operator() (CollD::ElementHolder& storage)
{
switch (i_ % 2)
{
case 0:
storage.create<Dummy> (i_+off_);
break;
case 1:
storage.create<SubDummy> (i_+off_, trigg_);
break;
}
++i_;
}
};
/** @test for using ScopedCollection in RAII style,
* several pre-defined "populators" are provided.
* The most obvious one being just to fill the
* collection with default constructed objects.
*/
void
verify_defaultPopulator()
{
CHECK (0 == Dummy::checksum());
CollD coll (25, CollD::FillAll() );
CHECK (!isnil (coll));
CHECK (25 == coll.size());
CHECK (0 != Dummy::checksum());
for (CollD::iterator ii = coll.begin(); ii; ++ii)
{
CHECK ( INSTANCEOF (Dummy, & (*ii)));
CHECK (!INSTANCEOF (SubDummy, & (*ii)));
}
}
void
verify_subclassPopulator()
{
CHECK (0 == Dummy::checksum());
CollD coll (25, CollD::FillWith<SubDummy>() );
CHECK (!isnil (coll));
CHECK (25 == coll.size());
CHECK (0 != Dummy::checksum());
for (CollD::iterator ii = coll.begin(); ii; ++ii)
CHECK (INSTANCEOF (SubDummy, & (*ii)));
}
void
verify_iteratorPopulator()
{
typedef ScopedCollection<uint> CollI;
CollI source (25);
for (uint i=0; i < source.capacity(); ++i)
source.emplace<uint>(i); // holding the numbers 0..24
CollI coll (20, CollI::pull(source.begin()));
// this immediately pulls in the first 20 elements
CHECK (!isnil (coll));
CHECK (20 == coll.size());
CHECK (25 == source.size());
for (uint i=0; i < coll.size(); ++i)
{
CHECK (coll[i] == i );
CHECK (coll[i] == source[i]);
}
// note: the iterator is assumed to deliver a sufficient amount of elements
VERIFY_ERROR (ITER_EXHAUST, CollI (50, CollI::pull (source.begin())));
}
/** @test simulate the typical situation of a manager
* owning some embedded components. Here, our ManagerDemo
* instance owns a collection of numbers 50..1. They are
* created right while initialising the manager, and this
* initialisation is done by invoking a member function
* of the manager
*/
void
verify_embeddedCollection()
{
ManagerDemo object_with_embedded_Collection(50);
CHECK (sum(50) == object_with_embedded_Collection.useMyNumbers());
}
class ManagerDemo
{
typedef ScopedCollection<uint> CollI;
uint memberVar_;
const CollI my_own_Numbers_;
void
buildNumbers (CollI::ElementHolder& storage)
{
storage.create<uint>(memberVar_);
--memberVar_;
}
public:
ManagerDemo(uint cnt)
: memberVar_(cnt)
, my_own_Numbers_(cnt, &ManagerDemo::buildNumbers, this)
{
CHECK (0 == memberVar_);
CHECK (cnt == my_own_Numbers_.size());
}
uint
useMyNumbers()
{
uint sum(0);
for (CollI::const_iterator ii = my_own_Numbers_.begin(); ii; ++ii)
sum += *ii;
return sum;
}
};
};
LAUNCHER (ScopedCollection_test, "unit common");
}} // namespace lib::test