LUMIERA.clone/tests/library/scoped-collection-test.cpp
Ichthyostega ada5cefaaf re-arrange tests according to layer structure
the buildsystem will now pick up and link
all test cases according to the layer, e.g.
backend tests will automatically be linked
against the backend + library solely.
2013-01-07 05:43:01 +01:00

476 lines
14 KiB
C++

/*
ScopedCollection(Test) - holding and owning a fixed collection of noncopyable objects
Copyright (C) Lumiera.org
2012, Hermann Vosseler <Ichthyostega@web.de>
This program 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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/util.hpp"
#include "lib/scoped-collection.hpp"
#include "lib/test/testdummy.hpp"
#include <cstdlib>
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
acc (int i)
{
if (!i)
return getVal() + trigger_;
else
return Dummy::acc(i);
}
public:
SubDummy (int id, int trigger)
: Dummy(id)
, trigger_(trigger)
{
if (trigger == getVal())
throw new 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 lumiera::error::LUMIERA_ERROR_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)
{
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.appendNew<Dummy>(i);
int check=0;
CollD::iterator ii = coll.begin();
while (ii)
{
CHECK (check == ii->getVal());
CHECK (check == ii->acc(+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;
}
// 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 = rand() % 100;
CollD coll(3);
CHECK (0 == coll.size());
CHECK (0 == Dummy::checksum());
Dummy& d0 = coll.appendNewElement();
CHECK (1 == coll.size());
Dummy& d1 = coll.appendNew<Dummy> (rr);
CHECK (2 == coll.size());
int sum = Dummy::checksum();
// trigger the bomb
VERIFY_ERROR (SUBVERSIVE, coll.appendNew<SubDummy>(rr,rr) );
CHECK ( 2 == coll.size()); // the other objects survived
CHECK (sum == Dummy::checksum());
Dummy& d2 = coll.appendNew<SubDummy> (rr, rr+1);
CHECK (3 == coll.size());
CHECK (sum + rr == Dummy::checksum());
VERIFY_ERROR (CAPACITY, coll.appendNewElement());
VERIFY_ERROR (CAPACITY, coll.appendNewElement());
VERIFY_ERROR (CAPACITY, coll.appendNewElement());
CHECK (3 == coll.size());
CHECK (sum + rr == Dummy::checksum());
CHECK (d0.acc(11) == coll[0].getVal() + 11 );
CHECK (d1.acc(22) == rr + 22);
CHECK (d2.acc(33) == rr + 33);
CHECK (d2.acc(0) == rr + (rr+1) ); // SubDummy's special implementation of the acc()-function
// returns the trigger value, when the argument is zero
coll.clear();
coll.appendNew<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.acc(0) == 11 + 22);
// The others even point into obsoleted storage holding zombie objects
CHECK (d1.acc(44) == rr + 44);
}
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 = rand() % 100;
int trigger = 101;
CollD coll (6, Populator(rr, trigger));
CHECK (!isnil (coll));
CHECK (6 == coll.size());
CHECK (0 != Dummy::checksum());
CHECK (coll[0].acc(0) == 0 + rr);
CHECK (coll[1].acc(0) == 1 + rr + trigger);
CHECK (coll[2].acc(0) == 2 + rr);
CHECK (coll[3].acc(0) == 3 + rr + trigger);
CHECK (coll[4].acc(0) == 4 + rr);
CHECK (coll[5].acc(0) == 5 + rr + trigger);
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.appendNew<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