/* PolymorphicValue(Test) - verify handling of opaque polymorphic values Copyright (C) 2011, Hermann Vosseler   **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 polymorphic-value-test.cpp ** unit test \ref PolymorphicValue_test */ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" #include "lib/util-foreach.hpp" #include "lib/polymorphic-value.hpp" #include namespace lib { namespace test{ using ::Test; using util::for_each; using util::unConst; using util::isSameObject; using lumiera::error::LUMIERA_ERROR_ASSERTION; namespace { // test dummy hierarchy // Note: largely varying space requirements // correct function depending on concrete class struct Interface { virtual ~Interface() {} virtual long apiFunc() =0; virtual long& localSum() =0; bool operator== (Interface const& o) const { return unConst(this)->localSum() == unConst(o).localSum(); } }; const uint MAX_RAND = 1000; const uint MAX_ELM = 111; const uint MAX_SIZ = sizeof(long[MAX_ELM]); /* Checksums to verify proper ctor-dtor calls and copy operations */ long _checkSum = 0; long _callSum = 0; uint _created = 0; /** * Template to generate concrete implementation classes. * @note the generated classes vary largely in size, and * moreover the actual place to store the checksum * also depends on that size parameter. */ template struct Imp : BASE { long localData_[ii]; ~Imp() { mark (-1 * localSum()); CHECK (0 == localSum()); } Imp() { REQUIRE (0 < ii); localSum() = 0; mark(ii); ++ _created; } Imp (Imp const& o) { ++ _created; copyData (o); _checkSum += localSum(); }// adjust, because we've gotten two identical instances Imp& operator= (Imp const& o) { _checkSum -= localSum(); copyData (o); _checkSum += localSum(); return *this; } private: virtual long apiFunc() { long rr = ii * (1 + rani(MAX_RAND)); mark (rr); _callSum += rr; return rr; } long& localSum() { return localData_[ii-1]; } void mark (long markerValue) { localSum() += markerValue; _checkSum += markerValue; } void copyData (Imp const& o) { for (uint i=0; i; using TestList = std::vector ; /******************************************************************************//** * @test build a bunch of PolymorphicValue objects. Handle them like copyable * value objects, without knowing the exact implementation type; moreover * execute implementation internals only disclosed to the concrete subtype. * Verify correctness through checksums. */ class PolymorphicValue_test : public Test { virtual void run (Arg) { _checkSum = 0; _callSum = 0; _created = 0; seedRand(); verifyBasics(); { TestList objs = createOpaqueValues (); for_each (objs, operate); } CHECK (0 == _checkSum); // all dead verifyOverrunProtection(); verifyCopySupportDetectionMetafunctions(); } TestList createOpaqueValues () { TestList list; list.push_back (PolyVal::build> () ); list.push_back (PolyVal::build> () ); list.push_back (PolyVal::build>() ); list.push_back (PolyVal::build> () ); list.push_back (PolyVal::build> () ); return list; } //note: copy static void operate (PolyVal& elm) { PolyVal myLocalVal(elm); CHECK (elm == myLocalVal); long prevSum = _callSum; long randVal = myLocalVal->apiFunc(); CHECK (prevSum + randVal == _callSum); CHECK (elm != myLocalVal); elm = myLocalVal; CHECK (elm == myLocalVal); CHECK (!isSameObject (elm, myLocalVal)); CHECK (sizeof(myLocalVal) <= MAX_SIZ + polyvalue::Trait::ADMIN_OVERHEAD + _ALIGN_); } void verifyBasics() { typedef Imp MaximumSizedImp; // Standard case: no copy support by client objects verifyCreation_and_Copy(); // Special case: client objects expose extension point for copy support using CopySupportAPI = polyvalue::CopySupport; // Copy support API declared as sub-interface using CopySupportingImp = Imp; // insert this sub-interface between public API and Implementation using OptimalPolyVal = PolymorphicValue; // Make the Holder use this special attachment point CHECK (sizeof(OptimalPolyVal) < sizeof(PolyVal)); // results in smaller Holder and less implementation overhead verifyCreation_and_Copy(); } template void verifyCreation_and_Copy() { using Holder = PV; using ImpType = IMP; using Api = PV::Interface ; long prevSum = _checkSum; uint prevCnt = _created; Holder val = Holder::template build(); CHECK (prevSum+111 == _checkSum); // We got one primary ctor call CHECK (prevCnt+1 <= _created); // Note: usually, the compiler optimises CHECK (prevCnt+2 >= _created); // and skips the spurious copy-operation CHECK (sizeof(Holder) >= sizeof(ImpType)); Api& api = val; CHECK (isSameObject(api,val)); CHECK (INSTANCEOF(ImpType, &api)); prevCnt = _created; Holder val2(val); // invoke copy ctor without knowing the implementation type api.apiFunc(); CHECK (val != val2); // invoking the API function had an sideeffect on the state val = val2; // assignment of copy back to the original... CHECK (val == val2); // cancels the side effect CHECK (prevCnt+1 == _created); // one new embedded instance was created by copy ctor } void verifyOverrunProtection() { typedef Imp OversizedImp; CHECK (MAX_SIZ < sizeof(OversizedImp)); #if false ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #537 : restore throwing ASSERT VERIFY_ERROR (ASSERTION, PolyVal::build() ); #endif ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #537 : restore throwing ASSERT } /** @test internally, PolymorphicValue uses some metafunctions * to pick a suitable code path, based on the presence of helper functions * on the API of the embedded objects. Default is no support by these objects, * which then requires to use a more expensive implementation. Sometimes it's * desirable to support _cloning only_ (copy ctor), but no assignment after * the fact. In this special case, a support API with only a `cloneInto()` member * can be implemented, causing the PolymorphicValue container to raise an * exception in case the copy operator is invoked. */ void verifyCopySupportDetectionMetafunctions() { typedef polyvalue::CopySupport CopySupportAPI; typedef polyvalue::CloneValueSupport CloneOnlyAPI; CHECK ( !polyvalue::exposes_CloneFunction::value ); CHECK ( polyvalue::exposes_CloneFunction::value ); CHECK ( polyvalue::exposes_CloneFunction::value ); CHECK ( polyvalue::allow_Clone_but_no_Copy::value ); CHECK ( !polyvalue::allow_Clone_but_no_Copy::value ); } }; LAUNCHER (PolymorphicValue_test, "unit common"); }} // namespace lib::test