From d6786870f34c89f5b313fffdfb41bf46f50e2075 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 26 Mar 2018 07:47:59 +0200 Subject: [PATCH] DI: port the old Singleton unit tests all these tests are ported by drop-in replacement and should work afterwards exactly as before (and they do indeed) A minor twist was spotted though (nice to have more unit tests indeed!): Sometimes we want to pass a custom constructor *not* as modern-style lambda, but rather as direct function reference, function pointer or even member function pointer. However, we can not store those types into the closure for later lazy invocation. This is basically the same twist I run into yesterday, when modernising the thread-wrapper. And the solution is similar. Our traits class _Fun has a new typedef Functor with a suitable functor type to be instantiated and copied. In case of the Lambda this is the (anonymous) lamda class itself, but in case of a function reference or pointer it is a std::function. --- doc/design/architecture/Subsystems.txt | 2 +- src/lib/depend-inject.hpp | 26 +++- src/lib/meta/function.hpp | 11 +- tests/15library.tests | 8 +- tests/library/dependency-factory-test.cpp | 96 +++++++-------- tests/library/singleton-subclass-test.cpp | 34 +++--- tests/library/singleton-test.cpp | 4 +- tests/library/singleton-testmock-test.cpp | 64 ++++++---- wiki/thinkPad.ichthyo.mm | 137 +++++++++++++++++++--- 9 files changed, 256 insertions(+), 126 deletions(-) diff --git a/doc/design/architecture/Subsystems.txt b/doc/design/architecture/Subsystems.txt index db69d937e..9c1cf1747 100644 --- a/doc/design/architecture/Subsystems.txt +++ b/doc/design/architecture/Subsystems.txt @@ -150,7 +150,7 @@ Lifecycle Events ~~~~~~~~~~~~~~~~ The Application as a whole conducts a well defined lifecycle; whenever transitioning to the next phase, a _Lifecycle Event_ is issued. Components may register a notification hook with the central _Lifecycle Manager_ -(see 'include/lifecycle.h) to be invoked whenever a specific event is emitted. The process of registration +(see 'include/lifecycle.h') to be invoked whenever a specific event is emitted. The process of registration can be simplified by planting a static variable of type `lumiera::LifecycleHook`. WARNING: A callback enrolled this way needs to be callable at the respective point in the lifecycle, diff --git a/src/lib/depend-inject.hpp b/src/lib/depend-inject.hpp index b7d90a424..67ae8874d 100644 --- a/src/lib/depend-inject.hpp +++ b/src/lib/depend-inject.hpp @@ -103,6 +103,7 @@ ** persistent history and UNDO. ** ** @see DependencyConfiguration_test + ** @see SingletonSubclass_test ** @see subsys.hpp */ @@ -117,6 +118,7 @@ #include "lib/error.hpp" #include "lib/nocopy.hpp" #include "lib/depend2.hpp" +#include "lib/meta/trait.hpp" #include "lib/meta/function.hpp" #include "lib/sync-classlock.hpp" @@ -179,14 +181,11 @@ namespace lib { static void useSingleton(FUN&& ctor) { + using Fun = typename SubclassFactory::Fun; using Sub = typename SubclassFactory::Sub; __assert_compatible(); - static InstanceHolder singleton; - installFactory ([ctor]() - { - return singleton.buildInstance (ctor); - }); + installFactory (buildCustomSingleton (forward (ctor))); } @@ -329,6 +328,7 @@ namespace lib { static_assert (meta::_Fun(), "Need a Lambda or Function object to create a heap allocated instance"); + using Fun = typename meta::_Fun::Functor; // suitable type to store for later invocation using Res = typename meta::_Fun::Ret; using Sub = typename meta::Strip::TypePlain; @@ -348,6 +348,22 @@ namespace lib { Depend::factory = move (otherFac); } + /** wrap custom factory function to plant a singleton instance + * @remark call through this intermediary function because we need to capture a _copy_ of the functor, + * to invoke it later, on-demand. Especially we need the ability to change the type of this functor, + * since sometimes the argument is passed as function reference, which can not be instantiated, + * but needs to be wrapped into a std::function. */ + template + static Factory + buildCustomSingleton (FUN&& ctor) + { + static InstanceHolder singleton; + return ([ctor]() // copy of ctor in the closure + { + return singleton.buildInstance (ctor); + }); + } + static void temporarilyInstallAlternateFactory (SRV*& stashInstance, Factory& stashFac, Factory&& newFac) { diff --git a/src/lib/meta/function.hpp b/src/lib/meta/function.hpp index 9e6d9fe6f..1075c197c 100644 --- a/src/lib/meta/function.hpp +++ b/src/lib/meta/function.hpp @@ -67,12 +67,15 @@ namespace meta{ /** * Helper for uniform access to function signature types. * Extract the type information contained in a function or functor type, - * so it can be manipulated by metaprogramming. The embedded typedefs - * allow to pick up the return type, the sequence of argument types - * and the bare function signature type. This template works on + * so it can be manipulated by metaprogramming. This template works on * anything _function like_, irrespective if the parameter is given * as function reference, function pointer, member function pointer, - * functor object, `std::function` or lambda. + * functor object, `std::function` or lambda. The embedded typedefs + * allow to pick up + * - `Ret` : the return type + * - `Args`: the sequence of argument types as type sequence `Types` + * - `Sig` : the bare function signature type + * - `Functor` : corresponding Functor type which can be instantiated or copied. * * This template can also be used in metaprogramming with `enable_if` to enable * some definition or specialisation only if a function-like type was detected; thus diff --git a/tests/15library.tests b/tests/15library.tests index b00d48868..8f7e8777d 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -686,15 +686,15 @@ END TEST "SingletonTestMock_test" SingletonTestMock_test < @@ -73,12 +73,7 @@ namespace test{ struct SubSub : Sub - { - /** marker typedef for Depend4Test, - * allowing to pick the correct Depend - * to apply the instrumentation with the test mock. */ - typedef Sub ServiceInterface; - }; + { }; struct SubSubSub : SubSub @@ -111,7 +106,6 @@ namespace test{ verify_SubclassCreation(); verify_FactoryDefinition_is_sticky(); verify_customFactory(); - verify_temporaryReplacement(); verify_automaticReplacement(); } @@ -131,9 +125,12 @@ namespace test{ void verify_SubclassCreation() { - Depend specialAccessor(buildSingleton()); + Depend specialAccessor; Depend genericAccessor; + // configure singleton subclass (prior to first use) + DependInject::useSingleton(); + SubSub& oSub = specialAccessor(); Sub& o = genericAccessor(); @@ -155,13 +152,18 @@ namespace test{ SubSub& yetAnotherInstance = yetAnotherSpecialAccessor(); CHECK ( INSTANCEOF (SubSubSub, &yetAnotherInstance)); + + // both refer to the same configuration and thus access the singleton + CHECK (isSameObject (oSub, yetAnotherInstance)); } void verify_customFactory() { - Depend customisedAccessor(&customFactoryFunction); + DependInject::useSingleton (customFactoryFunction); + + Depend customisedAccessor; Depend otherSpecialAccessor; SubSub& oSub = otherSpecialAccessor(); @@ -175,47 +177,12 @@ namespace test{ CHECK (MAX_ID + 10 == oSubS.instanceID_); } - static void* + static SubSubSub* customFactoryFunction (void) { - static SubSubSub specialInstance; - // NOTE: the factory function is responsible - // for managing the instance's lifecycle - - specialInstance.instanceID_ = MAX_ID + 10; - return &specialInstance; - } - - - - void - verify_temporaryReplacement() - { - typedef Depend GenericAccessor; - - GenericAccessor genericAccessor; - Sub& original = genericAccessor(); - uint oID = original.instanceID_; - - SubSubSub mockObject; - Sub* shaddowedOriginal = GenericAccessor::injectReplacement (&mockObject); - - Sub& replacement = genericAccessor(); - CHECK ( isSameObject (replacement, mockObject)); - CHECK (!isSameObject (original, replacement)); - CHECK ( isSameObject (original, *shaddowedOriginal)); - - Depend special; - Depend custom; - - CHECK(!isSameObject (replacement, special() )); - CHECK(!isSameObject (replacement, custom() )); - - GenericAccessor::injectReplacement (shaddowedOriginal); - - Sub& nextFetch = genericAccessor(); - CHECK (isSameObject (original, nextFetch)); - CHECK (oID == nextFetch.instanceID_); + SubSubSub* specialInstance = new SubSubSub; + specialInstance->instanceID_ = MAX_ID + 10; + return specialInstance; } @@ -227,8 +194,26 @@ namespace test{ Sub& original = genericAccessor(); uint oID = original.instanceID_; - { - Depend4Test withinThisScope; + {////////////////////////////////////////////////////TEST-Scope + DependInject::Local mockObject; + + Sub& replacement = genericAccessor(); + CHECK ( isSameObject (replacement, *mockObject)); + CHECK (!isSameObject (original, replacement)); + + Depend special; + Depend custom; + + CHECK(!isSameObject (replacement, special() )); + CHECK(!isSameObject (replacement, custom() )); + }////////////////////////////////////////////////////(End)TEST-Scope + + Sub& nextFetch = genericAccessor(); + CHECK (isSameObject (original, nextFetch)); + CHECK (oID == nextFetch.instanceID_); + + {////////////////////////////////////////////////////TEST-Scope-2 + DependInject::Local otherMock; Sub& replacement = genericAccessor(); uint repID = replacement.instanceID_; @@ -254,11 +239,10 @@ namespace test{ CHECK (!isSameObject (original, subTypeAccess)); CHECK (repID != subTypeAccess.instanceID_); CHECK ( oID != subTypeAccess.instanceID_); - } + }////////////////////////////////////////////////////(End)TEST-Scope-2 - Sub& nextFetch = genericAccessor(); - CHECK (isSameObject (original, nextFetch)); - CHECK (oID == nextFetch.instanceID_); + CHECK (isSameObject (original, genericAccessor())); + CHECK (oID == genericAccessor().instanceID_); } }; diff --git a/tests/library/singleton-subclass-test.cpp b/tests/library/singleton-subclass-test.cpp index 521189a35..567308eea 100644 --- a/tests/library/singleton-subclass-test.cpp +++ b/tests/library/singleton-subclass-test.cpp @@ -32,7 +32,8 @@ #include "lib/util.hpp" #include "test-target-obj.hpp" -#include "lib/depend.hpp" +#include "lib/depend2.hpp" +#include "lib/depend-inject.hpp" #include @@ -65,7 +66,8 @@ namespace test{ Interface () : TestTargetObj(cnt) {} virtual ~Interface() {} - friend class lib::DependencyFactory; + friend class lib::InstanceHolder; + friend class std::default_delete; }; int Interface::cnt = 0; @@ -86,13 +88,12 @@ namespace test{ /***************************************************************//** - * @test specialised variant of the Singleton Factory, for creating - * subclasses (implementation classes) without coupling the - * caller to the concrete class type. - * Expected results: an instance of the subclass is created. + * @test specific dependency-injection setup, to create a singleton + * subclass (implementation class) instance, without coupling + * the caller to the concrete type. + * @remark Expected results: an instance of the subclass is created. * @see lib::Depend - * @see lib::buildSingleton() - * @see lib/dependency-factory.hpp + * @see lib/depend-inject.hpp */ class SingletonSubclass_test : public Test { @@ -106,12 +107,11 @@ namespace test{ Interface::setCountParam(num); - // marker to declare the concrete type to be created - DependencyFactory::InstanceConstructor factoryFunction = buildSingleton(); + // configuration to use the subclass on demand + DependInject::useSingleton(); - // define an instance of the Singleton factory, - // specialised to create the concrete Type passed in - Depend instance (factoryFunction); + // define an instance of the Singleton factory as always... + Depend instance; // Now use the Singleton factory... // Note: we get the Base type @@ -119,6 +119,8 @@ namespace test{ Interface& t2 = instance(); CHECK (isSameObject (t1, t2), "not a Singleton, got two different instances." ); + CHECK ( INSTANCEOF (Impl,&t1)); // got the subclass as expected + CHECK ("Implementation" == t2.identify()); cout << "calling a non-static method on the Singleton-" << t1.identify() << endl @@ -132,11 +134,13 @@ namespace test{ void verify_error_detection () { - VERIFY_ERROR (LIFECYCLE, Depend instance (buildSingleton()) ); - VERIFY_ERROR (LIFECYCLE, Depend instance (buildSingleton()) ); + VERIFY_ERROR (LIFECYCLE, DependInject::useSingleton() ); Depend newFactory; CHECK ( INSTANCEOF (Impl, &newFactory() )); // works as before + +//////////does not compile due to incompatible baseclass +// DependInject::useSingleton(); } }; diff --git a/tests/library/singleton-test.cpp b/tests/library/singleton-test.cpp index 1fd565fb4..4a57c7060 100644 --- a/tests/library/singleton-test.cpp +++ b/tests/library/singleton-test.cpp @@ -31,7 +31,7 @@ #include "lib/util.hpp" #include "test-target-obj.hpp" -#include "lib/depend.hpp" +#include "lib/depend2.hpp" #include @@ -59,7 +59,7 @@ namespace test{ protected: TargetObj () : TestTargetObj(cnt) {} - friend class lib::DependencyFactory; + friend class lib::InstanceHolder; }; int TargetObj::cnt = 0; diff --git a/tests/library/singleton-testmock-test.cpp b/tests/library/singleton-testmock-test.cpp index 8749b7957..90f0cd91d 100644 --- a/tests/library/singleton-testmock-test.cpp +++ b/tests/library/singleton-testmock-test.cpp @@ -27,7 +27,7 @@ #include "lib/test/run.hpp" -#include "lib/depend.hpp" +#include "lib/depend-inject.hpp" #include "lib/util.hpp" #include "lib/format-cout.hpp" @@ -45,14 +45,14 @@ namespace test{ * Client Class normally to be instantiated as Singleton. * But for tests, this class should be replaced by a Mock.... */ - class TestSingletonO + class TestSingO { int callCnt_; Symbol typid_; _Fmt msg_; public: - TestSingletonO(Symbol ty="TestSingletonO") + TestSingO(Symbol ty="TestSingO") : callCnt_(0) , typid_(ty) , msg_("%s::doIt() call=%d\n") @@ -61,7 +61,7 @@ namespace test{ } virtual - ~TestSingletonO() + ~TestSingO() { TRACE (test, "dtor %s", typid_.c()); } @@ -83,17 +83,23 @@ namespace test{ /** * Mock-1 to replace the Client Class... */ - struct Mock_1 : TestSingletonO + struct Mock_1 : TestSingO { - Mock_1() : TestSingletonO("Mock_1") { }; + Mock_1() : TestSingO("Mock_1") { }; }; /** * Mock-2 to replace the Client Class... + * @note no default ctor */ - struct Mock_2 : TestSingletonO + struct Mock_2 : TestSingO { - Mock_2() : TestSingletonO("Mock_2") { }; + int id; + + Mock_2(Literal specialID, int i) + : TestSingO{Symbol (_Fmt{"%s_%d"} % specialID % i)} + , id{i} + { }; }; @@ -112,7 +118,7 @@ namespace test{ * Client Object, then replace it by two different mocks, * and finally restore the original Client Object. * @see lib::Depend - * @see lib::test::Depend4Test + * @see depend-inject.hpp * @see DependencyFactory_test */ class SingletonTestMock_test : public Test @@ -121,28 +127,36 @@ namespace test{ void run (Arg) { - Depend sing; + Depend sing; sing().doIt(); sing().doIt(); CHECK (sing().getCnt() == 2); - Mock_1 mock_1; - TestSingletonO* original = - sing.injectReplacement (&mock_1); - sing().doIt(); - sing().doIt(); - sing().doIt(); - sing().doIt(); - sing().doIt(); - CHECK (sing().getCnt() == 5); + { + // shadow by local Mock instance + DependInject::Local mock_1; + sing().doIt(); + sing().doIt(); + sing().doIt(); + sing().doIt(); + sing().doIt(); + CHECK (sing().getCnt() == 5); + + // shadow again by different local Mock, this time with special ctor call + int instanceID = 0; + DependInject::Local mock_2 ([&]{ return new Mock_2{"Mock", instanceID}; }); + + // NOTE: the ctor call for the Mock really happens delayed... + instanceID = rand() % 10; + sing().doIt(); // ctor invoked on first access + CHECK (sing().getCnt() == 1); + + // can access the Mock for instrumentation + CHECK (instanceID == mock_2->id); + + }// original instance automatically un-shadowed here - Mock_2 mock_2; - sing.injectReplacement (&mock_2); - sing().doIt(); - CHECK (sing().getCnt() == 1); - - sing.injectReplacement (original); // un-shadowing original instance CHECK (sing().getCnt() == 2); sing().doIt(); CHECK (sing().getCnt() == 3); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 85b9a3735..3e2143b80 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -27775,28 +27775,29 @@ - - - + + + + - - + + - - + + - - - + + + - - - - + + + + @@ -27824,6 +27825,54 @@ + + + + + + +

+ die von der alten DependencyFactory abhängen +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -27831,6 +27880,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -27866,6 +27972,8 @@
+ + @@ -27891,6 +27999,7 @@ +