This is something I wanted to clean-up since a very long time indeed, but always postponed it, since it requires a lot of nasty clean-up and disentangling.... Several parts had been stashed away into `src/lib/test`, which are not really related: * the testrunner and testsuite builder * generic helper utilities * specialised test-support facilities, e.g. the EventLog This changeset introduces a new top-level test folder and a namespace `::test` Code from this folder is linked into a shared library `libtestsupport.so`, which resides in the linkage hierarchy between »Vessel« and »Vault«, yet is no longer part of the Lumiera application and only linked into the test-suite.
395 lines
14 KiB
C++
395 lines
14 KiB
C++
/*
|
||
DependencyConfiguration(Test) - verify configuration for injecting dependencies
|
||
|
||
Copyright (C)
|
||
2018, Hermann Vosseler <Ichthyostega@web.de>
|
||
|
||
**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 "test/run.hpp"
|
||
#include "lib/format-cout.hpp"
|
||
#include "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 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<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
|