404 lines
14 KiB
C++
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
|