diff --git a/src/backend/mediaaccessfacade.cpp b/src/backend/mediaaccessfacade.cpp index b41277436..720c31864 100644 --- a/src/backend/mediaaccessfacade.cpp +++ b/src/backend/mediaaccessfacade.cpp @@ -26,7 +26,9 @@ namespace backend_interface { - /** */ + /** storage for the SingletonFactory + * (actually a cinelerra::test::MockInjector) */ + Singleton MediaAccessFacade::instance; diff --git a/src/common/multithread.hpp b/src/common/multithread.hpp index 9451ada05..c4a070104 100644 --- a/src/common/multithread.hpp +++ b/src/common/multithread.hpp @@ -25,6 +25,7 @@ #ifndef CINELERRA_MULTITHREAD_H #define CINELERRA_MULTITHREAD_H +#include "nobugcfg.h" namespace cinelerra diff --git a/src/common/singleton.hpp b/src/common/singleton.hpp new file mode 100644 index 000000000..28100784d --- /dev/null +++ b/src/common/singleton.hpp @@ -0,0 +1,52 @@ +/* + SINGLETON.hpp - configuration header for singleton factory + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + 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 diff --git a/src/common/singletonfactory.hpp b/src/common/singletonfactory.hpp index 2647a6f2c..b2143ef7a 100644 --- a/src/common/singletonfactory.hpp +++ b/src/common/singletonfactory.hpp @@ -54,8 +54,8 @@ namespace cinelerra */ template < class SI, // the class to make Singleton - template class Create = singleton::Static, // how to create/destroy the instance - template class Life = singleton::Automatic, // how to manage Singleton Lifecycle + template class Create = singleton::StaticCreate, // how to create/destroy the instance + template class Life = singleton::AutoDestroy, // how to manage Singleton Lifecycle template 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::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() diff --git a/src/common/singletonpolicies.hpp b/src/common/singletonpolicies.hpp index b1d91412c..5f5624990 100644 --- a/src/common/singletonpolicies.hpp +++ b/src/common/singletonpolicies.hpp @@ -50,7 +50,7 @@ namespace cinelerra * Policy for creating the Singleton instance statically */ template - struct Static + struct StaticCreate { static S* create () { @@ -72,29 +72,13 @@ namespace cinelerra * Policy for creating the Singleton instance heap allocated */ template - 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 - 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 - struct Automatic + struct AutoDestroy { /** implements the Singleton removal by calling * the provided deleter function(s) at application shutdown, diff --git a/src/common/singletonpreconfigure.hpp b/src/common/singletonpreconfigure.hpp new file mode 100644 index 000000000..f2171fd82 --- /dev/null +++ b/src/common/singletonpreconfigure.hpp @@ -0,0 +1,112 @@ +/* + SINGLETONPRECONFIGURE - declare the configuration of some Singleton types in advance + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + 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 + ** using 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 Singleton + : public SingletonFactory + { } + ; + + + /* ********************************************************************** */ + /* 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 + : public MockInjector + { }; + + + template<> + class Singleton + : public MockInjector + { }; + +} // namespace cinelerra + + + + +#endif diff --git a/src/common/test/mockinjector.hpp b/src/common/test/mockinjector.hpp new file mode 100644 index 000000000..2bd20f25a --- /dev/null +++ b/src/common/test/mockinjector.hpp @@ -0,0 +1,94 @@ +/* + MOCKINJECTOR.hpp - replacement singleton factory for injecting Test-Mock objects + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + 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 + + +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 MockInjector : public SingletonFactory + { + scoped_ptr 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::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(other), mock_(0) { } + + MockInjector& operator= (const MockInjector& other) + { + return SingletonFactory::operator= (other); + } + }; + + + + } // namespace test + +} // namespace cinelerra +#endif diff --git a/src/nobugcfg.h b/src/nobugcfg.h index bca0108b4..c16a569c0 100644 --- a/src/nobugcfg.h +++ b/src/nobugcfg.h @@ -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" diff --git a/src/proc/asset/db.hpp b/src/proc/asset/db.hpp index b9faa0a42..d2fd0d02f 100644 --- a/src/proc/asset/db.hpp +++ b/src/proc/asset/db.hpp @@ -86,7 +86,7 @@ namespace asset DB () : table() {} ~DB () {} - friend class cinelerra::singleton::Static; + friend class cinelerra::singleton::StaticCreate; public: diff --git a/src/proc/assetmanager.hpp b/src/proc/assetmanager.hpp index c677f6ef4..8afd1a53a 100644 --- a/src/proc/assetmanager.hpp +++ b/src/proc/assetmanager.hpp @@ -108,7 +108,7 @@ namespace asset AssetManager (); - friend class cinelerra::singleton::Static; + friend class cinelerra::singleton::StaticCreate; private: diff --git a/tests/components/common/singletontest.cpp b/tests/components/common/singletontest.cpp index 1bc280585..e29f6e223 100644 --- a/tests/components/common/singletontest.cpp +++ b/tests/components/common/singletontest.cpp @@ -58,8 +58,8 @@ namespace cinelerra protected: TargetObj () : TestTargetObj(cnt) {} - friend class singleton::Static; - friend class singleton::Heap; + friend class singleton::StaticCreate; + friend class singleton::HeapCreate; }; 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 single; + SingletonFactory single; instance = single; useInstance (num, "statically allocated"); } @@ -107,7 +107,7 @@ namespace cinelerra */ void testHeapAllocatedSingleton (uint num) { - Singleton single; + SingletonFactory single; instance = single; useInstance (num, "heap allocated"); } diff --git a/tests/components/common/singletontestmocktest.cpp b/tests/components/common/singletontestmocktest.cpp new file mode 100644 index 000000000..a5f900a2a --- /dev/null +++ b/tests/components/common/singletontestmocktest.cpp @@ -0,0 +1,213 @@ +/* + SingletonTestMock(Test) - using Singleton for injecting Test-Mocks + + Copyright (C) CinelerraCV + 2007, Christian Thaeter + + 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 +#include +#include + +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 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