reworkted Singleton / DependencyFactory unit test pass
This commit is contained in:
parent
24792c1f19
commit
b225120d09
5 changed files with 58 additions and 140 deletions
|
|
@ -40,8 +40,8 @@ This code is heavily inspired by
|
|||
|
||||
#include "lib/meta/duck-detector.hpp" ////TODO move in separate header
|
||||
|
||||
#include <boost/scoped_ptr.hpp> ////TODO move in separate header
|
||||
#include <boost/noncopyable.hpp> ////TODO move in separate header
|
||||
#include <boost/static_assert.hpp> ////TODO move in separate header
|
||||
#include <boost/utility/enable_if.hpp> ////TODO move in separate header
|
||||
|
||||
|
||||
|
|
@ -151,29 +151,24 @@ namespace lib {
|
|||
instance = NULL;
|
||||
}
|
||||
|
||||
/** temporarily shadow the service instance with the given replacement.
|
||||
/** temporarily replace the service instance.
|
||||
* The purpose of this operation is to support unit testing.
|
||||
* @throw error::State in case there is already an installed replacement
|
||||
* @param mock heap allocated instance of the replacement (mock).
|
||||
* @param mock reference to an existing service instance (mock).
|
||||
* @return reference to the currently active service instance.
|
||||
* @warning not threadsafe. Same considerations as for \c shutdown() apply
|
||||
* @note ownership of mock will be transferred; the mock instance
|
||||
* will be destroyed automatically when deconfigured.
|
||||
* @remark the replacement is not actively managed by the DependencyFactory,
|
||||
* it remains in ownership of the calling client (unit test). Typically
|
||||
* this test will keep the returned original service reference and
|
||||
* care for restoring the original state when done.
|
||||
* @see Depend4Test scoped object for automated test mock injection
|
||||
*/
|
||||
static void
|
||||
static SI*
|
||||
injectReplacement (SI* mock)
|
||||
{
|
||||
REQUIRE (mock);
|
||||
factory.takeOwnership (mock); // EX_SANE
|
||||
|
||||
SyncLock guard;
|
||||
factory.shaddow (instance, mock); // EX_FREE
|
||||
}
|
||||
|
||||
static void
|
||||
dropReplacement()
|
||||
{
|
||||
SyncLock guard;
|
||||
factory.restore (instance); // EX_FREE
|
||||
SI* currentInstance = instance;
|
||||
instance = mock;
|
||||
return currentInstance;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -243,19 +238,23 @@ namespace lib {
|
|||
* it needs to expose a typedef \c ServiceInterface
|
||||
*/
|
||||
template<class TYPE>
|
||||
struct Use4Test
|
||||
struct Depend4Test
|
||||
: boost::noncopyable
|
||||
{
|
||||
typedef typename ServiceInterface<TYPE>::Type Interface;
|
||||
|
||||
Use4Test()
|
||||
{
|
||||
Depend<Interface>::injectReplacement (new TYPE);
|
||||
}
|
||||
boost::scoped_ptr<TYPE> mock_;
|
||||
Interface* shadowedOriginal_;
|
||||
|
||||
~Use4Test()
|
||||
Depend4Test()
|
||||
: mock_(new TYPE)
|
||||
, shadowedOriginal_(Depend<Interface>::injectReplacement (mock_.get()))
|
||||
{ }
|
||||
|
||||
~Depend4Test()
|
||||
{
|
||||
Depend<Interface>::dropReplacement();
|
||||
ENSURE (mock_);
|
||||
Depend<Interface>::injectReplacement (shadowedOriginal_);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -82,43 +82,10 @@ namespace lib {
|
|||
|
||||
bool AutoDestructor::shutdownLock = false;
|
||||
|
||||
|
||||
|
||||
class TemporaryShadowedInstanceFactory
|
||||
: boost::noncopyable
|
||||
{
|
||||
typedef DependencyFactory::InstanceConstructor Ctor;
|
||||
|
||||
void* originalInstance_;
|
||||
Ctor originalCtor_;
|
||||
|
||||
public:
|
||||
TemporaryShadowedInstanceFactory(void* instance, Ctor ctor)
|
||||
: originalInstance_(instance)
|
||||
, originalCtor_(ctor)
|
||||
{ }
|
||||
|
||||
void*
|
||||
constructorInvocation()
|
||||
{
|
||||
REQUIRE (originalCtor_);
|
||||
|
||||
if (!originalInstance_)
|
||||
return originalCtor_();
|
||||
|
||||
void* existingInstance = originalInstance_;
|
||||
originalInstance_ = NULL;
|
||||
return existingInstance;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** */
|
||||
|
||||
|
||||
/** */
|
||||
void
|
||||
DependencyFactory::deconfigure (void* existingInstance)
|
||||
|
|
@ -126,6 +93,12 @@ namespace lib {
|
|||
AutoDestructor::kill (existingInstance);
|
||||
}
|
||||
|
||||
/** hook to install a deleter function to clean up a service object.
|
||||
* The standard constructor function uses this hook to schedule the
|
||||
* destructor invocation on application shutdown; custom constructors
|
||||
* are free to use this mechanism (or care for clean-up otherwise)
|
||||
* @see lib::DelStash
|
||||
*/
|
||||
void
|
||||
DependencyFactory::scheduleDestruction (void* object, KillFun customDeleter)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
#include "lib/nobug-init.hpp"
|
||||
#include "lib/error.hpp"
|
||||
//#include "lib/sync-classlock.hpp"
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
|
@ -98,26 +97,6 @@ namespace lib {
|
|||
|
||||
void deconfigure (void* existingInstance);
|
||||
|
||||
|
||||
template<class TAR>
|
||||
void takeOwnership (TAR*);
|
||||
|
||||
|
||||
template<class TAR>
|
||||
void shaddow (TAR* volatile & activeInstance, TAR* replacement);
|
||||
|
||||
template<class TAR>
|
||||
void restore (TAR* volatile & activeInstance);
|
||||
|
||||
|
||||
|
||||
|
||||
/** hook to install a deleter function to clean up a service object.
|
||||
* The standard constructor function uses this hook to schedule the
|
||||
* destructor invocation on application shutdown; custom constructors
|
||||
* are free to use this mechanism (or care for clean-up otherwise)
|
||||
* @see lib::DelStash
|
||||
*/
|
||||
static void scheduleDestruction (void*, KillFun);
|
||||
|
||||
|
||||
|
|
@ -206,56 +185,6 @@ namespace lib {
|
|||
};
|
||||
|
||||
|
||||
namespace {
|
||||
/** helper: destroy heap allocated object.
|
||||
* This deleter function is used to clean-up
|
||||
* a heap allocated mock object, which was installed
|
||||
* as a temporary replacement for some service,
|
||||
* typically during an unit test
|
||||
*/
|
||||
template<class X>
|
||||
inline void
|
||||
releaseOnHeap (void* o)
|
||||
{
|
||||
if (!o) return;
|
||||
X* instance = static_cast<X*> (o);
|
||||
delete instance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<class TAR>
|
||||
void
|
||||
DependencyFactory::takeOwnership (TAR* newInstance)
|
||||
{
|
||||
scheduleDestruction (newInstance, &releaseOnHeap<TAR>);
|
||||
}
|
||||
|
||||
/**
|
||||
* set up a temporary replacement, allowing to restore the original later
|
||||
* @param activeInstance
|
||||
* @param replacement
|
||||
*/
|
||||
template<class TAR>
|
||||
void
|
||||
DependencyFactory::shaddow (TAR* volatile & activeInstance, TAR* replacement)
|
||||
{
|
||||
ctorFunction_ = 0; /////TODO how to implement a functor without explicitly allocated storage??
|
||||
activeInstance = replacement;
|
||||
}
|
||||
|
||||
/**
|
||||
* disable and destroy temporary shadowing instance and restore the dormant original instance
|
||||
* @param activeInstance
|
||||
*/
|
||||
template<class TAR>
|
||||
void
|
||||
DependencyFactory::restore (TAR* volatile & activeInstance)
|
||||
{
|
||||
deconfigure (activeInstance);
|
||||
activeInstance = NULL;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lib
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -231,6 +231,25 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
TEST "Dependency injection" DependencyFactory_test <<END
|
||||
out-lit: ctor TargetObj(0) successful
|
||||
out-lit: dtor ~TargetObj(0) successful
|
||||
out-lit: ctor TargetObj(1) successful
|
||||
out-lit: ctor TargetObj(2) successful
|
||||
out-lit: dtor ~TargetObj(2) successful
|
||||
out-lit: ctor TargetObj(3) successful
|
||||
out-lit: ctor TargetObj(4) successful
|
||||
out-lit: ctor TargetObj(5) successful
|
||||
out-lit: dtor ~TargetObj(5) successful
|
||||
out-lit: ctor TargetObj(6) successful
|
||||
out-lit: dtor ~TargetObj(6) successful
|
||||
out-lit: dtor ~TargetObj(4) successful
|
||||
out-lit: dtor ~TargetObj(3) successful
|
||||
out-lit: dtor ~TargetObj(1) successful
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
TEST "A Digxel (numeric component)" Digxel_test <<END
|
||||
out-lit: empty____## +0.0 ##
|
||||
out-lit: value____##-88.8 ##
|
||||
|
|
|
|||
|
|
@ -65,7 +65,9 @@ namespace test{
|
|||
|
||||
struct SubSub
|
||||
: Sub
|
||||
{ };
|
||||
{
|
||||
typedef Sub ServiceInterface;
|
||||
};
|
||||
|
||||
struct SubSubSub
|
||||
: SubSub
|
||||
|
|
@ -202,17 +204,13 @@ namespace test{
|
|||
Sub& original = genericAccessor();
|
||||
uint oID = original.instanceID_;
|
||||
|
||||
GenericAccessor::injectReplacement (new SubSubSub);
|
||||
SubSubSub mockObject;
|
||||
Sub* shaddowedOriginal = GenericAccessor::injectReplacement (&mockObject);
|
||||
|
||||
Sub& replacement = genericAccessor();
|
||||
uint repID = replacement.instanceID_;
|
||||
|
||||
CHECK (!INSTANCEOF (SubSubSub, &original));
|
||||
CHECK ( INSTANCEOF (SubSubSub, &replacement));
|
||||
CHECK ( isSameObject (replacement, mockObject));
|
||||
CHECK (!isSameObject (original, replacement));
|
||||
|
||||
CHECK (oID != repID);
|
||||
CHECK (oID == original.instanceID_);
|
||||
CHECK ( isSameObject (original, *shaddowedOriginal));
|
||||
|
||||
Depend<SubSub> special;
|
||||
Depend<SubSubSub> custom;
|
||||
|
|
@ -220,7 +218,7 @@ namespace test{
|
|||
CHECK(!isSameObject (replacement, special() ));
|
||||
CHECK(!isSameObject (replacement, custom() ));
|
||||
|
||||
GenericAccessor::dropReplacement();
|
||||
GenericAccessor::injectReplacement (shaddowedOriginal);
|
||||
|
||||
Sub& nextFetch = genericAccessor();
|
||||
CHECK (isSameObject (original, nextFetch));
|
||||
|
|
@ -237,7 +235,7 @@ namespace test{
|
|||
uint oID = original.instanceID_;
|
||||
|
||||
{
|
||||
Use4Test<SubSub> withinThisScope;
|
||||
Depend4Test<SubSub> withinThisScope;
|
||||
|
||||
Sub& replacement = genericAccessor();
|
||||
uint repID = replacement.instanceID_;
|
||||
|
|
|
|||
Loading…
Reference in a new issue