/* TypedCounter(Test) - managing a set of type based contexts Copyright (C) Lumiera.org 2009, Hermann Vosseler 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 #include #include #include 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 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(); record_internal (+1); } else { counter.dec(); record_internal (-1); } } void collect_externalCount (TypedCounter& counter) { // Lock not necessary, because of invocation sequence sum_TypedCounter_ += counter.get(); } 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 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 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 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()); CHECK (1 == myCounter.size()); CHECK (0 == myCounter.get()); CHECK (2 == myCounter.size()); CHECK (-1 == myCounter.dec()); CHECK (-2 == myCounter.dec()); CHECK (+1 == myCounter.inc()); CHECK (-2 == myCounter.get()); CHECK (+1 == myCounter.get()); CHECK (1 == TypedContext::ID::get()); CHECK (2 == TypedContext::ID::get()); CHECK (2 == 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