lumiera_documentation/tests/lib/scoped-collection-test.cpp
Ichthyostega 0178e11e65 BufferProvider: simplify OutputSlot implementation structure
The old implementation relied on two layered APIs:
one for the client and one for the implementer of
the Allocation state. This is logically sound, yet
clumsy in terms of design, since the internal API
turns out to be void of actual meaning.

It seems possible to eliminate this confusing structure
by a clear separation of ''implementation realms''
 * the ''mechanics'' of connection state management
 * the actual implementation of the connection

The first can be provided as generic template subclass,
while the latter should be confined entirely into the
implementation of the `Connection` interface.

Doing so requires to keep each part of the implementation
strictly focused onto its purpose — something I obviously
was not able to see at the time when writing this old
implementation, notably because I was completely guided
and driven by the test/dummy implementation without
considering how an actual implementation of
low-level data output would work.
2026-03-14 03:36:15 +01: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 "test/run.hpp"
#include "test/test-helper.hpp"
#include "test/tracking-dummy.hpp"
#include "lib/scoped-collection.hpp"
#include "lib/util.hpp"
using test::Dummy;
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 (not bool(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 (not 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 SubDummy 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