/* DependencyConfiguration(Test) - verify configuration for injecting dependencies Copyright (C) 2018, 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 dependency-configuration-test.cpp ** unit test \ref DependencyConfiguration_test ** @remark this test was written 3/2018 as a byproduct of the third rewrite ** of the framework for singletons and dependency-injection. It is ** quite redundant with the previously existing DependencyFactory_test */ #include "lib/test/run.hpp" #include "lib/format-cout.hpp" #include "lib/test/test-helper.hpp" #include "lib/util.hpp" #include "lib/depend.hpp" #include "lib/depend-inject.hpp" namespace lib { namespace test{ using ::Test; using util::isSameObject; namespace { struct Dum : util::NonCopyable { virtual ~Dum() { } virtual int probe() =0; }; int checksum = 0; template struct Dummy : Dum { Dummy() { checksum += N; } ~Dummy() { checksum -= N; } virtual int probe() override { return N * checksum; } }; } using LERR_(LIFECYCLE); using LERR_(FATAL); /***************************************************************************//** * @test verify the various modes of creating dependencies. * - standard case is singleton creation * - configuration of a specific subclass for the singleton * - expose a service with explicit lifecycle * - use of a custom factory function * - injection of a mock implementation for unit tests * * @remark this test basically covers the same ground as DependencyFactory_test; * but while the latter exists since our second rewrite of lib::Depend (2013), * this test here is a byproduct of the third rewrite from 2018 and focuses * more on the configuration and instance identities. * @see lib::Dependency * @see Singleton_test */ class DependencyConfiguration_test : public Test { virtual void run (Arg) { checksum = 0; verify_Singleton(); verify_SubclassSingleton(); verify_expose_Service_with_Lifecycle(); verify_automaticReplacement(); verify_customFactory(); CHECK (9+7+5+1 == checksum); // singletons stay alive until application shutdown } /** @test without special configuration, singletons are injected as dependency */ void verify_Singleton() { Depend> dep11; Depend> dep5; Depend> dep12; CHECK (1 == sizeof(dep11)); CHECK (1 == sizeof(dep12)); CHECK (1 == sizeof(dep5)); // no singleton instance created yet CHECK ( 0 == checksum ); CHECK ( 1*1 == dep11().probe() ); CHECK ( 1 == checksum ); CHECK ((1+5)*5 == dep5().probe() ); CHECK ((1+5) == checksum ); CHECK ((1+5)*1 == dep12().probe() ); CHECK ((1+5) == checksum ); CHECK (not isSameObject (dep11, dep12)); CHECK ( isSameObject (dep11(), dep12())); } /** @test preconfigure a specific subclass to be injected as singleton dependency */ void verify_SubclassSingleton() { // unable to create singleton instance of abstract baseclass VERIFY_ERROR (FATAL, Depend{}() ); CHECK ((1+5) == checksum ); Depend dumm; DependInject::useSingleton>(); CHECK ((1+5) == checksum ); CHECK ((1+5+7)*7 == dumm().probe() ); CHECK ((1+5+7) == checksum ); VERIFY_ERROR (LIFECYCLE, DependInject::useSingleton>() ); CHECK ((1+5+7)*7 == Depend{}().probe() ); CHECK ((1+5+7)*7 == dumm().probe() ); CHECK ((1+5+7) == checksum ); } /** @test expose a dedicated service instance, which can be shut down */ void verify_expose_Service_with_Lifecycle() { CHECK ((1+5+7) == checksum ); struct SubDummy : Dummy<3> { virtual int probe() override { return offset - checksum; } int offset = 0; }; Depend> dep3; CHECK ((1+5+7) == checksum ); { DependInject>::ServiceInstance service{}; CHECK (service); CHECK ((1+5+7+3) == checksum ); CHECK (-(1+5+7+3) == dep3().probe() ); CHECK ((1+5+7+3) == checksum ); service->offset = (1+5+7); CHECK ( -3 == dep3().probe() ); CHECK ((1+5+7+3) == checksum ); } CHECK ((1+5+7) == checksum ); VERIFY_ERROR (LIFECYCLE, dep3().probe() ); VERIFY_ERROR (LIFECYCLE, DependInject::ServiceInstance{} ); CHECK ((1+5+7) == checksum ); } /** @test injecting test mocks temporarily */ void verify_automaticReplacement() { Depend dumm; Depend> depp; CHECK ((1+5+7) == checksum ); CHECK ((1+5+7)*7 == dumm().probe() ); VERIFY_ERROR (LIFECYCLE, depp().probe() ); struct Mock : Dummy<3> { virtual int probe() override { return response; } int response = -1; }; {////////////////////////////////////////////////////TEST-Scope DependInject::Local mockDumm; DependInject>::Local mockDummy3; CHECK ((1+5+7) == checksum ); CHECK (!mockDumm); CHECK (!mockDummy3); CHECK (-1 == dumm().probe() ); // NOTE: now returning the response from the mock instance CHECK ( mockDumm); CHECK (!mockDummy3); CHECK ((1+5+7+3) == checksum ); CHECK (-1 == mockDumm->probe() ); CHECK ((1+5+7+3) == checksum ); mockDumm->response = 11; CHECK (11 == dumm().probe() ); // NOTE: now returning the response changed on the mock instance CHECK (!mockDummy3); // the second mock is still in not yet created state... CHECK ((1+5+7+3) == checksum ); CHECK (-1 == depp().probe() ); CHECK ((1+5+7+3+3) == checksum ); // ...and now we got a second mock instance! CHECK ( mockDummy3); CHECK (-1 == mockDummy3->probe() ); CHECK ((1+5+7+3+3) == checksum ); mockDummy3->response = 22; CHECK (22 == depp().probe() ); mockDumm->response = 12; CHECK (22 == depp().probe() ); // these are really two distinct instances CHECK (12 == dumm().probe() ); CHECK ((1+5+7+3+3) == checksum ); }////////////////////////////////////////////////////(End)TEST-Scope // Back to normal: the Mocks are gone, original behaviour uncovered CHECK ((1+5+7) == checksum ); CHECK ((1+5+7)*7 == dumm().probe() ); VERIFY_ERROR (LIFECYCLE, depp().probe() ); CHECK ((1+5+7) == checksum ); {/////////////////////////////////////////////////////////Service-Scope DependInject>::ServiceInstance service{}; CHECK ((1+5+7+3) == checksum ); // NOTE: we got a new Dummy<3> service instance CHECK (-1 == depp().probe() ); // ..... which returns the pristine mock response service->response = 33; CHECK (33 == depp().probe() ); CHECK ((1+5+7+3) == checksum ); {/////////////////////////////////////////////////////////NESTED-TEST-Scope DependInject>::Local mockDummy31; CHECK (!mockDummy31); CHECK ((1+5+7+3) == checksum ); // ...while SerivceInstance is created eagerly CHECK (-1 == depp().probe() ); // the Local mock instance is only created on-demand CHECK ((1+5+7+3+3) == checksum ); mockDummy31->response = 44; CHECK (44 == depp().probe() ); CHECK (44 == mockDummy31->probe() ); CHECK (33 == service->probe() ); CHECK (mockDummy31->response != service->response); service->response = 34; CHECK (44 == depp().probe() ); // NOTE: remains shadowed by the mockDummy CHECK (44 == mockDummy31->probe() ); CHECK (34 == service->probe() ); CHECK ((1+5+7+3+3) == checksum ); }/////////////////////////////////////////////////////////(End)NESTED-TEST-Scope // Now the mock is gone and the service instance becomes uncovered CHECK ((1+5+7+3) == checksum ); CHECK (34 == depp().probe() ); // now reveals the response changed from within the nested test scope CHECK ((1+5+7+3) == checksum ); }/////////////////////////////////////////////////////////(End)Service-Scope // Back to normal: Mocks is gone, Service is shutdown, original behaviour uncovered CHECK ((1+5+7) == checksum ); VERIFY_ERROR (LIFECYCLE, depp().probe() ); CHECK ((1+5+7)*7 == dumm().probe() ); CHECK ((1+5+7) == checksum ); } /** @test instance creation can be preconfigured with a closure. * Both Singleton and Test-Mock creation can optionally be performed through a * user provided Lambda or Functor. To demonstrate this, we use a `Veryspecial` local class, * which takes an `int&` as constructor parameter -- and we create the actual instance through * a lambda, which happens to capture a local variable by reference. * @warning this can be dangerous; in the example demonstrated here, the created singleton instance * continues to live until termination of the test-suite. After leaving this test function, * it thus holds a dangling reference, pointing into stack memory.... */ void verify_customFactory() { CHECK ((1+5+7) == checksum ); struct Veryspecial : Dummy<9> { Veryspecial(int& ref) : magic_{ref} { } int& magic_; virtual int probe() override { return magic_++; } }; // NOTE: the following is rejected due to missing default ctor DependInject>::useSingleton(); VERIFY_ERROR (FATAL, Depend>{}() ); int backdoor = 22; DependInject>::useSingleton ([&]{ return new Veryspecial{backdoor}; }); CHECK ((1+5+7) == checksum ); CHECK ( 22 == backdoor ); Depend> tricky; CHECK ((1+5+7) == checksum ); CHECK (22 == backdoor ); CHECK (22 == tricky().probe()); CHECK (23 == backdoor ); CHECK ((1+5+7+9) == checksum ); // Veryspecial Dummy<9> subclass was created on the heap // and will continue to live there until the testsuite terminates backdoor = 41; CHECK (41 == tricky().probe()); CHECK (42 == backdoor ); Depend dumm; CHECK ((1+5+7+9)*7 == dumm().probe() ); {////////////////////////////////////////////////////TEST-Scope ////////////// NOTE: the following does not compile // // since Veryspecial has no default ctor... // // // DependInject::Local impossible; DependInject::Local insidious ([&]{ return new Veryspecial{backdoor}; }); CHECK ((1+5+7+9) == checksum ); CHECK (not insidious); CHECK (42 == dumm().probe() ); CHECK (43 == backdoor ); CHECK ((1+5+7+9+9) == checksum ); CHECK (isSameObject (dumm(), *insidious)); CHECK (43 == tricky().probe()); CHECK (44 == backdoor ); backdoor = -1; CHECK (-1 == dumm().probe() ); CHECK ( 0 == backdoor ); CHECK ((1+5+7+9+9) == checksum ); }////////////////////////////////////////////////////(End)TEST-Scope CHECK ((1+5+7+9) == checksum ); CHECK ((1+5+7+9)*7 == dumm().probe() ); CHECK ( 0 == tricky().probe()); CHECK (+1 == backdoor ); } // NOTE: Veryspecial holds a dangling reference into stack memory from now on! }; LAUNCHER (DependencyConfiguration_test, "unit common"); }} // namespace lib::test