WIP injecting a test-mock instead of a singleton

This commit is contained in:
Fischlurch 2007-09-23 16:50:05 +02:00
parent 7585c5c358
commit eef591f873
12 changed files with 496 additions and 32 deletions

View file

@ -26,7 +26,9 @@
namespace backend_interface
{
/** */
/** storage for the SingletonFactory
* (actually a cinelerra::test::MockInjector) */
Singleton<MediaAccessFacade> MediaAccessFacade::instance;

View file

@ -25,6 +25,7 @@
#ifndef CINELERRA_MULTITHREAD_H
#define CINELERRA_MULTITHREAD_H
#include "nobugcfg.h"
namespace cinelerra

52
src/common/singleton.hpp Normal file
View file

@ -0,0 +1,52 @@
/*
SINGLETON.hpp - configuration header for singleton factory
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
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 singleton.hpp
** Factory for creating Singleton instances.
** This configuration header just pulls in some other implementation headers in
** the right order. The basic class template for creating singletons resides in
** singletonfactory.hpp, besides we need policy classes defining how to create
** the singleton objects, how to manage lifecycle and multithreading. Finally,
** we want to preconfigure singleton factories for some important facilities;
** e.g. sometimes we want to include a hook for injecting Test Mock instances.
**
** You'll find the default Policies in singletonfactory.hpp and the default
** definition of type cinelerra::singleton in singletonpreconfigure.hpp
**
** @see SingletonFactory
** @see singleton::StaticCreate
** @see singleton::AutoDestroy
** @see singletontest.hpp
** @see singletontestmocktest.hpp
*/
#ifndef CINELERRA_SINGLETON_H
#define CINELERRA_SINGLETON_H
#include "common/singletonpolicies.hpp"
#include "common/singletonfactory.hpp"
#include "common/singletonpreconfigure.hpp"
#endif

View file

@ -54,8 +54,8 @@ namespace cinelerra
*/
template
< class SI, // the class to make Singleton
template <class> class Create = singleton::Static, // how to create/destroy the instance
template <class> class Life = singleton::Automatic, // how to manage Singleton Lifecycle
template <class> class Create = singleton::StaticCreate, // how to create/destroy the instance
template <class> class Life = singleton::AutoDestroy, // how to manage Singleton Lifecycle
template <class> class Threading = singleton::IgnoreThreadsafety //TODO use Multithreaded!!!
>
class SingletonFactory
@ -99,6 +99,8 @@ namespace cinelerra
*/
static void destroy()
{
TRACE (singleton, "Singleton: triggering destruction");
REQUIRE (!isDead_);
Create<SI>::destroy (pInstance_);
pInstance_ = 0;
@ -131,7 +133,9 @@ namespace cinelerra
///// is tricky because of invoking the destructors. If we rely on instance vars,
///// the object may already have been released when the runtime system calls the
///// destructors of static objects at shutdown.
///// It seems this would either cost us much of the flexibility or get complicated
///// to a point where we could as well implement our own Depenency Injection Manager.
/** @internal used to link together the Create policy and Life policy.
* @return a functor object for invoking this->destroy() */
/* SingletonFactory::DelFunc getDeleter()

View file

@ -50,7 +50,7 @@ namespace cinelerra
* Policy for creating the Singleton instance statically
*/
template<class S>
struct Static
struct StaticCreate
{
static S* create ()
{
@ -72,29 +72,13 @@ namespace cinelerra
* Policy for creating the Singleton instance heap allocated
*/
template<class S>
struct Heap
struct HeapCreate
{
static S* create () { return new S; }
static void destroy (S* pS) { delete pS; }
};
/**
* Policy for creating dynamic Singleton instance, with
* additional facility to support Mock testing. When injecting
* a Mock instance (typically a subclass of the product), the
* main Singleton instance is temporarily shaddowed.
*/
template<class S>
struct DynamicMockTestable
{
static S* create () { return new S; }
static void destroy (S* pS) { delete pS; }
};
void injectSubclass (SI* mock)
{
}
@ -105,7 +89,7 @@ namespace cinelerra
* Policy relying on the compiler/runtime system for Singleton Lifecycle
*/
template<class S>
struct Automatic
struct AutoDestroy
{
/** implements the Singleton removal by calling
* the provided deleter function(s) at application shutdown,

View file

@ -0,0 +1,112 @@
/*
SINGLETONPRECONFIGURE - declare the configuration of some Singleton types in advance
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
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 singletonpreconfigure.hpp
** Preconfiguration of some Singleton types, done by template specialisation.
** Typically the client code just includes singleton.h and uses the Singleton
** type. But in some cases, we want to configure specific (dependency injection)
** behaviour at a central location. At some point, we may well have a full blown
** Dependency Manager, but for the moment using just some specialized Singleton
** type for some instances seems sufficient.
**
** One Reason why one wants special Singleton behaviour is Testing: Without
** altering the executable, for running some Tests we need to inject a Test Mock
** in place of some Service Object, so we can verify the behaviour of the code
** <i>using</i> this Service. For this, we mix cinelerra::test::MockInjector
** into the actual Singleton type.
**
** @note we declare the specialisations into the target namespace
**
** @see SingletonFactory
** @see singletontestmocktest.hpp
*/
#ifndef CINELERRA_SINGLETONPRECONFIGURE_H
#define CINELERRA_SINGLETONPRECONFIGURE_H
#include "common/test/mockinjector.hpp"
namespace cinelerra
{
/**
* Default Singleton configuration
* @note all Policy template parameters taking default values
*/
template <class SI>
class Singleton
: public SingletonFactory<SI>
{ }
;
/* ********************************************************************** */
/* Forward declarations of all Classes we want to specialize the template */
/* ********************************************************************** */
namespace test
{
class TargetObj;
using cinelerra::Singleton;
} // namespace test
} // namespace cinelerra
namespace backend_interface
{
class MediaAccessFacade;
using cinelerra::Singleton;
} // namespace backend_interface
/* ************************** */
/* Specialisation Definitions */
/* ************************** */
namespace cinelerra
{
using test::MockInjector;
template<>
class Singleton<test::TargetObj>
: public MockInjector<test::TargetObj>
{ };
template<>
class Singleton<backend_interface::MediaAccessFacade>
: public MockInjector<backend_interface::MediaAccessFacade>
{ };
} // namespace cinelerra
#endif

View file

@ -0,0 +1,94 @@
/*
MOCKINJECTOR.hpp - replacement singleton factory for injecting Test-Mock objects
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
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.
*/
#ifndef CINELERRA_TEST_MOCKINJECTOR_H
#define CINELERRA_TEST_MOCKINJECTOR_H
#include "common/singletonfactory.hpp"
#include <boost/scoped_ptr.hpp>
namespace cinelerra
{
namespace test
{
using boost::scoped_ptr;
/**
* Special SingletonFactory allowing to inject some instance of the Singleton
* class, thus shaddowing "the" (default) Singleton instance temporarily.
* This allows installing a Mock Subclass of the Singleton for running tests,
* while the Singleton can be used as usual in production code.
* @note we use the default policies or SingletonFactory
*/
template<class SI>
class MockInjector : public SingletonFactory<SI>
{
scoped_ptr<SI> mock_;
public:
/** Overwriting the normal Singleton creation Interface
* to return some mock if defined, falling back to the
* default Singleton creation behaviour else.
*/
SI& operator() ()
{
if (mock_)
return *mock_;
else
return SingletonFactory<SI>::operator() ();
}
void injectSubclass (SI* mockobj)
{
TRACE_IF (mockobj, singleton, "Singleton: installing Mock object");
TRACE_IF (!mockobj, singleton, "Singleton: removing Mock object");
mock_.reset (mockobj);
}
MockInjector () {};
/** @note MockInjector singleton factory objects can be copied,
* but the copy will start out with clean internal state,
* i.e. exhibiting normal SingletonFactory behaviour
* without mock object.
*/
MockInjector (const MockInjector& other)
: SingletonFactory<SI>(other), mock_(0) { }
MockInjector<SI>& operator= (const MockInjector<SI>& other)
{
return SingletonFactory<SI>::operator= (other);
}
};
} // namespace test
} // namespace cinelerra
#endif

View file

@ -50,6 +50,7 @@
/* declare flags used throughout the code base... */
NOBUG_DECLARE_FLAG(config);
NOBUG_DECLARE_FLAG(test);
NOBUG_DECLARE_FLAG(singleton);
NOBUG_DECLARE_FLAG(assetmem);
NOBUG_DECLARE_FLAG(mobjectmem);
@ -76,7 +77,8 @@
/* flags used throughout the code base... */
NOBUG_CPP_DEFINE_FLAG(config);
NOBUG_CPP_DEFINE_FLAG(test);
NOBUG_CPP_DEFINE_FLAG_LIMIT(assetmem, LOG_WARNING);
NOBUG_CPP_DEFINE_FLAG_LIMIT(singleton, LOG_WARNING);
NOBUG_CPP_DEFINE_FLAG_LIMIT(assetmem, LOG_WARNING);
NOBUG_CPP_DEFINE_FLAG_LIMIT(mobjectmem, LOG_WARNING);
#include "common/error.hpp"

View file

@ -86,7 +86,7 @@ namespace asset
DB () : table() {}
~DB () {}
friend class cinelerra::singleton::Static<DB>;
friend class cinelerra::singleton::StaticCreate<DB>;
public:

View file

@ -108,7 +108,7 @@ namespace asset
AssetManager ();
friend class cinelerra::singleton::Static<AssetManager>;
friend class cinelerra::singleton::StaticCreate<AssetManager>;
private:

View file

@ -58,8 +58,8 @@ namespace cinelerra
protected:
TargetObj () : TestTargetObj(cnt) {}
friend class singleton::Static<TargetObj>;
friend class singleton::Heap<TargetObj>;
friend class singleton::StaticCreate<TargetObj>;
friend class singleton::HeapCreate<TargetObj>;
};
int TargetObj::cnt = 0;
@ -74,8 +74,8 @@ namespace cinelerra
* @test implement a Singleton class using our Singleton Template.
* Expected results: no memory leaks.
* @see cinelerra::Singleton
* @see cinelerra::singleton::Static
* @see cinelerra::singleton::Heap
* @see cinelerra::singleton::StaticCreate
* @see cinelerra::singleton::HeapCreate
*/
class Singleton_test : public Test
{
@ -96,7 +96,7 @@ namespace cinelerra
*/
void testStaticallyAllocatedSingleton (uint num)
{
Singleton<TargetObj> single;
SingletonFactory<TargetObj> single;
instance = single;
useInstance (num, "statically allocated");
}
@ -107,7 +107,7 @@ namespace cinelerra
*/
void testHeapAllocatedSingleton (uint num)
{
Singleton<TargetObj,singleton::Heap> single;
SingletonFactory<TargetObj,singleton::HeapCreate> single;
instance = single;
useInstance (num, "heap allocated");
}

View file

@ -0,0 +1,213 @@
/*
SingletonTestMock(Test) - using Singleton for injecting Test-Mocks
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
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.
* *****************************************************/
#include "common/test/run.hpp"
#include "common/singleton.hpp"
#include "common/util.hpp"
#include "nobugcfg.h"
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
#include <iostream>
using boost::lexical_cast;
using boost::format;
using util::isnil;
using std::string;
using std::cout;
namespace cinelerra
{
namespace test
{
/**
* Client Class normally to be instantiated as Singleton.
* But for tests, this class should be replaced by a Mock....
*/
class TargetObj
{
int callCnt;
char* typid;
format msg;
public:
TargetObj(char* ty="TargetObj")
: callCnt (0), typid(ty), msg ("%s::doIt() call=%d\n")
{
TRACE (singleton, "ctor %s", typid);
}
virtual ~TargetObj()
{
TRACE (singleton, "dtor %s", typid);
}
void doIt ()
{
++callCnt;
cout << msg % typid % callCnt;
}
int getCnt () {return callCnt; }
};
/**
* Mock-1 to replace the Client Class...
*/
struct Mock_1 : TargetObj
{
Mock_1() : TargetObj("Mock_1") {};
};
/**
* Mock-2 to replace the Client Class...
*/
struct Mock_2 : TargetObj
{
Mock_2() : TargetObj("Mock_2") {};
};
/*******************************************************************
* @test inject a Mock object into the Singleton Factory,
* to be returned and used in place of the original object.
* Expected results: Mock(s) called, no memory leaks.
* @see cinelerra::Singleton
* @see cinelerra::singleton::Static
*/
class SingletonTestMock_test : public Test
{
Singleton<TargetObj> instance;
virtual void run(Arg arg)
{
string scenario = isnil(arg)? "default" : arg[1];
if (scenario == "default")
injectBoth();
else
if (scenario == "noMock")
noMock();
else
if (scenario == "onlyMock")
onlyMock();
else
if (scenario == "firstMock")
firstMock();
}
/** @test complete use sequence: first access the Client Object,
* then replace it by two different mocks, and finally
* restore the original Client Object
*/
void injectBoth ()
{
TargetObj* tartar = &instance();
tartar->doIt();
tartar->doIt();
ASSERT (tartar->getCnt() == 2);
instance.injectSubclass (new Mock_1);
tartar = &instance();
tartar->doIt();
tartar->doIt();
tartar->doIt();
tartar->doIt();
tartar->doIt();
ASSERT (tartar->getCnt() == 5);
instance.injectSubclass (new Mock_2);
tartar = &instance();
tartar->doIt();
ASSERT (tartar->getCnt() == 1);
instance.injectSubclass (0); // unshaddowing original instance
tartar = &instance();
ASSERT (tartar->getCnt() == 2);
tartar->doIt();
ASSERT (tartar->getCnt() == 3);
}
/** @test just use Singleton Factory normally without any Mock.
*/
void noMock ()
{
TargetObj& tartar = instance();
tartar.doIt();
}
/** @test inject the Mock prior to using the Singleton Factory,
* thus the original Client Object shouldn't be created.
*/
void onlyMock ()
{
instance.injectSubclass (new Mock_1);
TargetObj& tartar = instance();
tartar.doIt();
}
/** @test inject the Mock prior to using the Singleton Factory,
* but then reset the Mock, so following calls should
* create the original Client Object.
*/
void firstMock ()
{
instance.injectSubclass (new Mock_1);
TargetObj* tartar = &instance();
tartar->doIt();
tartar->doIt();
ASSERT (tartar->getCnt() == 2);
instance.injectSubclass (0);
tartar = &instance();
tartar->doIt();
ASSERT (tartar->getCnt() == 1);
}
};
/** Register this test class... */
LAUNCHER (SingletonTestMock_test, "unit common");
} // namespace test
} // namespace cinelerra