funny enough this possible memory corruption didn't happen in the unit test, because my compiler optimised the additional int field of class SubDummy, making it the same size of the baseclass. Now matters should be safe.
476 lines
14 KiB
C++
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
|
|
|