LUMIERA.clone/tests/basics/typed-counter-test.cpp
Ichthyostega 3395d002bd Library: helper to produce threadsafe member-IDs for a family of objects
This is a little bit of functionality needed again and again;
first I thought to use the TypedCounter, but this would be overkill,
since we do not actually need different instances, and we do not need
to select by type when incrementing the counter. In fact, we do not
even need anything beyond just allocating a number.

So I made a new class, which can be used RAII style
2017-01-14 03:07:48 +01:00

393 lines
12 KiB
C++

/*
TypedCounter(Test) - managing a set of type based contexts
Copyright (C) Lumiera.org
2009, 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.
* *****************************************************/
/** @file typed-counter-test.cpp
** Stress test to verify type-based contexts.
** Besides a simple usage (unit) test, this test performs a massively multithreaded
** test of the type-based contexts, through use of the TypedCounter. The idea behind
** this facility is to provide a context, in which type-IDs can be allocated. In the
** case of the TypedCounter, these type-IDs are used to index into a vector of counters,
** this way allowing to access a counter for a given type.
**
** This test builds several "families", each sharing a TypedCounter. Each of these
** families runs a set of member threads, which concurrently access the TypedCounter of
** this family. After waiting for all threads to finish, we compare the checksum built
** within the target objects with the checksum collected through the TypedCounters.
**
** @see thread-wrapper-test.cpp
** @see thread-wrapper-join-test.cpp
**
*/
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/typed-counter.hpp"
#include "lib/scoped-ptrvect.hpp"
#include "backend/thread-wrapper.hpp"
#include "lib/util-foreach.hpp"
#include "lib/sync.hpp"
#include <functional>
#include <cstdlib>
#include <vector>
#include <time.h>
namespace lib {
namespace test{
using backend::ThreadJoinable;
using util::for_each;
using util::isnil;
using std::placeholders::_1;
using std::bind;
using std::ref;
using std::vector;
using std::rand;
namespace { // test data and helpers...
const uint MAX_FAMILIES = 4; ///< maximum separate "families", each sharing a TypedCounter
const uint MAX_MEMBERS = 10; ///< maximum members per family (member == test thread)
const uint MAX_ITERATIONS = 50; ///< maximum iterations within a single test thread
const uint MAX_DELAY_ms = 3; ///< maximum delay between check iterations
/* Hint: number of threads = MEMBERS * FAMILIES
*
* The values set here are fairly conservative,
* but increasing the number of threads causes the test suite
* to fail frequently. Please increase these values e.g.
* to 20 and 50 for a more thorough stress test!
*/
/**
* Interface to a family of dummy types
*/
class DummyType
{
public:
virtual ~DummyType() { }
/** core test operation: do a random increment or decrement
* on the provided TypedCounter instance, and also save an
* account to a local embedded checksum for verification */
virtual void doCount (TypedCounter&) =0;
virtual void collect_externalCount (TypedCounter&) =0;
virtual void collect_internalCount () =0;
};
/* === Checksums === */
long sum_TypedCounter_; ///< Sum1: calculated from TypedCounter
long sum_internal_; ///< Sum2: control value calculated from Dummy::localChecksum_
void
accountExternal (DummyType& target, TypedCounter& counter_to_use)
{
target.collect_externalCount (counter_to_use);
}
void
accountInternal (DummyType& target)
{
target.collect_internalCount();
}
/**
* To actually drive the TypedCounter invocations, we need a family
* of different (but of course related) types. Actually, we use these
* subclasses here also to carry out the invocations and the accounting
* to build up the checksums for verification.
*/
template<uint i>
class Dummy
: public DummyType
, public lib::Sync<>
{
long localChecksum_;
void
record_internal (int increment)
{
Lock protect(this);
localChecksum_ += increment;
}
void
doCount (TypedCounter& counter)
{
// note: deliberately *not* synchronised
if (rand() % 2)
{
counter.inc<Dummy>();
record_internal (+1);
}
else
{
counter.dec<Dummy>();
record_internal (-1);
}
}
void
collect_externalCount (TypedCounter& counter)
{
// Lock not necessary, because of invocation sequence
sum_TypedCounter_ += counter.get<Dummy>();
}
void
collect_internalCount ()
{
sum_internal_ += localChecksum_;
}
public:
Dummy() : localChecksum_(0) {}
};
/**
* Collection of target functions,
* to be invoked during the test run
*/
struct DummyTarget
{
typedef ScopedPtrVect<DummyType> TargetVect;
TargetVect targets_;
DummyTarget ()
: targets_(10)
{
targets_.manage(new Dummy<0>);
targets_.manage(new Dummy<1>);
targets_.manage(new Dummy<2>);
targets_.manage(new Dummy<3>);
targets_.manage(new Dummy<4>);
targets_.manage(new Dummy<5>);
targets_.manage(new Dummy<6>);
targets_.manage(new Dummy<7>);
targets_.manage(new Dummy<8>);
targets_.manage(new Dummy<9>);
}
/** entry point for the SingleCheck instances
* to trigger off a single invocation
*/
void
torture (TypedCounter& counter_to_use)
{
uint victim = (rand() % 10);
targets_[victim].doCount (counter_to_use);
}
typedef TargetVect::iterator iterator;
/** allow Iteration over all targets in the TargetVect */
iterator begin() { return targets_.begin(); }
iterator end() { return targets_.end(); }
};
DummyTarget targetCollection;
/**
* Each single check runs in a separate thread
* and performs a random sequence of increments
* and decrements on random targets.
*/
class SingleCheck
: ThreadJoinable
{
public:
SingleCheck (TypedCounter& counter_to_use)
: ThreadJoinable("TypedCounter_test worker Thread"
, bind (&SingleCheck::runCheckSequence, this, ref(counter_to_use), (rand() % MAX_ITERATIONS))
)
{ }
~SingleCheck () { this->join(); }
private:
void runCheckSequence(TypedCounter& counter, uint iterations)
{
do
{
usleep (1000 * (rand() % MAX_DELAY_ms));
targetCollection.torture (counter);
}
while (iterations--);
}
};
/**
* Family of individual checks, sharing
* a common TypedCounter instance.
*/
struct TestFamily
{
TypedCounter ourCounter_;
ScopedPtrVect<SingleCheck> checks_;
TestFamily()
: checks_(MAX_MEMBERS)
{
uint members (1 + rand() % MAX_MEMBERS);
while (members--)
checks_.manage (new SingleCheck (ourCounter_));
}
~TestFamily()
{
checks_.clear(); // blocks until all test threads finished
account();
}
void
account()
{
for_each ( targetCollection
, bind (accountExternal, _1, ourCounter_)
);
}
};
/** a series of independent context sets */
typedef ScopedPtrVect<TestFamily> FamilyTable;
}
/***********************************************************************************//**
* @test build multiple sets of type-based contexts and run a simple counting operation
* in each of them concurrently. Check the proper allocation of type-IDs in each
* context and verify correct counting operation by checksum.
*
* @see TypedAllocationManager
* @see typed-counter.hpp
*/
class TypedCounter_test : public Test
{
void
run (Arg)
{
simpleUsageTest();
tortureTest();
}
void
simpleUsageTest ()
{
TypedCounter myCounter;
CHECK (isnil (myCounter));
CHECK (0==myCounter.size());
CHECK (0 == myCounter.get<short>());
CHECK (0 < myCounter.size());
// probably greater than 1;
// other parts of the application allocate type-IDs as well
// now allocate a counter for a type not seen yet
struct X { };
struct U { };
CHECK (0 == myCounter.get<X>());
size_t sX = myCounter.size();
CHECK (0 == myCounter.get<U>());
CHECK (sX + 1 == myCounter.size());
CHECK (0 == myCounter.get<X>());
CHECK (sX + 1 == myCounter.size());
CHECK (-1 == myCounter.dec<X>());
CHECK (-2 == myCounter.dec<X>());
CHECK (+1 == myCounter.inc<U>());
CHECK (-2 == myCounter.get<X>());
CHECK (+1 == myCounter.get<U>());
// each new type has gotten a new "slot" (i.e. a distinct type-ID)
IxID typeID_short = TypedContext<TypedCounter>::ID<short>::get();
IxID typeID_X = TypedContext<TypedCounter>::ID<X>::get();
IxID typeID_U = TypedContext<TypedCounter>::ID<U>::get();
CHECK (0 < typeID_short);
CHECK (0 < typeID_X);
CHECK (0 < typeID_U);
CHECK (typeID_short < typeID_X);
CHECK (typeID_X < typeID_U);
// type-IDs are allocated in the order of first usage
CHECK (sX + 1 == myCounter.size());
}
void
tortureTest ()
{
std::srand (::time (NULL));
sum_TypedCounter_ = 0;
sum_internal_ = 0;
uint num_Families (1 + rand() % MAX_FAMILIES);
FamilyTable testFamilies(num_Families);
for (uint i=0; i<num_Families; ++i)
testFamilies.manage (new TestFamily);
testFamilies.clear(); // blocks until all threads have terminated
for_each (targetCollection, accountInternal);
CHECK (sum_TypedCounter_ == sum_internal_);
}
};
/** Register this test class... */
LAUNCHER (TypedCounter_test, "unit common");
}} // namespace lib::test