LUMIERA.clone/tests/basics/dependency-configuration-test.cpp

404 lines
14 KiB
C++

/*
DependencyConfiguration(Test) - verify configuration for injecting dependencies
Copyright (C) Lumiera.org
2018, 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 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<int N>
struct Dummy
: Dum
{
Dummy() { checksum += N; }
~Dummy() { checksum -= N; }
virtual int
probe() override
{
return N * checksum;
}
};
}
using error::LERR_(LIFECYCLE);
using error::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<Dummy<1>> dep11;
Depend<Dummy<5>> dep5;
Depend<Dummy<1>> 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<Dum>{}() );
CHECK ((1+5) == checksum );
Depend<Dum> dumm;
DependInject<Dum>::useSingleton<Dummy<7>>();
CHECK ((1+5) == checksum );
CHECK ((1+5+7)*7 == dumm().probe() );
CHECK ((1+5+7) == checksum );
VERIFY_ERROR (LIFECYCLE, DependInject<Dum>::useSingleton<Dummy<9>>() );
CHECK ((1+5+7)*7 == Depend<Dum>{}().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<Dummy<3>> dep3;
CHECK ((1+5+7) == checksum );
{
DependInject<Dummy<3>>::ServiceInstance<SubDummy> 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<Dum>::ServiceInstance<SubDummy>{} );
CHECK ((1+5+7) == checksum );
}
/** @test injecting test mocks temporarily */
void
verify_automaticReplacement()
{
Depend<Dum> dumm;
Depend<Dummy<3>> 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<Dum>::Local<Mock> mockDumm;
DependInject<Dummy<3>>::Local<Mock> 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<Dummy<3>>::ServiceInstance<Mock> 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<Dummy<3>>::Local<Mock> 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<Dummy<9>>::useSingleton<Veryspecial>();
VERIFY_ERROR (FATAL, Depend<Dummy<9>>{}() );
int backdoor = 22;
DependInject<Dummy<9>>::useSingleton ([&]{ return new Veryspecial{backdoor}; });
CHECK ((1+5+7) == checksum );
CHECK ( 22 == backdoor );
Depend<Dummy<9>> 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<Dum> dumm;
CHECK ((1+5+7+9)*7 == dumm().probe() );
{////////////////////////////////////////////////////TEST-Scope
////////////// NOTE: the following does not compile
// // since Veryspecial has no default ctor...
// //
// DependInject<Dum>::Local<Dum> impossible;
DependInject<Dum>::Local<Dum> 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