From aaf2ee4928dcc45ad41befe92f66dfd57fdcec5a Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 18 Aug 2007 06:13:39 +0200 Subject: [PATCH 1/9] now the testsuite runs with SCons as well.. --- .gitignore | 1 + tests/SConscript | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 141d31bb7..ca2f40822 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *~ *.tar.* .[^.]* +*.os Buildhelper.pyc optcache Makefile.in diff --git a/tests/SConscript b/tests/SConscript index 95ecdc0a7..dbb7e29a7 100644 --- a/tests/SConscript +++ b/tests/SConscript @@ -27,7 +27,7 @@ def treatPluginTestcase(env): """ env = env.Clone() env.Append(CPPPATH='plugin') - testplugin = env.SharedLibrary('hello_1', 'plugin/example_plugin.c', SHLIBPREFIX='') + testplugin = env.SharedLibrary('.libs/example_plugin', 'plugin/example_plugin.c', SHLIBPREFIX='') testExe = env.Program('test-plugin', ['plugin/plugin_main.c'] + corelib) env.Depends(testExe, testplugin) return testExe @@ -46,7 +46,6 @@ moduledirs = globRootdirs('*') isnt_plugin = lambda dir : dir!='plugin' moduledirs = filter(isnt_plugin, moduledirs) pluginExe = treatPluginTestcase(env) -print 'moduledirs: %s' %moduledirs artifacts['testsuite'] = ts = [ SingleTestExecutableSubdir(env, dir) for dir in moduledirs] + pluginExe @@ -62,8 +61,7 @@ artifacts['testsuite'] = ts = [ SingleTestExecutableSubdir(env, dir) for dir in # - it depends on all artifacts defined as "ts" above # runTs = env.Command(',testlog', ts, "./test.sh", chdir=1) - -env.Clean (runTs, [ ',*']) # declare tempfiles of test.sh as cleanable + # @@ -74,3 +72,5 @@ env.Clean (runTs, [ ',*']) # declare tempfiles of test.sh as cleanable env.Alias('testcode', ts ) env.Alias('check', runTs ) +# declare tempfiles of test.sh as cleanable +env.Clean ('check', [',testlog.pre',',expect_stdout',',stdout',',stderr',',testtmp','.libs']) From cb13b09360bcd0800d607b740ab6100ee81be5d2 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 19 Aug 2007 21:57:19 +0200 Subject: [PATCH 2/9] WIP: started augmenting my test-runner class to be configurable via cmdline Added dependency to boost::program_options. Still trying to get into pace with the testing thing ;-) --- SConstruct | 13 +- src/common/error.cpp | 15 +++ src/common/error.hpp | 25 +++- .../helper => src/common/test}/run.hpp | 12 +- .../helper => src/common/test}/suite.cpp | 15 ++- .../helper => src/common/test}/suite.hpp | 13 +- src/common/test/testoption.cpp | 122 ++++++++++++++++++ src/common/test/testoption.hpp | 70 ++++++++++ tests/.gitignore | 1 + tests/50components.tests | 5 + tests/components/common/factorytest.cpp | 2 +- .../components/common/test/testoptiontest.cpp | 73 +++++++++++ .../{common => }/helloworldtest.cpp | 3 +- tests/components/mainsuite.cpp | 3 +- wiki/index.html | 7 +- 15 files changed, 353 insertions(+), 26 deletions(-) rename {tests/components/helper => src/common/test}/run.hpp (85%) rename {tests/components/helper => src/common/test}/suite.cpp (92%) rename {tests/components/helper => src/common/test}/suite.hpp (82%) create mode 100644 src/common/test/testoption.cpp create mode 100644 src/common/test/testoption.hpp create mode 100644 tests/components/common/test/testoptiontest.cpp rename tests/components/{common => }/helloworldtest.cpp (92%) diff --git a/SConstruct b/SConstruct index 9b11e379e..8c7a7781c 100644 --- a/SConstruct +++ b/SConstruct @@ -174,6 +174,7 @@ def configurePlatform(env): print 'Did not find the pthread lib or pthread.h, exiting.' else: conf.env.Append(CPPFLAGS = ' -DHAVE_PTHREAD_H') + conf.env.Append(CCFLAGS = ' -pthread') if conf.CheckCHeader('execinfo.h'): conf.env.Append(CPPFLAGS = ' -DHAS_EXECINFO_H') @@ -188,10 +189,14 @@ def configurePlatform(env): if not conf.CheckCXXHeader('boost/config.hpp'): print 'We need the C++ boost-lib.' Exit(1) - - if not conf.CheckCXXHeader('boost/shared_ptr.hpp'): - print 'We need boost::shared_ptr (shared_ptr.hpp).' - Exit(1) + else: + if not conf.CheckCXXHeader('boost/shared_ptr.hpp'): + print 'We need boost::shared_ptr (shared_ptr.hpp).' + Exit(1) + if not conf.CheckLibWithHeader('boost_program_options-mt','boost/program_options.hpp','C++'): + print 'We need boost::program_options (also the corresponding binary lib).' + Exit(1) + # create new env containing the finished configuration return conf.Finish() diff --git a/src/common/error.cpp b/src/common/error.cpp index 0e869fe93..cd46fa15f 100644 --- a/src/common/error.cpp +++ b/src/common/error.cpp @@ -22,9 +22,21 @@ #include "common/error.hpp" +#include "nobugcfg.h" + +///////////////////////////////////TODO +#include + +extern void booo() + { + std::cerr << "Booooo!!!" << std::endl; + std::cerr.flush(); + } +///////////////////////////////////TODO namespace cinelerra { + char* killme ="cinelerra Errrror. TODO real description needed"; /** Description of the problem, including the internal char constant * in accordance to cinelerras error identification scheme. @@ -34,6 +46,8 @@ namespace cinelerra const char* Error::what () const throw() { + TODO("really implement cinelerra::Error description"); + return killme; } @@ -46,6 +60,7 @@ namespace cinelerra std::exception Error::rootCause() const throw() { + UNIMPLEMENTED("storing and managing root causes"); } diff --git a/src/common/error.hpp b/src/common/error.hpp index 973fa1d8a..bc72d6861 100644 --- a/src/common/error.hpp +++ b/src/common/error.hpp @@ -72,6 +72,11 @@ namespace cinelerra { }; + + class Fatal : public Logic + { + + }; class Config : public Error { @@ -93,10 +98,24 @@ namespace cinelerra }; - - - + + } // namespace error } // namespace cinelerra + +#include + +extern void booo(); + +/****************************************************** + * if NoBug is used, redefine some macros + * to rather throw Cinelerra Errors instead of aborting + */ +#ifdef NOBUG_ABORT +#undef NOBUG_ABORT +#define NOBUG_ABORT throw cinelerra::error::Fatal(); ////////////////TODO +#endif + + #endif diff --git a/tests/components/helper/run.hpp b/src/common/test/run.hpp similarity index 85% rename from tests/components/helper/run.hpp rename to src/common/test/run.hpp index 518ff37d2..79fae255f 100644 --- a/tests/components/helper/run.hpp +++ b/src/common/test/run.hpp @@ -28,7 +28,7 @@ #include #include -#include "helper/suite.hpp" +#include "common/test/suite.hpp" namespace test @@ -37,7 +37,7 @@ namespace test using std::string; using std::auto_ptr; - typedef std::vector * Arg; + typedef std::vector & Arg; @@ -89,4 +89,12 @@ using ::test::Arg; using ::test::Test; using ::test::Launch; +// and provide shortcut for registration +#define LAUNCHER(_TEST_CLASS_, _GROUPS_) \ + /** Register _TEST_CLASS_ to be invoked in some test suites (groups) _GROUPS_ */ \ + Launch<_TEST_CLASS_> run_##_TEST_CLASS_##_(STRINGIFY(_TEST_CLASS_), _GROUPS_); + +#define STRINGIFY(TOKEN) __STRINFY(TOKEN) +#define __STRINFY(TOKEN) #TOKEN + #endif diff --git a/tests/components/helper/suite.cpp b/src/common/test/suite.cpp similarity index 92% rename from tests/components/helper/suite.cpp rename to src/common/test/suite.cpp index 6d8250cac..ee3596c29 100644 --- a/tests/components/helper/suite.cpp +++ b/src/common/test/suite.cpp @@ -28,8 +28,8 @@ #include #include -#include "helper/suite.hpp" -#include "helper/run.hpp" +#include "common/test/suite.hpp" +#include "common/test/run.hpp" #include "common/error.hpp" #include "common/util.hpp" @@ -151,15 +151,18 @@ namespace test // go ahead and invoke just this test. if (argc > 2) { // pass additional cmdline as vector + FIXME("use Optparser"); vector arglist(argc-2); for ( int i=2; irun(&arglist); + (*test)()->run(arglist); } else - (*test)()->run(0); // without additional argumens + FIXME("use Optparser"); + vector nix; + (*test)()->run(nix); // without additional argumens return; } @@ -172,7 +175,9 @@ namespace test { std::cout << " ----------"<< i->first<< "----------\n"; Launcher& test = *(i->second); - test()->run(0); // without cmdline arguments + FIXME("use Optparser"); + vector nix; + test()->run(nix); // without cmdline arguments } } diff --git a/tests/components/helper/suite.hpp b/src/common/test/suite.hpp similarity index 82% rename from tests/components/helper/suite.hpp rename to src/common/test/suite.hpp index 8060aaab3..addd13e07 100644 --- a/tests/components/helper/suite.hpp +++ b/src/common/test/suite.hpp @@ -37,10 +37,12 @@ namespace test class Launcher; - /** - * Helper class for running a collection of tests. - * + * Enables running a collection of tests. + * In internal registration service #enroll() is provided + * for the individual Test - inscances to be recognized as + * testcases. The groupID passed to the constructor selects + * all testcases declared as belonging to this Group. */ class Suite { @@ -53,8 +55,7 @@ namespace test static const string ALLGROUP; }; - - - + + } // namespace test #endif diff --git a/src/common/test/testoption.cpp b/src/common/test/testoption.cpp new file mode 100644 index 000000000..2e1e4686e --- /dev/null +++ b/src/common/test/testoption.cpp @@ -0,0 +1,122 @@ +/* + Suite - helper class for running collections of tests + + 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 +//#include +//#include +//#include +//#include + +#include "common/test/testoption.hpp" +#include "common/test/suite.hpp" +//#include "common/util.hpp" + +#include "nobugcfg.h" +#include "common/error.hpp" + + +namespace test + { +// using std::map; +// using std::auto_ptr; +// using std::tr1::shared_ptr; + +// using util::isnil; + + + + /** set up an options parser to use the current commandline. + * reconizes the following options + * \code + * --help + * --group + * \endcode + */ + TestOption::TestOption (int argc, const char* argv[]) + : vm(), cmdline() + { + cmdline.reserve (10); + parseOptions (argc,argv); + } + + + /** variant of the ctor taking a "fake" cmdline */ + TestOption::TestOption (string line) + : vm(), cmdline() + { + cmdline.reserve (10); + const char* fakeArg[3] = {"test", line.c_str() }; +// fakeArg[1] = line.c_str(); + parseOptions (1,fakeArg); + } + + + /** do the comandline parsing for the ctors */ + void TestOption::parseOptions (int argc, const char* argv[]) + { + TODO("define options"); + UNIMPLEMENTED("actual commandline parsing!!"); + } + + + /** @return the Tests-Group as given on cmdline, or Suite::ALLGROUP as default + */ + const string & + TestOption::getTestgroup () + { + FIXME("actual commandline parsing!!"); + return Suite::ALLGROUP; + } + + string booooh = "boooh!"; + /** @return ID of a single test to run, empty if not specified + */ + const string & + TestOption::getTestID () + { + UNIMPLEMENTED("actual commandline parsing"); + return booooh; + } + + + /** gather all remaining unknown cmd line tokens into a vector. + * @Note: the actual vector remains a member of this object, but isn't const + */ + vector& + TestOption::remainingCmdline () + { + UNIMPLEMENTED("get unknown remaining options"); + return cmdline; + } + + + /** */ + TestOption::operator string const () + { + UNIMPLEMENTED("convert the remaining Cmndline to string"); + return 0; + } + + + +} // namespace test diff --git a/src/common/test/testoption.hpp b/src/common/test/testoption.hpp new file mode 100644 index 000000000..c0aa073cb --- /dev/null +++ b/src/common/test/testoption.hpp @@ -0,0 +1,70 @@ +/* + TESTOPTION.hpp - handle cmdline for invoking Testsuite + + 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 TESTHELPER_TESTOPTION_H +#define TESTHELPER_TESTOPTION_H + +#include +#include +#include + + + +namespace test + { + using std::string; + using std::vector; + + typedef boost::program_options::variables_map VarMap; + + + /** + * Support for selecting and configuring testcases + * via commandline arguments. A preconfigured wrapper + * around boost::program_options, with the ability + * to tolerate unknown options and get an vector + * of everything remaining in the commandline after + * parsing the known options. + */ + class TestOption + { + public: + TestOption (int argc, const char* argv[]); + explicit TestOption (string cmdline); + const string& getTestgroup (); + const string& getTestID (); + vector& remainingCmdline (); + + operator string const (); + + private: + VarMap vm; + vector cmdline; + + void parseOptions (int argc, const char* argv[]); + }; + + + +} // namespace test +#endif diff --git a/tests/.gitignore b/tests/.gitignore index cbfcd6fe3..0ae34206f 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,4 +1,5 @@ ,* +test-* mainsuite errortest plugin-example diff --git a/tests/50components.tests b/tests/50components.tests index e022d0d35..99e575ad0 100644 --- a/tests/50components.tests +++ b/tests/50components.tests @@ -10,3 +10,8 @@ out: This is how the world ends... return: 0 END +TEST "Parseoption" TestOption_test < + + 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 +#include "common/test/run.hpp" +#include "common/test/testoption.hpp" +#include "common/util.hpp" + +using util::isnil; + +namespace test + { + + class TestOption_test : public Test + { + virtual void run(Arg arg) + { + noOptions(); + help(); + groupID(); + singleTest(); + groupFilter1(); + groupFilter2(); + additionalCmd(); + additionalCmd2(); + } + + void doIt (const string cmdline) + { + std::cout << "Testing invocation with cmdline: " << cmdline << "..." << std::endl; + + TestOption optparser (cmdline); + const string testID = optparser.getTestID(); + std::cout << "--> Testgroup=" << optparser.getTestgroup() << std::endl; + std::cout << "--> Test-ID =" << (isnil(testID)? testID : "--missing--") << std::endl; + std::cout << "--> remaining=" << string(optparser) << std::endl; + } + + void noOptions() { doIt (""); } + void help() { doIt ("--help"); } + void groupID() { doIt ("--group TestGroupID"); } + void singleTest() { doIt (" SingleTestID"); } + void groupFilter1() { doIt (" SingleTestID --group TestGroupID"); } + void groupFilter2() { doIt (" --group TestGroupID SingleTestID "); } + void additionalCmd() { doIt (" --group TestGroupID SingleTestID spam eggs"); } + void additionalCmd2() { doIt ("\t\tSingleTestID spam --group TestGroupID \t eggs"); } + + }; + + LAUNCHER (TestOption_test, "function common"); + +} // namespace test + diff --git a/tests/components/common/helloworldtest.cpp b/tests/components/helloworldtest.cpp similarity index 92% rename from tests/components/common/helloworldtest.cpp rename to tests/components/helloworldtest.cpp index d22ad619d..09c049f55 100644 --- a/tests/components/common/helloworldtest.cpp +++ b/tests/components/helloworldtest.cpp @@ -22,7 +22,7 @@ #include -#include "helper/run.hpp" +#include "common/test/run.hpp" namespace cinelerra @@ -49,6 +49,7 @@ namespace cinelerra /** Register this test class to be invoked in some test groups (suites) */ Launch run_HelloWorld_test("HelloWorld_test","unit common"); + // NOTE: you may use the Macro "LAUNCHER" in run.hpp to simplify this Registration } // namespace test diff --git a/tests/components/mainsuite.cpp b/tests/components/mainsuite.cpp index 9ed0831fe..3264ad7de 100644 --- a/tests/components/mainsuite.cpp +++ b/tests/components/mainsuite.cpp @@ -21,8 +21,7 @@ */ -#include "helper/suite.hpp" - +#include "common/test/suite.hpp" /** run all tests or any single test specified in the first * cmd line argument. diff --git a/wiki/index.html b/wiki/index.html index e77f90799..cc4fc1b8d 100755 --- a/wiki/index.html +++ b/wiki/index.html @@ -747,14 +747,17 @@ config.macros.timeline.handler = function(place,macroName,params,wikifier,paramS } //}}} -
+
for __Building__
 * gcc (4.1), glibc6 (2.3), libstdc++6 (4.1)
 * [[build system|BuildSystem]] dependencies: SCons (0.96.90), Python (2.3)
 * NoBug for Logging, Tracing, Asserting (can be obtained from [[Pipapo.org|http://www.pipapo.org/pipawiki/NoBug]])
 * ~NoBug needs [[valgrind|Valgrind]] (3.2), execinfo.h and libpthread (&rarr; glibc)
 * std::tr1 &mdash; esp. for the former BOOST::shared_ptr (which is now proposed standard)
-* BOOST
+* BOOST ~~(below are the DEBIAN package names)~~
+** libboost-dev (=1.34.1-2)
+** libboost-program-options-dev (=1.34.1-2)
+** libboost-program-options1.34.1 (=1.34.1-2) ''NOTE: binary dependency''
 //usually, newer versions are OK//
 
 for __Running__
From 5575a7679d786a95cc7d2d882016aa06b8e6d78b Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 23 Aug 2007 17:52:33 +0200 Subject: [PATCH 3/9] wrapper for cmdline parsing, finished testsuite-runner, solved shutdown-memoryleak --- SConstruct | 9 +- src/common/appconfig.cpp | 19 ++- src/common/appconfig.hpp | 52 ++++---- src/common/cmdline.cpp | 85 +++++++++++++ src/common/cmdline.hpp | 70 +++++++++++ src/common/test/run.hpp | 3 +- src/common/test/suite.cpp | 95 ++++++++------ src/common/test/suite.hpp | 8 +- src/common/test/testoption.cpp | 119 +++++++++--------- src/common/test/testoption.hpp | 39 +++--- src/common/util.hpp | 46 ++++++- src/nobugcfg.h | 3 +- tests/50components.tests | 30 +++++ .../common/test/cmdlinewrappertest.cpp | 96 ++++++++++++++ .../components/common/test/testoptiontest.cpp | 15 ++- tests/components/mainsuite.cpp | 11 +- wiki/index.html | 4 +- 17 files changed, 534 insertions(+), 170 deletions(-) create mode 100644 src/common/cmdline.cpp create mode 100644 src/common/cmdline.hpp create mode 100644 tests/components/common/test/cmdlinewrappertest.cpp diff --git a/SConstruct b/SConstruct index 8c7a7781c..bfb239f2e 100644 --- a/SConstruct +++ b/SConstruct @@ -61,7 +61,7 @@ def setupBasicEnvironment(): , SRCDIR=SRCDIR , BINDIR=BINDIR , CPPPATH=["#"+SRCDIR] # used to find includes, "#" means always absolute to build-root - , CPPDEFINES=['-DCINELERRA_VERSION=\\"%s\\"' % VERSION ] # note: make it a list to append further defines + , CPPDEFINES=['-DCINELERRA_VERSION='+VERSION ] # note: it's a list to append further defines , CCFLAGS='-Wall' ) @@ -184,7 +184,7 @@ def configurePlatform(env): if not conf.CheckCXXHeader('tr1/memory'): print 'We rely on the std::tr1 proposed standard extension for shared_ptr.' - Exit(1) + Exit(1) if not conf.CheckCXXHeader('boost/config.hpp'): print 'We need the C++ boost-lib.' @@ -194,7 +194,10 @@ def configurePlatform(env): print 'We need boost::shared_ptr (shared_ptr.hpp).' Exit(1) if not conf.CheckLibWithHeader('boost_program_options-mt','boost/program_options.hpp','C++'): - print 'We need boost::program_options (also the corresponding binary lib).' + print 'We need boost::program_options (including binary lib for linking).' + Exit(1) + if not conf.CheckLibWithHeader('boost_regex-mt','boost/regex.hpp','C++'): + print 'We need the boost regular expression lib (incl. binary lib for linking).' Exit(1) diff --git a/src/common/appconfig.cpp b/src/common/appconfig.cpp index 769cde604..1d1cec4e1 100644 --- a/src/common/appconfig.cpp +++ b/src/common/appconfig.cpp @@ -30,6 +30,7 @@ #undef NOBUG_INIT_DEFS_ + using util::isnil; namespace cinelerra @@ -41,10 +42,10 @@ namespace cinelerra * Appconfig::instance() probably already has been called * by another compilation unit. This is ugliy, but preferable * to beeing dependant on inclusion order of headers. */ - Appconfig* Appconfig::theApp_ ; +// scoped_ptr Appconfig::theApp_; #ifndef CINELERRA_VERSION -#define CINELERRA_VERSION "3++devel" +#define CINELERRA_VERSION 3++devel #endif @@ -61,32 +62,30 @@ namespace cinelerra ////////// INFO(config, "Basic application configuration triggered."); - (*configParam_)["version"] = CINELERRA_VERSION; + (*configParam_)["version"] = STRINGIFY (CINELERRA_VERSION); } - - - + + + /** access the configuation value for a given key. * @return empty string for unknown keys, else the corresponding configuration value */ const string & Appconfig::get (const string & key) throw() { - try { const string& val = (*instance().configParam_)[key]; WARN_IF( isnil(val), config, "undefined config parameter \"%s\" requested.", key.c_str()); return val; - } catch (...) { ERROR(config, "error while accessing configuration parameter \"%s\".", key.c_str()); - } - } + throw cinelerra::error::Fatal (); + } } diff --git a/src/common/appconfig.hpp b/src/common/appconfig.hpp index 9cf20f9f9..488ac2ba8 100644 --- a/src/common/appconfig.hpp +++ b/src/common/appconfig.hpp @@ -38,48 +38,50 @@ #include #include -#include +#include #include "nobugcfg.h" -using std::string; -using std::auto_ptr; - - - namespace cinelerra { + using std::string; + using boost::scoped_ptr; /** * Singleton to hold inevitable global flags and constants * and for performing early (static) global initialization tasks. + * Appconfig services are available already from static + * initialsation code. + * @warning don't use Appconfig in destuctors. */ class Appconfig { private: - - /** holds the single instance and triggers initialization */ - static Appconfig* theApp_; - - /** perform initialization on first access. - * A call is placed in static initialization code - * included via cinelerra.h (see below), - * thus it will happen rather early. - */ - Appconfig () ; - + * @see #instance() for Lifecycle */ + Appconfig (); + + Appconfig (const Appconfig&); ///< copy prohibited, not implemented + ~Appconfig () throw() {}; ///< deletion prohibited + friend void boost::checked_delete(Appconfig*); + public: + /** get the (single) Appconfig instance. + * Implemented as Meyers singleton. + * @warning don't use it in destruction code! + */ static Appconfig& instance() { - if (!theApp_) theApp_ = new Appconfig (); + static scoped_ptr theApp_ (0); + if (!theApp_) theApp_.reset (new Appconfig ()); return *theApp_; } - + + /** access the configuation value for a given key. * @return empty string for unknown keys, config value else * @todo do we need such a facility? @@ -89,10 +91,11 @@ namespace cinelerra private: typedef std::map Configmap; - typedef auto_ptr PConfig; + typedef std::auto_ptr PConfig; /** @TODO the following is just placeholder code! - * Appconfig could do such things if necessary. + * Appconfig could do such things if necessary, + * or provide similar "allways available" services. */ PConfig configParam_; @@ -100,12 +103,5 @@ namespace cinelerra - - namespace - { - /** "magic code" to cause early static initialization */ - Appconfig& init (Appconfig::instance ()); - } - } // namespace cinelerra #endif diff --git a/src/common/cmdline.cpp b/src/common/cmdline.cpp new file mode 100644 index 000000000..4ab1b5131 --- /dev/null +++ b/src/common/cmdline.cpp @@ -0,0 +1,85 @@ +/* + Cmdline - abstraction of the usual commandline, a sequence of strings + + 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/cmdline.hpp" +#include "common/util.hpp" +#include "nobugcfg.h" + +#include +#include + +using boost::algorithm::split; +using boost::algorithm::join; +using boost::algorithm::is_any_of; +using boost::algorithm::token_compress_on; + + + +#include + +namespace util + { + + /** create as a tokenized copy of the current commandline. + * Note that argv[0] is allways ignored. */ + Cmdline::Cmdline (int argc, char* argv[]) + : vector (noneg(argc-1)) + { + for (int i=1; ipush_back(ss); + it = match[0].second; + } + + } + + + /** conversion to string by joining the tokens */ + Cmdline::operator string () const + { + return join(*this," "); + } + + + +} // namespace cinelerra diff --git a/src/common/cmdline.hpp b/src/common/cmdline.hpp new file mode 100644 index 000000000..f05605b01 --- /dev/null +++ b/src/common/cmdline.hpp @@ -0,0 +1,70 @@ +/* + CMDLINE.hpp - abstraction of the usual commandline, a sequence of strings + + 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 UTIL_CMDLINE_H +#define UTIL_CMDLINE_H + +#include +#include +#include + + + +namespace util + { + using std::string; + using std::vector; + using std::ostream; + + typedef vector VectS; + + + /** + * Abstraction of the usual "int argc, int** argv"-Commandline, + * to be able to treat it as a vector of strings. Inherits from + * vector, but provides convienient conversions to + * string (joining delimited by space)... + */ + class Cmdline : public VectS + { + public: + Cmdline (int argc, char* argv[]); + explicit Cmdline (const string cmdline); + + operator string () const; + VectS& operator= (const VectS& source) { return VectS::operator= (source); } + + // inherited ctors + template + Cmdline (In first, In last) : VectS (first,last) {} + Cmdline () : VectS () {} + + }; + + /** for outputting Cmdline objects */ + inline ostream& operator<< (ostream& os, const Cmdline& cmdL) { return os << (string (cmdL)); } + + + +} // namespace util +#endif diff --git a/src/common/test/run.hpp b/src/common/test/run.hpp index 79fae255f..668f29645 100644 --- a/src/common/test/run.hpp +++ b/src/common/test/run.hpp @@ -29,6 +29,7 @@ #include #include "common/test/suite.hpp" +#include "common/util.hpp" namespace test @@ -94,7 +95,5 @@ using ::test::Launch; /** Register _TEST_CLASS_ to be invoked in some test suites (groups) _GROUPS_ */ \ Launch<_TEST_CLASS_> run_##_TEST_CLASS_##_(STRINGIFY(_TEST_CLASS_), _GROUPS_); -#define STRINGIFY(TOKEN) __STRINFY(TOKEN) -#define __STRINFY(TOKEN) #TOKEN #endif diff --git a/src/common/test/suite.cpp b/src/common/test/suite.cpp index ee3596c29..df592dec0 100644 --- a/src/common/test/suite.cpp +++ b/src/common/test/suite.cpp @@ -27,7 +27,9 @@ #include #include #include +#include +#include "common/cmdline.hpp" #include "common/test/suite.hpp" #include "common/test/run.hpp" #include "common/error.hpp" @@ -42,8 +44,10 @@ namespace test using std::vector; using std::auto_ptr; using std::tr1::shared_ptr; + using boost::algorithm::trim; using util::isnil; + using util::contains; typedef map TestMap; typedef shared_ptr PTestMap; @@ -129,57 +133,78 @@ namespace test //throw "empty testsuite"; /////////// TODO Errorhandling! } +#define VALID(test,testID) \ + ASSERT ((test), "NULL testcase laucher for test '%s' found in testsuite '%s'", groupID_.c_str(),testID.c_str()); + /** run all testcases contained in this Suite. - * The first argument in the commandline, if present, will select - * one single testcase with a matching ID. + * The first argument in the commandline, if present, + * will select one single testcase with a matching ID. + * In case of invoking a single testcase, the given cmdline + * will be forwarded to the testcase, after removind the + * testcaseID from cmdline[0]. Otherwise, every testcase + * in this suite is invoked with a empty cmdline vector. + * @param cmdline ref to the vector of commandline tokens */ void - Suite::run (int argc, char* argv[]) + Suite::run (Arg cmdline) { - PTestMap tests = testcases.getGroup(groupID_); if (!tests) - throw cinelerra::error::Invalid (); + throw cinelerra::error::Invalid (); ///////// TODO: pass error description - if (argc >= 2) + if (0 < cmdline.size()) { - if (Launcher* test = (*tests)[argv[1]]) + string& testID (cmdline[0]); + trim(testID); + if ( contains (*tests, testID)) { - // first cmdline argument denotes a valid - // testcase registered in this group: - // go ahead and invoke just this test. - if (argc > 2) - { // pass additional cmdline as vector - FIXME("use Optparser"); - vector arglist(argc-2); - for ( int i=2; irun(arglist); - } - else - FIXME("use Optparser"); - vector nix; - (*test)()->run(nix); // without additional argumens - + // first cmdline argument denotes a valid testcase registered in + // this group: invoke just this test with the remaining cmdline + Launcher* test = (*tests)[testID]; + cmdline.erase (cmdline.begin()); + VALID (test,testID); + (*test)()->run(cmdline); return; - } - } + } } // no test-ID was specified. // instantiiate all tests cases and execute them. for ( TestMap::iterator i=tests->begin(); i!=tests->end(); ++i ) - if (i->second) - { - std::cout << " ----------"<< i->first<< "----------\n"; - Launcher& test = *(i->second); - FIXME("use Optparser"); - vector nix; - test()->run(nix); // without cmdline arguments - } + { + std::cout << "\n ----------"<< i->first<< "----------\n"; + Launcher* test = (i->second); + VALID (test, i->first); + (*test)()->run(cmdline); // actually no cmdline arguments + } + } + + + /** print to stdout an ennumeration of all testcases in this suite, + * in a format suitable for use with Cehteh's ./test.sh + */ + void + Suite::describe () + { + util::Cmdline noCmdline(""); + PTestMap tests = testcases.getGroup(groupID_); + ASSERT (tests); + std::cout << "TESTING \"Component Test Suite: " << groupID_ << "\" ./test-components\n\n"; + + for ( TestMap::iterator i=tests->begin(); i!=tests->end(); ++i ) + { + string key (i->first); + std::cout << "\n\n"; + std::cout << "TEST \""<second); + VALID (test, i->first); + (*test)()->run(noCmdline); // run it to insert test generated output + std::cout << "END\n"; + } + + + } diff --git a/src/common/test/suite.hpp b/src/common/test/suite.hpp index addd13e07..acf9d270e 100644 --- a/src/common/test/suite.hpp +++ b/src/common/test/suite.hpp @@ -24,6 +24,7 @@ #ifndef TESTHELPER_SUITE_H #define TESTHELPER_SUITE_H +#include #include @@ -35,11 +36,13 @@ namespace test // Forward decls needed for run.hpp class Test; class Launcher; + + typedef std::vector & Arg; /** * Enables running a collection of tests. - * In internal registration service #enroll() is provided + * An internal registration service #enroll() is provided * for the individual Test - inscances to be recognized as * testcases. The groupID passed to the constructor selects * all testcases declared as belonging to this Group. @@ -50,7 +53,8 @@ namespace test public: Suite (string groupID); - void run (int argc, char* argv[]); + void run (Arg cmdline); + void describe (); static void enroll (Launcher *test, string testID, string groups); static const string ALLGROUP; diff --git a/src/common/test/testoption.cpp b/src/common/test/testoption.cpp index 2e1e4686e..4e78dee19 100644 --- a/src/common/test/testoption.cpp +++ b/src/common/test/testoption.cpp @@ -21,28 +21,23 @@ * *****************************************************/ -//#include -//#include -//#include -//#include -//#include - #include "common/test/testoption.hpp" #include "common/test/suite.hpp" -//#include "common/util.hpp" #include "nobugcfg.h" #include "common/error.hpp" + +typedef boost::program_options::options_description Syntax; +typedef boost::program_options::variables_map VarMap; + +namespace op = boost::program_options; + +using util::VectS; + namespace test { -// using std::map; -// using std::auto_ptr; -// using std::tr1::shared_ptr; - -// using util::isnil; - /** set up an options parser to use the current commandline. @@ -52,69 +47,81 @@ namespace test * --group * \endcode */ - TestOption::TestOption (int argc, const char* argv[]) - : vm(), cmdline() + TestOption::TestOption (util::Cmdline& cmdline) + : syntax("Run a collection of test cases. Supported parameters"), + parameters() { - cmdline.reserve (10); - parseOptions (argc,argv); + syntax.add_options() + ("help,h", "produce help message") + ("group,g", op::value()->default_value(Suite::ALLGROUP), + "the group (selection) of testcases to execute") + ("describe", op::bool_switch(), + "ennumerate all testcases in this Suite in a format usable with ./test.sh.") + ("id", op::value(), + "an individual testcase to be called.\nIf not specified, run all.") + ; + + // the testcase-ID is really an positional parameter + op::positional_options_description posopt; + posopt.add("id", -1); + + op::parsed_options parsed = + op::command_line_parser (cmdline) + .options (syntax) + .positional(posopt) + .allow_unregistered() + .run(); + + op::store (parsed, parameters); + op::notify(parameters); + + // remove all recognized options from original cmdline vector + cmdline = op::collect_unrecognized(parsed.options, op::include_positional); + + if (parameters.count("help")) + std::cerr << *this; } - /** variant of the ctor taking a "fake" cmdline */ - TestOption::TestOption (string line) - : vm(), cmdline() - { - cmdline.reserve (10); - const char* fakeArg[3] = {"test", line.c_str() }; -// fakeArg[1] = line.c_str(); - parseOptions (1,fakeArg); - } - - - /** do the comandline parsing for the ctors */ - void TestOption::parseOptions (int argc, const char* argv[]) - { - TODO("define options"); - UNIMPLEMENTED("actual commandline parsing!!"); - } /** @return the Tests-Group as given on cmdline, or Suite::ALLGROUP as default */ - const string & + const string TestOption::getTestgroup () { - FIXME("actual commandline parsing!!"); - return Suite::ALLGROUP; + ASSERT (parameters.count ("group")); + return parameters["group"].as(); } - string booooh = "boooh!"; - /** @return ID of a single test to run, empty if not specified + /** @return ID of a single test to run, empty string if not specified */ - const string & + const string TestOption::getTestID () { - UNIMPLEMENTED("actual commandline parsing"); - return booooh; + if (parameters.count ("id") && + parameters["id"].as().size() > 0) + return parameters["id"].as()[0]; + else + return string (); + } + + /** @return \c true if --describe switch was given */ + const bool + TestOption::getDescribe () + { + return parameters["describe"].as(); } - /** gather all remaining unknown cmd line tokens into a vector. - * @Note: the actual vector remains a member of this object, but isn't const + + /** forward the accummulated help messages from all + * contained option defintions to the outputstream */ - vector& - TestOption::remainingCmdline () + ostream& + operator<< (ostream& os, const TestOption& to) { - UNIMPLEMENTED("get unknown remaining options"); - return cmdline; - } - - - /** */ - TestOption::operator string const () - { - UNIMPLEMENTED("convert the remaining Cmndline to string"); - return 0; + return os << to.syntax; } diff --git a/src/common/test/testoption.hpp b/src/common/test/testoption.hpp index c0aa073cb..2006a7d19 100644 --- a/src/common/test/testoption.hpp +++ b/src/common/test/testoption.hpp @@ -24,47 +24,50 @@ #ifndef TESTHELPER_TESTOPTION_H #define TESTHELPER_TESTOPTION_H +#include "common/cmdline.hpp" + #include -#include +#include #include +#include namespace test { using std::string; - using std::vector; + using std::ostream; - typedef boost::program_options::variables_map VarMap; /** * Support for selecting and configuring testcases * via commandline arguments. A preconfigured wrapper * around boost::program_options, with the ability - * to tolerate unknown options and get an vector - * of everything remaining in the commandline after - * parsing the known options. + * to tolerate unknown options. The commandline + * to be parsed is taken wrapped into a Cmdline + * instance; after parsing this commandline + * vector will contain only the remaining + * unrecognized parts. */ - class TestOption + class TestOption : private boost::noncopyable { public: - TestOption (int argc, const char* argv[]); - explicit TestOption (string cmdline); - const string& getTestgroup (); - const string& getTestID (); - vector& remainingCmdline (); - - operator string const (); + TestOption (util::Cmdline& cmdline); + const string getTestgroup (); + const string getTestID (); + const bool getDescribe (); private: - VarMap vm; - vector cmdline; + boost::program_options::options_description syntax; + boost::program_options::variables_map parameters; - void parseOptions (int argc, const char* argv[]); + friend ostream& operator<< (ostream&, const TestOption&); }; - + /** for outputting the help messages. */ + ostream& operator<< (ostream& os, const TestOption& to); + } // namespace test #endif diff --git a/src/common/util.hpp b/src/common/util.hpp index f0a8bfc2f..622084b84 100644 --- a/src/common/util.hpp +++ b/src/common/util.hpp @@ -1,5 +1,5 @@ /* - TIME.hpp - unified representation of a time point, including conversion functions + UTIL.hpp - collection of small helper functions used "everywhere" Copyright (C) CinelerraCV 2007, Christian Thaeter @@ -34,20 +34,54 @@ namespace util /** a family of util functions providing a "no value whatsoever" test */ - inline bool isnil(const string& val) + inline bool isnil(const string& val) { return 0 == val.length(); } - - inline bool isnil(const string* pval) + + inline bool isnil(const string* pval) { return !pval || 0 == pval->length(); } - - inline bool isnil(const char* pval) + + inline bool isnil(const char* pval) { return !pval || 0 == std::strlen(pval); } + + /** cut a numeric value to be >=0 */ + template + inline NUM noneg (NUM val) + { + return (0 + inline bool contains (MAP& map, typename MAP::key_type& key) + { + return map.find(key) != map.end(); + } + + /** shortcut for operating on all elements of a container. + * Isn't this already defined somewhere? It's so obvious.. + */ + template + inline Oper + for_each (Container& c, Oper& doIt) + { + return std::for_each (c.begin(),c.end(), doIt); + } + + } // namespace util + + /* some common macro definitions */ + +/** this macro wraps its parameter into a cstring literal */ +#define STRINGIFY(TOKEN) __STRNGFY(TOKEN) +#define __STRNGFY(TOKEN) #TOKEN + + #endif /*UTIL_HPP_*/ diff --git a/src/nobugcfg.h b/src/nobugcfg.h index c89a284af..fc2ce4622 100644 --- a/src/nobugcfg.h +++ b/src/nobugcfg.h @@ -75,7 +75,8 @@ NOBUG_CPP_DEFINE_FLAG(config); NOBUG_CPP_DEFINE_FLAG(test); - +#include "common/error.hpp" + #endif /* ===================== (End) C++-Part ============= */ diff --git a/tests/50components.tests b/tests/50components.tests index 99e575ad0..033aaf2d5 100644 --- a/tests/50components.tests +++ b/tests/50components.tests @@ -10,6 +10,36 @@ out: This is how the world ends... return: 0 END +TEST "Cmdline Wrapper" CmdlineWrapper_test < +out: wrapping cmdline: +out: ... +out: --> +out: wrapping cmdline:spam... +out: 0|spam| +out: -->spam +out: wrapping cmdline: +out: spam... +out: 0|spam| +out: -->spam +out: wrapping cmdline:eat more spam... +out: 0|eat| +out: 1|more| +out: 2|spam| +out: -->eat more spam +out: wrapping cmdline: oo _O()O_ ä + €... +out: 0|oo| +out: 1|_O()O_| +out: 2|ä| +out: 3|+| +out: 4|€| +out: -->oo _O()O_ ä + € +out: wrapping cmdline:... +out: --> +out: Standard Cmdlineformat: one two +END + TEST "Parseoption" TestOption_test < + + 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 "nobugcfg.h" + +#include "common/test/run.hpp" +#include "common/cmdline.hpp" +#include "common/util.hpp" + +#include +#include + +#include + +using namespace boost::lambda; +using std::cout; + + + +namespace util + { + namespace test + { + + + class CmdlineWrapper_test : public Test + { + virtual void run (Arg arg) + { + testLine(""); + testLine("\n\t "); + testLine("spam"); + testLine("\nspam"); + testLine("eat more spam"); + testLine(" oo _O()O_ ä + €"); + testLine("\0\too\0\to\0o\t\0oo"); + + testStandardCmdlineformat(); + } + + void testLine (const string cmdline) + { + cout << "wrapping cmdline:" << cmdline << "..." << "\n"; + + int i=0; + Cmdline theCmdline (cmdline); + for_each(theCmdline, (cout << var(i)++ << "|" << _1 << "|\n")); + cout << "-->" << theCmdline << "\n"; + + // consistency checks + std::ostringstream output; + output << theCmdline; + ENSURE (output.str() == string(theCmdline)); + + i=0; + string token; + std::istringstream input(theCmdline); + while (input >> token) + ENSURE (token == theCmdline[i++]); + } + + void testStandardCmdlineformat() + { + char* fakeArg[3] = {"CMD", "one ", "two"}; + Cmdline theCmdline(3, fakeArg); + cout << "Standard Cmdlineformat:" << theCmdline << "\n"; + } + }; + + LAUNCHER (CmdlineWrapper_test, "unit common"); + + + } // namespace test + +} // namespace util + diff --git a/tests/components/common/test/testoptiontest.cpp b/tests/components/common/test/testoptiontest.cpp index b34cbe0e8..0738e91e0 100644 --- a/tests/components/common/test/testoptiontest.cpp +++ b/tests/components/common/test/testoptiontest.cpp @@ -26,7 +26,9 @@ #include "common/test/testoption.hpp" #include "common/util.hpp" +using util::Cmdline; using util::isnil; +using std::endl; namespace test { @@ -47,13 +49,14 @@ namespace test void doIt (const string cmdline) { - std::cout << "Testing invocation with cmdline: " << cmdline << "..." << std::endl; + std::cout << "Testing invocation with cmdline: " << cmdline << "..." << endl; - TestOption optparser (cmdline); + Cmdline args(cmdline); + TestOption optparser (args); const string testID = optparser.getTestID(); - std::cout << "--> Testgroup=" << optparser.getTestgroup() << std::endl; - std::cout << "--> Test-ID =" << (isnil(testID)? testID : "--missing--") << std::endl; - std::cout << "--> remaining=" << string(optparser) << std::endl; + std::cout << "--> Testgroup=" << optparser.getTestgroup() << endl; + std::cout << "--> Test-ID =" << (isnil(testID)? "--missing--" : testID ) << endl; + std::cout << "--> remaining=" << args << endl; } void noOptions() { doIt (""); } @@ -63,7 +66,7 @@ namespace test void groupFilter1() { doIt (" SingleTestID --group TestGroupID"); } void groupFilter2() { doIt (" --group TestGroupID SingleTestID "); } void additionalCmd() { doIt (" --group TestGroupID SingleTestID spam eggs"); } - void additionalCmd2() { doIt ("\t\tSingleTestID spam --group TestGroupID \t eggs"); } + void additionalCmd2() { doIt ("\t\tSingleTestID spam --group TestGroupID \t --eggs"); } }; diff --git a/tests/components/mainsuite.cpp b/tests/components/mainsuite.cpp index 3264ad7de..c0de8a8ac 100644 --- a/tests/components/mainsuite.cpp +++ b/tests/components/mainsuite.cpp @@ -22,6 +22,7 @@ #include "common/test/suite.hpp" +#include "common/test/testoption.hpp" /** run all tests or any single test specified in the first * cmd line argument. @@ -29,7 +30,13 @@ */ int main (int argc, char* argv[]) { - test::Suite suite (test::Suite::ALLGROUP); - suite.run(argc,argv); + util::Cmdline args (argc,argv); + test::TestOption optparser (args); + test::Suite suite (optparser.getTestgroup()); + + if (optparser.getDescribe()) + suite.describe(); + else + suite.run (args); return 0; } diff --git a/wiki/index.html b/wiki/index.html index e5f155086..22d424583 100755 --- a/wiki/index.html +++ b/wiki/index.html @@ -747,7 +747,7 @@ config.macros.timeline.handler = function(place,macroName,params,wikifier,paramS } //}}}
-
+
for __Building__
 * gcc (4.1), glibc6 (2.3), libstdc++6 (4.1)
 * [[build system|BuildSystem]] dependencies: SCons (0.96.90), Python (2.3)
@@ -758,6 +758,8 @@ config.macros.timeline.handler = function(place,macroName,params,wikifier,paramS
 ** libboost-dev (=1.34.1-2)
 ** libboost-program-options-dev (=1.34.1-2)
 ** libboost-program-options1.34.1 (=1.34.1-2) ''NOTE: binary dependency''
+** libboost-regex-dev (=1.34.1-2)
+** libboost-regex1.34.1 (=1.34.1-2) ''binary..''
 //usually, newer versions are OK//
 
 for __Running__
From e3d1c35ca36df2475fb10dab95659971bc6fa1e9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 23 Aug 2007 19:13:28 +0200 Subject: [PATCH 4/9] documentation of my internal testcase runner --- tests/50components.tests | 49 +++++++++++++++++++++++++++++++++------- wiki/index.html | 45 +++++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/tests/50components.tests b/tests/50components.tests index 033aaf2d5..20bb036f1 100644 --- a/tests/50components.tests +++ b/tests/50components.tests @@ -1,16 +1,15 @@ +TESTING "Component Test Suite: ALL" ./test-components + -TESTING "Component Test Suite" ./test-components -TEST "Fac test" Factory_test < out: wrapping cmdline: @@ -37,11 +36,45 @@ out: 4|€| out: -->oo _O()O_ ä + € out: wrapping cmdline:... out: --> -out: Standard Cmdlineformat: one two +out: Standard Cmdlineformat:one two END -TEST "Parseoption" TestOption_test < Testgroup=ALL +out: --> Test-ID =--missing-- +out: --> remaining= +out: Testing invocation with cmdline: --help... +out: --> Testgroup=ALL +out: --> Test-ID =--missing-- +out: --> remaining= +out: Testing invocation with cmdline: --group TestGroupID... +out: --> Testgroup=TestGroupID +out: --> Test-ID =--missing-- +out: --> remaining= +out: Testing invocation with cmdline: SingleTestID... +out: --> Testgroup=ALL +out: --> Test-ID =SingleTestID +out: --> remaining=SingleTestID +out: Testing invocation with cmdline: SingleTestID --group TestGroupID... +out: --> Testgroup=TestGroupID +out: --> Test-ID =SingleTestID +out: --> remaining=SingleTestID +out: Testing invocation with cmdline: --group TestGroupID SingleTestID ... +out: --> Testgroup=TestGroupID +out: --> Test-ID =SingleTestID +out: --> remaining=SingleTestID +out: Testing invocation with cmdline: --group TestGroupID SingleTestID spam eggs... +out: --> Testgroup=TestGroupID +out: --> Test-ID =SingleTestID +out: --> remaining=SingleTestID spam eggs +out: Testing invocation with cmdline: SingleTestID spam --group TestGroupID --eggs... +out: --> Testgroup=TestGroupID +out: --> Test-ID =SingleTestID +out: --> remaining=SingleTestID spam --eggs END + +TEST "Factory_test" Factory_test <
-
+
For running the automatic Tests, we use Cehteh's simple [[test.sh|TestSh]].
 
 This page is a proposal (by Ichthyo) how the various tests could be organized.
@@ -3564,6 +3564,49 @@ This page is a proposal (by Ichthyo) how the various tests could be organized.
 * the Testsuite executable provides some command line magic to select individual tests.
 * Top-level Testsuites or ''~Test-Collections'' for [[test.sh|TestSh]] contain calls to the different (sub)-Suites, together with the expected results/output
 
+!internal Testsuite runner
+The class {{{test::Suite}}} (common/test/suite.hpp) helps building an executable which will run all //registered// test case objects, or some group of such testcases. Each test case implements a simple interface and thus provides a {{{run (args)}}} function, moreover, it registers itself immediately alongside with his definition; this works by the usual trick of defining a static class object and calling some registration function from the constructor of this static var. See the following __hello-world-Example__:
+{{{
+#include <iostream>
+#include "common/test/run.hpp"
+    
+    class HelloWorld_test : public Test
+      {
+        virtual void run(Arg arg) 
+          {
+            greeting();
+          } 
+        
+        void greeting() 
+          { 
+            std::cout << "This is how the world ends...\n"; 
+          }
+      };
+
+    /** Register this test class to be invoked in some test groups (suites) */
+    LAUNCHER (HelloWorld_test, "unit function common");    
+}}}
+Notes:
+* type Arg is {{{typedef std::vector<string> & Arg;}}}
+* this vector may be {{{size()==0}}}, which means no comandline args available.
+* otherwise arg[0] is always the ID (normally the classname) of the test
+* the following args may contain further arguments passed from system commandline.
+* the test can/should produce output that can be checked with Cehteh's [[./test.sh|TestSh]].
+* the macro "LAUNCHER" expands to {{{Launch<HelloWorld_test> run_HelloWorld_test("HelloWorld_test","unit function common");}}}
+* note the second parameter to the macro (or the Laucher-ctor) is a space-delimited list of group names
+* thus any test can declare itself as belonging to some groups, and we can create a {{{test::Suite}}} for each group if we want.
+
+!!!invoking the testrunner
+The class {{{test::TestOption}}} predefines a boost-commandlineparser to support the following optons:
+
+|>|!{{{./test-components --group <groupID> [testID [arguments...]]}}}|
+|{{{--help}}}| options summary|
+|{{{--group|-g <groupID>}}}| build a Testsuite out of all tests from this group. If missing, ALL tests will be included |
+|{{{[testID]}}}| (optional) one single testcase. If missing, all testcases of the group will be invoked |
+|{{{--describe}}}| print all registered tests to stdout in a format suited for use with test.sh |
+Further commandline arguments are deliverd to a single testcase only if you specify a {{{testID}}}. Otherwise, all commandline arguments remaining after options parsing will be discarded and all tests of the suite will be run with an commandline vector of size()==0
+
+
 !conventions for the Buildsystem
 to help with automating the build, ichthyo would appreciate to have the following conventions.
 * in the {{{tests}}} directory are 

From 6d9ce217bd1ac70fa87b0083308a745099cc1459 Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Fri, 24 Aug 2007 02:58:13 +0200
Subject: [PATCH 5/9] played a bit with doxygen and tried some small tweeks...

---
 doc/devel/Doxyfile                                  | 11 ++++++-----
 src/common/appconfig.hpp                            |  2 +-
 src/common/test/testoption.cpp                      |  4 ++--
 tests/components/common/factorytest.cpp             |  4 ++++
 tests/components/common/test/cmdlinewrappertest.cpp |  1 +
 tests/components/common/test/testoptiontest.cpp     |  5 +++++
 tests/components/helloworldtest.cpp                 |  2 +-
 7 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/doc/devel/Doxyfile b/doc/devel/Doxyfile
index ebc2bbc4a..7b74108b9 100644
--- a/doc/devel/Doxyfile
+++ b/doc/devel/Doxyfile
@@ -28,12 +28,12 @@ FULL_PATH_NAMES        = YES
 STRIP_FROM_PATH        = ../../src/
 STRIP_FROM_INC_PATH    = 
 SHORT_NAMES            = NO
-JAVADOC_AUTOBRIEF      = NO
+JAVADOC_AUTOBRIEF      = YES
 MULTILINE_CPP_IS_BRIEF = NO
 DETAILS_AT_TOP         = NO
 INHERIT_DOCS           = YES
 SEPARATE_MEMBER_PAGES  = NO
-TAB_SIZE               = 8
+TAB_SIZE               = 4
 ALIASES                = 
 OPTIMIZE_OUTPUT_FOR_C  = NO
 OPTIMIZE_OUTPUT_JAVA   = NO
@@ -52,7 +52,7 @@ HIDE_UNDOC_MEMBERS     = NO
 HIDE_UNDOC_CLASSES     = NO
 HIDE_FRIEND_COMPOUNDS  = NO
 HIDE_IN_BODY_DOCS      = NO
-INTERNAL_DOCS          = NO
+INTERNAL_DOCS          = YES
 CASE_SENSE_NAMES       = YES
 HIDE_SCOPE_NAMES       = NO
 SHOW_INCLUDE_FILES     = YES
@@ -74,7 +74,7 @@ FILE_VERSION_FILTER    =
 #---------------------------------------------------------------------------
 QUIET                  = NO
 WARNINGS               = YES
-WARN_IF_UNDOCUMENTED   = YES
+WARN_IF_UNDOCUMENTED   = NO
 WARN_IF_DOC_ERROR      = YES
 WARN_NO_PARAMDOC       = NO
 WARN_FORMAT            = "$file:$line: $text"
@@ -82,7 +82,8 @@ WARN_LOGFILE           =
 #---------------------------------------------------------------------------
 # configuration options related to the input files
 #---------------------------------------------------------------------------
-INPUT                  = ../../src/
+INPUT                  = ../../src/ \
+                         ../../tests/
 FILE_PATTERNS          = *.c \
                          *.cc \
                          *.cxx \
diff --git a/src/common/appconfig.hpp b/src/common/appconfig.hpp
index 488ac2ba8..d457bc6fe 100644
--- a/src/common/appconfig.hpp
+++ b/src/common/appconfig.hpp
@@ -93,7 +93,7 @@ namespace cinelerra
       typedef std::map Configmap; 
       typedef std::auto_ptr PConfig;
       
-      /** @TODO the following is just placeholder code!
+      /** @todo the following is just placeholder code!
        *  Appconfig could do such things if necessary,
        *  or provide similar "allways available" services.
        */
diff --git a/src/common/test/testoption.cpp b/src/common/test/testoption.cpp
index 4e78dee19..ee169a14c 100644
--- a/src/common/test/testoption.cpp
+++ b/src/common/test/testoption.cpp
@@ -115,8 +115,8 @@ namespace test
   
   
 
-  /** forward the accummulated help messages from all
-   *  contained option defintions to the outputstream 
+  /** @intern forward the accummulated help messages from 
+   *  all contained option defintions to the outputstream 
    */
   ostream& 
   operator<< (ostream& os, const TestOption& to)
diff --git a/tests/components/common/factorytest.cpp b/tests/components/common/factorytest.cpp
index 80574ef75..c4868cc0d 100644
--- a/tests/components/common/factorytest.cpp
+++ b/tests/components/common/factorytest.cpp
@@ -37,6 +37,10 @@ namespace cinelerra
       public:
       };
       
+    ///// @test the various object creation factories
+    ///// @see  cinelerra::Factory
+    ///// @todo still to be written...
+    /////
     class Factory_test : public Test
       {
         virtual void run(Arg arg) 
diff --git a/tests/components/common/test/cmdlinewrappertest.cpp b/tests/components/common/test/cmdlinewrappertest.cpp
index 3a20ef9ca..e3082a561 100644
--- a/tests/components/common/test/cmdlinewrappertest.cpp
+++ b/tests/components/common/test/cmdlinewrappertest.cpp
@@ -43,6 +43,7 @@ namespace util
     {
     
   
+    ///// @test for util::Cmndline, wrapping various example cmdlines
     class CmdlineWrapper_test : public Test
       {
         virtual void run (Arg arg)
diff --git a/tests/components/common/test/testoptiontest.cpp b/tests/components/common/test/testoptiontest.cpp
index 0738e91e0..d3907a120 100644
--- a/tests/components/common/test/testoptiontest.cpp
+++ b/tests/components/common/test/testoptiontest.cpp
@@ -33,6 +33,11 @@ using std::endl;
 namespace test
   {
   
+  ///// @test for test::TestOption, parsing of commandline options
+  /////       for running collections of Tests
+  ///// @see test::Suite
+  ///// @see util::Cmdline
+  /////
   class TestOption_test : public Test
     {
       virtual void run(Arg arg)
diff --git a/tests/components/helloworldtest.cpp b/tests/components/helloworldtest.cpp
index 09c049f55..dccc74940 100644
--- a/tests/components/helloworldtest.cpp
+++ b/tests/components/helloworldtest.cpp
@@ -30,7 +30,7 @@ namespace cinelerra
   namespace test
     {
 
-    
+    ///// @test demo of using the test framework
     class HelloWorld_test : public Test
       {
         virtual void run(Arg arg) 

From 8c7a2055e8671883ae0cd30492d06d7b10124adf Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Fri, 24 Aug 2007 16:41:16 +0200
Subject: [PATCH 6/9] integrate Doxygen into SCons build. some doc fixes

---
 SConstruct                                    | 43 ++++++++++++++-----
 admin/scons/Buildhelper.py                    | 11 ++---
 src/common/test/suite.cpp                     |  3 ++
 src/common/test/testoption.cpp                |  3 --
 src/common/test/testoption.hpp                |  4 +-
 tests/components/common/factorytest.cpp       |  9 ++--
 .../common/test/cmdlinewrappertest.cpp        |  5 ++-
 .../components/common/test/testoptiontest.cpp | 12 +++---
 tests/components/helloworldtest.cpp           |  5 ++-
 9 files changed, 64 insertions(+), 31 deletions(-)

diff --git a/SConstruct b/SConstruct
index bfb239f2e..b9ac74167 100644
--- a/SConstruct
+++ b/SConstruct
@@ -31,7 +31,7 @@ from Buildhelper import *
 OPTIONSCACHEFILE = 'optcache' 
 CUSTOPTIONSFILE  = 'custom-options'
 SRCDIR           = 'src'
-BINDIR           = 'src/bin'
+BINDIR           = 'bin'
 TESTDIR          = 'tests'
 VERSION          = '3+alpha.01'
 #-----------------------------------Configuration
@@ -137,6 +137,7 @@ Special Targets:
      build   : just compile and link
      testcode: additionally compile the Testsuite
      check   : build and run the Testsuite
+     doc     : generate documetation (Doxygen)
      install : install created artifacts at PREFIX
      src.tar : create source tarball
      doc.tar : create developer doc tarball
@@ -243,22 +244,41 @@ def defineBuildTargets(env, artifacts):
     SConscript(dirs=[TESTDIR], exports='env artifacts corelib')
 
 
-def defineInstallTargets(env, artifacts):
-    """ define install locations and cleanup after the build.
+
+def definePostBuildTargets(env, artifacts):
+    """ define further actions after the core build (e.g. Documentaion).
         define alias targets to trigger the installing.
     """
+    ib = env.Alias('install-bin', '$DESTDIR/bin')
+    il = env.Alias('install-lib', '$DESTDIR/lib')
+    env.Alias('install', [ib, il])
+    
+    build = env.Alias('build', '$BINDIR')
+    allbu = env.Alias('allbuild', build+artifacts['testsuite'])
+    env.Default('build')
+    # additional files to be cleaned when cleaning 'build'
+    env.Clean ('build', [ 'scache.conf', '.sconf_temp', '.sconsign.dblite', 'config.log'])
+
+    # Doxygen documentation
+    # Note: at the moment we only depend on Doxyfile
+    #       obviousely, we should depend on all sourcefiles
+    #       real Doxygen builder for scons is under developement for 0.97
+    #       so for the moment I prefere not to bother
+    doxyfile = File('doc/devel/Doxyfile')
+    env.NoClean(doxyfile)
+    doxydoc = artifacts['doxydoc'] = [ Dir('doc/devel/html'), Dir('doc/devel/latex') ]
+    env.Command(doxydoc, doxyfile, "doxygen Doxyfile 2>&1 |tee ,doxylog",  chdir='doc/devel')
+    env.Clean ('doc/devel', doxydoc + ['doc/devel/,doxylog'])
+
+
+def defineInstallTargets(env, artifacts):
+    """ define some artifacts to be installed into target locations.
+    """
     env.Install(dir = '$DESTDIR/bin', source=artifacts['cinelerra'])
     env.Install(dir = '$DESTDIR/lib', source=artifacts['plugins'])
     env.Install(dir = '$DESTDIR/bin', source=artifacts['tools'])
     
-    ib = env.Alias('install-bin', '$DESTDIR/bin')
-    il = env.Alias('install-lib', '$DESTDIR/lib')
-    env.Alias('install', [ib, il])
-    
-    env.Alias('build', '$BINDIR')
-    env.Default('build')
-    # additional files to be cleaned when cleaning 'build'
-    env.Clean ('build', [ 'scache.conf', '.sconf_temp', '.sconsign.dblite', 'config.log'])
+    env.Install(dir = '$DESTDIR/share/doc/cinelerra$VERSION/devel', source=artifacts['doxydoc'])
 
 #####################################################################
 
@@ -286,5 +306,6 @@ artifacts = {}
 
 definePackagingTargets(env, artifacts)
 defineBuildTargets(env, artifacts)
+definePostBuildTargets(env, artifacts)
 defineInstallTargets(env, artifacts)
 
diff --git a/admin/scons/Buildhelper.py b/admin/scons/Buildhelper.py
index 3f6a0e7d5..81d501424 100644
--- a/admin/scons/Buildhelper.py
+++ b/admin/scons/Buildhelper.py
@@ -46,16 +46,17 @@ def isHelpRequest():
 
 
 
-def srcSubtree(env,tree,isShared=False, **args):
+def srcSubtree(env,tree,isShared=False,builder=None, **args):
     """ convienience wrapper: scans the given subtree, which is
         relative to the current SConscript, find all source files and
         declare them as Static or SharedObjects for compilation
     """
     root = env.subst(tree)  # expand Construction Vars
-    if isShared:
-        builder = lambda f: env.SharedObject(f, **args)
-    else:
-        builder = lambda f: env.Object(f, **args)
+    if not builder:
+        if isShared:
+            builder = lambda f: env.SharedObject(f, **args)
+        else:
+            builder = lambda f: env.Object(f, **args)
         
     return [builder(f) for f in scanSrcSubtree(root)] 
 
diff --git a/src/common/test/suite.cpp b/src/common/test/suite.cpp
index df592dec0..355b94135 100644
--- a/src/common/test/suite.cpp
+++ b/src/common/test/suite.cpp
@@ -95,7 +95,10 @@ namespace test
    *  either as a member of one of the specified groups, or direcly
    *  by its testID. Any test is automatically added to the groupID
    *  #ALLGROUP
+   *  @param test the Launcher object used to run this test
+   *  @param testID unique ID to refere to this test (will be used as std::map key)
    *  @param groups List of group-IDs selected by whitespace
+   * 
    */
   void 
   Suite::enroll (Launcher* test, string testID, string groups)
diff --git a/src/common/test/testoption.cpp b/src/common/test/testoption.cpp
index ee169a14c..7fa0d7020 100644
--- a/src/common/test/testoption.cpp
+++ b/src/common/test/testoption.cpp
@@ -115,9 +115,6 @@ namespace test
   
   
 
-  /** @intern forward the accummulated help messages from 
-   *  all contained option defintions to the outputstream 
-   */
   ostream& 
   operator<< (ostream& os, const TestOption& to)
     {
diff --git a/src/common/test/testoption.hpp b/src/common/test/testoption.hpp
index 2006a7d19..ca50bebed 100644
--- a/src/common/test/testoption.hpp
+++ b/src/common/test/testoption.hpp
@@ -65,7 +65,9 @@ namespace test
       friend ostream& operator<< (ostream&, const TestOption&);
     };
   
-  /** for outputting the help messages. */
+    
+  /** for outputting the help messages. Forward accummulated 
+   *  help messages from all contained option defintions */
   ostream& operator<< (ostream& os, const TestOption& to);
  
   
diff --git a/tests/components/common/factorytest.cpp b/tests/components/common/factorytest.cpp
index c4868cc0d..cfa5e5444 100644
--- a/tests/components/common/factorytest.cpp
+++ b/tests/components/common/factorytest.cpp
@@ -37,10 +37,11 @@ namespace cinelerra
       public:
       };
       
-    ///// @test the various object creation factories
-    ///// @see  cinelerra::Factory
-    ///// @todo still to be written...
-    /////
+    /**********************************************************
+     * @test the various object creation factories
+     * @see  cinelerra::Factory
+     * @todo still to be written...
+     */
     class Factory_test : public Test
       {
         virtual void run(Arg arg) 
diff --git a/tests/components/common/test/cmdlinewrappertest.cpp b/tests/components/common/test/cmdlinewrappertest.cpp
index e3082a561..66547535f 100644
--- a/tests/components/common/test/cmdlinewrappertest.cpp
+++ b/tests/components/common/test/cmdlinewrappertest.cpp
@@ -43,7 +43,7 @@ namespace util
     {
     
   
-    ///// @test for util::Cmndline, wrapping various example cmdlines
+    /** @test for util::Cmdline, wrapping various example cmdlines */
     class CmdlineWrapper_test : public Test
       {
         virtual void run (Arg arg)
@@ -80,6 +80,9 @@ namespace util
               ENSURE (token == theCmdline[i++]);
           }
         
+        /** @test wrapping a (albeit faked) standard commandline 
+         *        given as (argc, argv[])
+         */
         void testStandardCmdlineformat()
           {
             char* fakeArg[3] = {"CMD", "one ", "two"};
diff --git a/tests/components/common/test/testoptiontest.cpp b/tests/components/common/test/testoptiontest.cpp
index d3907a120..afd141477 100644
--- a/tests/components/common/test/testoptiontest.cpp
+++ b/tests/components/common/test/testoptiontest.cpp
@@ -33,11 +33,12 @@ using std::endl;
 namespace test
   {
   
-  ///// @test for test::TestOption, parsing of commandline options
-  /////       for running collections of Tests
-  ///// @see test::Suite
-  ///// @see util::Cmdline
-  /////
+  /****************************************************************
+   * invokes the TestOption parser for various example commandlines
+   * @test for test::TestOption, parsing of commandline options
+   * @see test::Suite
+   * @see util::Cmdline
+   */
   class TestOption_test : public Test
     {
       virtual void run(Arg arg)
@@ -52,6 +53,7 @@ namespace test
           additionalCmd2();
         }
       
+      /** @test performs the actual test for the option parser test::TestOption */
       void doIt (const string cmdline)
         {
           std::cout << "Testing invocation with cmdline: " << cmdline << "..." << endl;
diff --git a/tests/components/helloworldtest.cpp b/tests/components/helloworldtest.cpp
index dccc74940..c40511728 100644
--- a/tests/components/helloworldtest.cpp
+++ b/tests/components/helloworldtest.cpp
@@ -30,7 +30,10 @@ namespace cinelerra
   namespace test
     {
 
-    ///// @test demo of using the test framework
+    /******************************************
+     * Helooooooo to the world of TDD
+     * @test demo of using the test framework
+     */
     class HelloWorld_test : public Test
       {
         virtual void run(Arg arg) 

From fee63d8f06fea4c6f0125410930b3272ea91b235 Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Sat, 25 Aug 2007 02:07:04 +0200
Subject: [PATCH 7/9] added C++ plugin to SCons build; added test for
 common/appconfig.hpp

---
 .gitignore                                |  1 +
 {src/bin => bin}/DIR_INFO                 |  0
 tests/50components.tests                  |  5 ++
 tests/SConscript                          |  9 +++-
 tests/components/common/appconfigtest.cpp | 64 +++++++++++++++++++++++
 5 files changed, 77 insertions(+), 2 deletions(-)
 rename {src/bin => bin}/DIR_INFO (100%)
 create mode 100644 tests/components/common/appconfigtest.cpp

diff --git a/.gitignore b/.gitignore
index ca2f40822..d55b1ba9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ Buildhelper.pyc
 optcache
 Makefile.in
 build/*
+bin/*
 autom4te.cache/*
 scripts/*
 configure
diff --git a/src/bin/DIR_INFO b/bin/DIR_INFO
similarity index 100%
rename from src/bin/DIR_INFO
rename to bin/DIR_INFO
diff --git a/tests/50components.tests b/tests/50components.tests
index 20bb036f1..a0104a255 100644
--- a/tests/50components.tests
+++ b/tests/50components.tests
@@ -9,6 +9,11 @@ return: 0
 END
 
 
+TEST "Appconfig_test" Appconfig_test <
diff --git a/tests/SConscript b/tests/SConscript
index dbb7e29a7..3bf0f7396 100644
--- a/tests/SConscript
+++ b/tests/SConscript
@@ -26,8 +26,13 @@ def treatPluginTestcase(env):
     """ Special case: the test-plugin executable
     """
     env = env.Clone()
-    env.Append(CPPPATH='plugin') 
-    testplugin = env.SharedLibrary('.libs/example_plugin', 'plugin/example_plugin.c', SHLIBPREFIX='')
+    env.Append(CPPPATH='plugin')
+    prfx = 'plugin/example_plugin'
+    oC   = env.SharedObject(prfx,        prfx+'.c')
+    oCPP = env.SharedObject(prfx+'_cpp', prfx+'.cpp')
+    testplugin = ( env.SharedLibrary('.libs/example_plugin',     oC,   SHLIBPREFIX='')
+                 + env.SharedLibrary('.libs/example_plugin_cpp', oCPP, SHLIBPREFIX='')
+                 )
     testExe = env.Program('test-plugin', ['plugin/plugin_main.c'] + corelib)
     env.Depends(testExe, testplugin)
     return testExe
diff --git a/tests/components/common/appconfigtest.cpp b/tests/components/common/appconfigtest.cpp
new file mode 100644
index 000000000..bf756701a
--- /dev/null
+++ b/tests/components/common/appconfigtest.cpp
@@ -0,0 +1,64 @@
+/*
+  Appconfig(Test)  -  accessing the allwasy-available Appconfig singleton
+ 
+  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 "nobugcfg.h"
+
+#include "common/appconfig.hpp"
+
+#include "common/test/run.hpp"
+#include "common/util.hpp"
+
+
+#include 
+using std::cout;
+
+
+
+namespace cinelerra
+  {
+  namespace test
+    {
+    
+  
+    class Appconfig_test : public Test
+      {
+        virtual void run (Arg arg)
+          {
+            testAccess("version");
+          }
+        
+        /** @test accessing a value from cinelerra::Appconfig */
+        void testAccess (const string& key)
+          {
+            string ver = cinelerra::Appconfig::get(key);
+            ASSERT ( !util::isnil(ver));
+          }
+      };
+    
+      LAUNCHER (Appconfig_test, "function common");
+
+      
+  } // namespace test
+    
+} // namespace util
+

From d54b600382bf1aff030c1688405dcddd17104636 Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Sun, 26 Aug 2007 19:14:39 +0200
Subject: [PATCH 8/9] implemented C++ error handling system

---
 src/common/appconfig.cpp                      |  45 ++--
 src/common/appconfig.hpp                      |  10 +-
 src/common/error.cpp                          | 178 +++++++++++--
 src/common/error.hpp                          | 141 +++++++----
 src/common/test/suite.cpp                     | 160 ++++++------
 src/common/util.hpp                           |  37 +--
 src/lib/error.h                               |  10 +-
 src/main.cpp                                  |  12 +-
 tests/components/common/appconfigtest.cpp     |   8 +-
 .../components/common/exceptionerrortest.cpp  | 239 ++++++++++++++++++
 tests/components/helloworldtest.cpp           |  12 +-
 tests/components/mainsuite.cpp                |  22 +-
 12 files changed, 651 insertions(+), 223 deletions(-)
 create mode 100644 tests/components/common/exceptionerrortest.cpp

diff --git a/src/common/appconfig.cpp b/src/common/appconfig.cpp
index 1d1cec4e1..d9475bc97 100644
--- a/src/common/appconfig.cpp
+++ b/src/common/appconfig.cpp
@@ -29,6 +29,7 @@
 #include "nobugcfg.h"
 #undef NOBUG_INIT_DEFS_
 
+#include 
 
 
 using util::isnil;
@@ -56,14 +57,18 @@ namespace cinelerra
    */
   Appconfig::Appconfig()
     : configParam_ (new Configmap)
-    {
-      ////////// 
-      NOBUG_INIT;
-      //////////
-      
-      INFO(config, "Basic application configuration triggered.");
-      (*configParam_)["version"] = STRINGIFY (CINELERRA_VERSION);
-    }
+  {
+    ////////// 
+    NOBUG_INIT;
+    //////////
+    
+    INFO(config, "Basic application configuration triggered.");
+    
+    // install our own handler for undeclared exceptions
+    std::set_unexpected (cinelerra::error::cinelerra_unexpectedException);
+    
+    (*configParam_)["version"] = STRINGIFY (CINELERRA_VERSION);
+  }
   
   
   
@@ -74,18 +79,18 @@ namespace cinelerra
    */
   const string &
   Appconfig::get (const string & key)  throw()
-    {
-      try
-        {
-          const string& val = (*instance().configParam_)[key];
-          WARN_IF( isnil(val), config, "undefined config parameter \"%s\" requested.", key.c_str());
-          return val;
-        }
-      catch (...)
-        {
-          ERROR(config, "error while accessing configuration parameter \"%s\".", key.c_str());
-          throw cinelerra::error::Fatal ();
-    }   }
+  {
+    try
+      {
+        const string& val = (*instance().configParam_)[key];
+        WARN_IF( isnil(val), config, "undefined config parameter \"%s\" requested.", key.c_str());
+        return val;
+      }
+    catch (...)
+      {
+        ERROR(config, "error while accessing configuration parameter \"%s\".", key.c_str());
+        throw cinelerra::error::Fatal ();
+  }   }
 
   
 
diff --git a/src/common/appconfig.hpp b/src/common/appconfig.hpp
index d457bc6fe..84ea317ec 100644
--- a/src/common/appconfig.hpp
+++ b/src/common/appconfig.hpp
@@ -75,11 +75,11 @@ namespace cinelerra
        *  @warning don't use it in destruction code!
        */
       static Appconfig& instance()
-        {
-          static scoped_ptr theApp_ (0);
-          if (!theApp_) theApp_.reset (new Appconfig ());
-          return *theApp_;
-        }
+      {
+        static scoped_ptr theApp_ (0);
+        if (!theApp_) theApp_.reset (new Appconfig ());
+        return *theApp_;
+      }
       
       
       /** access the configuation value for a given key.
diff --git a/src/common/error.cpp b/src/common/error.cpp
index cd46fa15f..4c7b5b656 100644
--- a/src/common/error.cpp
+++ b/src/common/error.cpp
@@ -21,48 +21,174 @@
 * *****************************************************/
 
 
-#include "common/error.hpp"
-#include "nobugcfg.h"
 
-///////////////////////////////////TODO
+#include "common/error.hpp"
+#include "common/util.hpp"
+
+#include 
+#include 
 #include 
 
-extern void booo()
-  {
-    std::cerr << "Booooo!!!" << std::endl;
-    std::cerr.flush();
-  }
-///////////////////////////////////TODO
+using util::isnil;
+using std::exception;
+
 
 namespace cinelerra
   {
-  char* killme ="cinelerra Errrror. TODO real description needed";
+
+  namespace error
+    {
+    
+    /** the message shown to the user per default
+     *  if an exception reaches one of the top-level
+     *  catch clauses.
+     *  @todo to be localized
+     */
+    inline const string default_usermsg (Error* exception_obj)  throw() 
+    {
+      return string("Sorry, Cinelerra encountered an internal error. (")
+           + typeid(*exception_obj).name() + ")";
+    }
+    
+    
+    /* constants to be used as error IDs */
+    CINELERRA_ERROR_DEFINE (LOGIC    , "internal logic broken");   
+    CINELERRA_ERROR_DEFINE (FATAL    , "floundered");      
+    CINELERRA_ERROR_DEFINE (CONFIG   , "misconfiguration"); 
+    CINELERRA_ERROR_DEFINE (STATE    , "unforseen state"); 
+    CINELERRA_ERROR_DEFINE (INVALID  , "invalid input or parameters"); 
+    CINELERRA_ERROR_DEFINE (EXTERNAL , "failure in external service"); 
+    CINELERRA_ERROR_DEFINE (ASSERTION, "assertion failure");
+
+  } // namespace error
+  
+  CINELERRA_ERROR_DEFINE (EXCEPTION, "generic cinelerra exception"); 
+  
+  
+  
+  
+  /** @note we set the C-style errorstate as a side effect */
+  Error::Error (string description, const char* id) throw()
+    : std::exception (),
+      id_ (id),
+      msg_ (error::default_usermsg (this)),
+      desc_ (description),
+      cause_ ("")
+  {
+    cinelerra_error_set (this->id_);
+  }
+  
+  
+  Error::Error (std::exception& cause, 
+                string description, const char* id) throw()
+    : std::exception (),
+      id_ (id),
+      msg_ (error::default_usermsg (this)),
+      desc_ (description),
+      cause_ (extractCauseMsg(cause))
+  {
+    cinelerra_error_set (this->id_);
+  }
+  
+  
+  /** @note copy ctor behaves like chaining, i.e setting the cause_. */
+  Error::Error (const Error& ref) throw()
+    : std::exception (),
+      id_ (ref.id_),
+      msg_ (ref.msg_),
+      desc_ (ref.desc_),
+      cause_ (extractCauseMsg(ref))
+  { }
+
+
   
   /** Description of the problem, including the internal char constant
-   *  in accordance to cinelerras error identification scheme.
-   *  If a ::rootCause() can be obtained, this will be included in the
+   *  in accordance to cinelerra's error identification scheme.
+   *  If a root cause can be obtained, this will be included in the
    *  generated output as well. 
    */
   const char*
-  Error::what () const  throw()
-    {
-      TODO("really implement cinelerra::Error description");
-      return killme; 
-    }
+  Error::what() const  throw()
+  {
+    if (isnil (this->what_))
+      {
+        what_ = string(id_);
+        if (!isnil (desc_))  what_ += " ("+desc_+").";
+        if (!isnil (cause_)) what_ += string(" -- caused by: ") + cause_;
+      }
+    return what_.c_str(); 
+  }
   
-  
-  /** If this exception was caused by a chain of further exceptions,
-   *  return the first one registered in this throw sequence.
-   *  This works only, if every exceptions thrown as a consequence
-   *  of another exception is propperly constructed by passing
-   *  the original exception to the constructor
+
+  /** @internal get at the description message of the 
+   *  first exception encountered in a chain of exceptions
    */
-  std::exception 
-  Error::rootCause() const  throw()
+  const string
+  Error::extractCauseMsg (const exception& cause)  throw()
+  {
+    const Error* err=dynamic_cast (&cause);
+    if (err)
+      if (isnil (err->cause_))
+        return cause.what(); // cause is root cause
+      else
+        return err->cause_; // cause was caused by another exception
+    
+    // unknown other exception type
+    return cause.what ();
+  }
+
+  
+/* -- originally, I wanted to chain the exception objects themselfs.
+      but this doesn't work; we'd need to clone the "cause" error object,
+      because it can be destroyed when leaving the original
+      handler by throwing a new exception.
+      Anyways, not needed at the moment; maybe later? 8/2007  
+  
+  const exception&
+  Error::rootCause () const throw()
     {
-      UNIMPLEMENTED("storing and managing root causes");
+      const exception * root(this);
+      if (this->cause)
+        if (Error* err = dynamic_cast (this->cause))
+          root = &err->rootCause ();
+        else
+          root = this->cause;
+      
+      ENSURE (root);
+      ENSURE (root!=this || !cause); 
+      return *root;
     }
+*/  
   
   
   
+  
+  
+  namespace error
+  {
+    
+    void cinelerra_unexpectedException ()  throw()
+    {
+      const char* is_halted 
+        = "### Cinelerra halted due to an unexpected Error ###";
+      
+      std::cerr << "\n" << is_halted << "\n\n";
+      ERROR (NOBUG_ON, "%s", is_halted);
+      
+      if (const char * errorstate = cinelerra_error ())
+        ERROR (NOBUG_ON, "last registered error was....\n%s", errorstate);
+      
+      std::terminate();
+    }
+
+    void assertion_terminate (const string& location)
+    {
+      throw Fatal (location, CINELERRA_ERROR_ASSERTION)
+                 .setUsermsg("Program terminated because of violating "
+                             "an internal consistency check.");    
+    }
+    
+    
+  } // namespace error
+  
 } // namespace cinelerra
diff --git a/src/common/error.hpp b/src/common/error.hpp
index bc72d6861..fff8490e4 100644
--- a/src/common/error.hpp
+++ b/src/common/error.hpp
@@ -21,44 +21,75 @@
 */
 
 
-#ifndef CINELERRA_ERROR_H
-#define CINELERRA_ERROR_H
+#ifndef CINELERRA_ERROR_HPP_
+#define CINELERRA_ERROR_HPP_
 
-#include 
+#include 
+#include "nobugcfg.h"
+#include "lib/error.h"
 
 
 namespace cinelerra
   {
+  using std::string;
 
+
+  
+  /** error-ID for unspecified exceptions */
+  CINELERRA_ERROR_DECLARE(EXCEPTION);  
   
   /**
    * Interface and Baseclass of all Exceptions thrown 
    * from within cinelerra (C++) code. Common operations
    * for getting an diagnostic message and for obtaining
-   * the root cause, i.e. the frist exception encountered
+   * the root cause, i.e. the first exception encountered
    * in a chain of exceptions. 
    */
   class Error : public std::exception
     {
     public:
+      Error (string description="", const char* id=CINELERRA_ERROR_EXCEPTION) throw();
+      Error (std::exception& cause,
+             string description="", const char* id=CINELERRA_ERROR_EXCEPTION) throw();
+      
+      Error (const Error&) throw();
       virtual ~Error () throw() {};
       
-      /** yield a diagnostig message characterizing the problem */
+      /** yield a diagnostic message characterizing the problem */
       virtual const char* what () const throw();
+
+      /** the internal cinelerra-error-ID (was set as C-errorstate in ctor) */
+      const char* getID () const throw() { return this->id_; }
+      
+      /** extract the message to be displayed for the user */
+      const string& getUsermsg () const throw();
       
       /** If this exception was caused by a chain of further exceptions,
-       *  return the first one registered in this throw sequence.
-       *  This works only, if every exceptions thrown as a consequence
-       *  of another exception is propperly constructed by passing
-       *  the original exception to the constructor
+       *  return the description of the first one registered in this throw sequence.
+       *  This works only if every exceptions thrown as a consequence of another exception 
+       *  is propperly constructed by passing the original exception to the constructor
+       *  @return the description string, maybe empty (if there is no known root cause)
        */
-      std::exception rootCause () const throw();
+      const string& rootCause () const throw()  { return this->cause_; }
 
+      /** replace the previous or default friendly message for the user. To be localized. */
+      Error& setUsermsg (const string& newMsg) throw() { this->msg_ = newMsg; return *this; }
+
+      /** give additional developer info. Typically used at intermediate handlers to add context. */
+      Error& prependInfo (const string& text) throw() { this->desc_.insert (0,text); return *this; }
+      
+      
     private:
-      /** a copy of the first exception encountered in this exception chain */
-      std::exception cause;
+      const char* id_;       ///< an CINELERRA_ERROR id, which is set as errorstate on construction
+      string msg_;           ///< friendly message intended for users (to be localized)
+      string desc_;          ///< detailed description of the error situation for the developers
+      mutable string what_;  ///< buffer for generating the detailed description on demand
+      const string cause_;   ///< descriptoin of first exception encountered in the chain
 
+      static const string extractCauseMsg (const std::exception&)  throw();
     };
+  
+    
     
     
     
@@ -68,45 +99,62 @@ namespace cinelerra
   namespace error
     {
     
-    class Logic : public Error
-      {
-        
+    /** global function for handling unknown exceptions
+     *  encountered at functions declaring not to throw 
+     *  this kind of exception. Basically, any such event
+     *  can be considered a severe design flaw; we can just
+     *  add some diagnostics prior to halting.
+     */
+    void cinelerra_unexpectedException ()  throw();
+
+    /** throw an error::Fatal indicating "assertion failure" */
+    void assertion_terminate (const string& location);
+    
+
+    /* constants to be used as error IDs */
+    CINELERRA_ERROR_DECLARE (LOGIC    );    ///< contradiction to internal logic assumptions detected  
+    CINELERRA_ERROR_DECLARE (FATAL    );    ///< unable to cope with, internal logic floundered     
+    CINELERRA_ERROR_DECLARE (CONFIG   );    ///< execution aborted due to misconfiguration
+    CINELERRA_ERROR_DECLARE (STATE    );    ///< unforseen internal state
+    CINELERRA_ERROR_DECLARE (INVALID  );    ///< invalid input or parameters encountered
+    CINELERRA_ERROR_DECLARE (EXTERNAL );    ///< failure in external service the application relies on
+    CINELERRA_ERROR_DECLARE (ASSERTION);    ///< assertion failure
+    
+/** Macro for creating derived exception classes properly 
+ *  integrated into cinelerra's exception hierarchy. Using
+ *  this macro asures that the new class will get the full
+ *  set of constructors and behaviour common to all exception
+ *  classes, so it should be used when creating an derived
+ *  exception type for more then stricly local purposes
+ */
+#define CINELERRA_EXCEPTION_DECLARE(CLASS, PARENT, _ID_) \
+    class CLASS : public PARENT                          \
+      {                                                  \
+      public:                                            \
+        CLASS (std::string description="",               \
+               const char* id=_ID_) throw()              \
+        : PARENT (description, id)  {}                   \
+                                                         \
+        CLASS (std::exception& cause,                    \
+               std::string description="",               \
+               const char* id=_ID_) throw()              \
+        : PARENT (cause, description, id)   {}           \
       };
 
-    class Fatal : public Logic
-      {
-        
-      };
-      
-    class Config : public Error
-      {
-        
-      };
-      
-    class State : public Error
-      {
-        
-      };
-      
-    class Invalid : public Error
-      {
-        
-      };
-      
-    class External : public Error
-      {
-        
-      };
-      
-   
+    //---------------------------CLASS-----PARENT--ID----------------------
+    CINELERRA_EXCEPTION_DECLARE (Logic,    Error,  CINELERRA_ERROR_LOGIC);
+    CINELERRA_EXCEPTION_DECLARE (Fatal,    Logic,  CINELERRA_ERROR_FATAL);
+    CINELERRA_EXCEPTION_DECLARE (Config,   Error,  CINELERRA_ERROR_CONFIG);
+    CINELERRA_EXCEPTION_DECLARE (State,    Error,  CINELERRA_ERROR_STATE);
+    CINELERRA_EXCEPTION_DECLARE (Invalid,  Error,  CINELERRA_ERROR_INVALID);
+    CINELERRA_EXCEPTION_DECLARE (External, Error,  CINELERRA_ERROR_EXTERNAL);
+    
     
   } // namespace error
 
 } // namespace cinelerra
 
-#include 
 
-extern void booo();
 
 /******************************************************
  * if NoBug is used, redefine some macros 
@@ -114,8 +162,11 @@ extern void booo();
  */
 #ifdef NOBUG_ABORT
 #undef NOBUG_ABORT
-#define NOBUG_ABORT throw cinelerra::error::Fatal(); ////////////////TODO
+#define CIN_NOBUG_LOCATION \
+  std::string (NOBUG_BASENAME(__FILE__)) +":"+ NOBUG_STRINGIZE(__LINE__) + ", function " + __func__
+#define NOBUG_ABORT \
+  cinelerra::error::assertion_terminate (CIN_NOBUG_LOCATION);
 #endif
 
 
-#endif
+#endif // CINELERRA_ERROR_HPP_
diff --git a/src/common/test/suite.cpp b/src/common/test/suite.cpp
index 355b94135..b2cfcfea8 100644
--- a/src/common/test/suite.cpp
+++ b/src/common/test/suite.cpp
@@ -21,6 +21,7 @@
 * *****************************************************/
 
 
+
 #include 
 #include 
 #include 
@@ -29,13 +30,13 @@
 #include 
 #include 
 
+#include "nobugcfg.h"
 #include "common/cmdline.hpp"
 #include "common/test/suite.hpp"
 #include "common/test/run.hpp"
 #include "common/error.hpp"
 #include "common/util.hpp"
 
-#include "nobugcfg.h"  
 
 
 namespace test
@@ -75,16 +76,16 @@ namespace test
     
   void 
   Registry::add2group (Launcher* test, string testID, string groupID)
-    {
-      REQUIRE( test );
-      REQUIRE( !isnil(testID) );
-      REQUIRE( !isnil(groupID) );
-      
-      PTestMap& group = getGroup(groupID);
-      if (!group)
-        group.reset( new TestMap );
-      (*group)[testID] = test;
-    }
+  {
+    REQUIRE( test );
+    REQUIRE( !isnil(testID) );
+    REQUIRE( !isnil(groupID) );
+    
+    PTestMap& group = getGroup(groupID);
+    if (!group)
+      group.reset( new TestMap );
+    (*group)[testID] = test;
+  }
   
   Registry testcases;
   
@@ -102,18 +103,18 @@ namespace test
    */
   void 
   Suite::enroll (Launcher* test, string testID, string groups)
-    {
-      REQUIRE( test );
-      REQUIRE( !isnil(testID) );
-      
-      std::istringstream ss(groups);
-      string group;
-      while (ss >> group )
-        testcases.add2group(test, testID, group);
-      
-      // Magic: allways add any testcas to groupID="ALL"
-      testcases.add2group(test,testID, ALLGROUP);
-    }
+  {
+    REQUIRE( test );
+    REQUIRE( !isnil(testID) );
+    
+    std::istringstream ss(groups);
+    string group;
+    while (ss >> group )
+      testcases.add2group(test, testID, group);
+    
+    // Magic: allways add any testcas to groupID="ALL"
+    testcases.add2group(test,testID, ALLGROUP);
+  }
   
   /** "magic" groupID containing all registered testcases */
   const string Suite::ALLGROUP = "ALL";
@@ -127,14 +128,14 @@ namespace test
    */
   Suite::Suite(string groupID) 
     : groupID_(groupID)
-    {
-      REQUIRE( !isnil(groupID) );
-      TRACE(test, "Test-Suite( groupID=%s )\n", groupID.c_str () );
-      
-      if (!testcases.getGroup(groupID))
-        throw cinelerra::error::Invalid ();
-        //throw "empty testsuite";     /////////// TODO Errorhandling!
-    }
+  {
+    REQUIRE( !isnil(groupID) );
+    TRACE(test, "Test-Suite( groupID=%s )\n", groupID.c_str () );
+    
+    if (!testcases.getGroup(groupID))
+      throw cinelerra::error::Invalid ();
+      //throw "empty testsuite";     /////////// TODO Errorhandling!
+  }
     
 #define VALID(test,testID) \
   ASSERT ((test), "NULL testcase laucher for test '%s' found in testsuite '%s'", groupID_.c_str(),testID.c_str());
@@ -151,36 +152,36 @@ namespace test
    */
   void 
   Suite::run (Arg cmdline)
-    {
-      PTestMap tests = testcases.getGroup(groupID_);
-      if (!tests)
-        throw cinelerra::error::Invalid (); ///////// TODO: pass error description
-      
-      if (0 < cmdline.size())
-        {
-          string& testID (cmdline[0]);
-          trim(testID);
-          if ( contains (*tests, testID))
-            {
-              // first cmdline argument denotes a valid testcase registered in 
-              // this group: invoke just this test with the remaining cmdline
-              Launcher* test = (*tests)[testID];
-              cmdline.erase (cmdline.begin());
-              VALID (test,testID);
-              (*test)()->run(cmdline);
-              return;
-        }   }
-      
-      // no test-ID was specified.
-      // instantiiate all tests cases and execute them.
-      for ( TestMap::iterator i=tests->begin(); i!=tests->end(); ++i )
-        {
-          std::cout << "\n  ----------"<< i->first<< "----------\n";
-          Launcher* test = (i->second);
-          VALID (test, i->first);
-          (*test)()->run(cmdline); // actually no cmdline arguments
-        }
-    }
+  {
+    PTestMap tests = testcases.getGroup(groupID_);
+    if (!tests)
+      throw cinelerra::error::Invalid (); ///////// TODO: pass error description
+    
+    if (0 < cmdline.size())
+      {
+        string& testID (cmdline[0]);
+        trim(testID);
+        if ( contains (*tests, testID))
+          {
+            // first cmdline argument denotes a valid testcase registered in 
+            // this group: invoke just this test with the remaining cmdline
+            Launcher* test = (*tests)[testID];
+            cmdline.erase (cmdline.begin());
+            VALID (test,testID);
+            (*test)()->run(cmdline);
+            return;
+      }   }
+    
+    // no test-ID was specified.
+    // instantiiate all tests cases and execute them.
+    for ( TestMap::iterator i=tests->begin(); i!=tests->end(); ++i )
+      {
+        std::cout << "\n  ----------"<< i->first<< "----------\n";
+        Launcher* test = (i->second);
+        VALID (test, i->first);
+        (*test)()->run(cmdline); // actually no cmdline arguments
+      }
+  }
   
   
   /** print to stdout an ennumeration of all testcases in this suite,
@@ -188,27 +189,24 @@ namespace test
    */
   void
   Suite::describe ()
-    {
-      util::Cmdline noCmdline("");
-      PTestMap tests = testcases.getGroup(groupID_);
-      ASSERT (tests);
-      
-      std::cout << "TESTING \"Component Test Suite: " << groupID_ << "\" ./test-components\n\n";
+  {
+    util::Cmdline noCmdline("");
+    PTestMap tests = testcases.getGroup(groupID_);
+    ASSERT (tests);
+    
+    std::cout << "TESTING \"Component Test Suite: " << groupID_ << "\" ./test-components\n\n";
 
-      for ( TestMap::iterator i=tests->begin(); i!=tests->end(); ++i )
-        {
-          string key (i->first);
-          std::cout << "\n\n";
-          std::cout << "TEST \""<second);
-          VALID (test, i->first);
-          (*test)()->run(noCmdline); // run it to insert test generated output
-          std::cout << "END\n";
-        }
-
-      
-
-    }
+    for ( TestMap::iterator i=tests->begin(); i!=tests->end(); ++i )
+      {
+        string key (i->first);
+        std::cout << "\n\n";
+        std::cout << "TEST \""<second);
+        VALID (test, i->first);
+        (*test)()->run(noCmdline); // run it to insert test generated output
+        std::cout << "END\n";
+      }
+  }
 
 
 
diff --git a/src/common/util.hpp b/src/common/util.hpp
index 622084b84..cf5f6ca55 100644
--- a/src/common/util.hpp
+++ b/src/common/util.hpp
@@ -35,34 +35,35 @@ namespace util
 
   /** a family of util functions providing a "no value whatsoever" test */
   inline bool isnil(const string& val)
-    {
-      return 0 == val.length();
-    }
+  {
+    return 0 == val.length();
+  }
   
   inline bool isnil(const string* pval)
-    {
-      return !pval || 0 == pval->length();
-    }
+  {
+    return !pval || 0 == pval->length();
+  }
   
   inline bool isnil(const char* pval)
-    {
-      return !pval || 0 == std::strlen(pval);
-    }
+  {
+    return !pval || 0 == std::strlen(pval);
+  }
 
   
   /** cut a numeric value to be >=0 */
   template 
   inline NUM noneg (NUM val)
-    {
-      return (0
   inline bool contains (MAP& map, typename MAP::key_type& key)
-    {
-      return map.find(key) != map.end();
-    }
+  {
+    return map.find(key) != map.end();
+  }
+  
   
   /** shortcut for operating on all elements of a container.
    *  Isn't this already defined somewhere? It's so obvious..
@@ -70,9 +71,9 @@ namespace util
   template 
   inline Oper
   for_each (Container& c, Oper& doIt)
-    {
-      return std::for_each (c.begin(),c.end(), doIt);
-    }
+  {
+    return std::for_each (c.begin(),c.end(), doIt);
+  }
    
   
 } // namespace util
diff --git a/src/lib/error.h b/src/lib/error.h
index abc2d151a..b467df1b5 100644
--- a/src/lib/error.h
+++ b/src/lib/error.h
@@ -21,6 +21,12 @@
 #ifndef CINELERRA_ERROR_H
 #define CINELERRA_ERROR_H
 
+#ifdef __cplusplus
+extern "C" {
+#elif 0
+} /*eek, fixes emacs indenting for now*/
+#endif
+
 #include 
 #include 
 
@@ -42,5 +48,7 @@ cinelerra_error_set (const char * err);
 const char*
 cinelerra_error ();
 
-
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
 #endif /* CINELERRA_ERROR_H */
diff --git a/src/main.cpp b/src/main.cpp
index ccc1dd481..ae1d20e9c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -31,9 +31,9 @@ using cinelerra::Appconfig;
 
 
 int main (int argc, char* argv[])
-  {
-    cout << "*** Cinelerra NLE for Linux ***" << endl
-         << "    Version: " << Appconfig::get("version") << endl;
-    assert(true);
-    return 0;
-  }
+{
+  cout << "*** Cinelerra NLE for Linux ***" << endl
+       << "    Version: " << Appconfig::get("version") << endl;
+  assert(true);
+  return 0;
+}
diff --git a/tests/components/common/appconfigtest.cpp b/tests/components/common/appconfigtest.cpp
index bf756701a..748e05a72 100644
--- a/tests/components/common/appconfigtest.cpp
+++ b/tests/components/common/appconfigtest.cpp
@@ -49,10 +49,10 @@ namespace cinelerra
         
         /** @test accessing a value from cinelerra::Appconfig */
         void testAccess (const string& key)
-          {
-            string ver = cinelerra::Appconfig::get(key);
-            ASSERT ( !util::isnil(ver));
-          }
+        {
+          string ver = cinelerra::Appconfig::get(key);
+          ASSERT ( !util::isnil(ver));
+        }
       };
     
       LAUNCHER (Appconfig_test, "function common");
diff --git a/tests/components/common/exceptionerrortest.cpp b/tests/components/common/exceptionerrortest.cpp
new file mode 100644
index 000000000..2fdfa80b1
--- /dev/null
+++ b/tests/components/common/exceptionerrortest.cpp
@@ -0,0 +1,239 @@
+/*
+  Exceptionhandlin(Test)  -  throwing and catching our exception type
+ 
+  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 "lib/error.h"
+#include "common/error.hpp"
+#include "common/appconfig.hpp"
+
+#include "common/test/run.hpp"
+#include "common/util.hpp"
+
+
+#include 
+#include 
+#include 
+#include 
+
+using std::runtime_error;
+using std::exception;
+using std::string;
+using std::cout;
+
+
+
+namespace cinelerra
+  {
+  namespace test
+    {
+        
+    /** local specific error-constant for use in the 
+     *  construcor of the nested SpecificError class.
+     */
+    CINELERRA_ERROR_DEFINE(LIFE_AND_UNIVERSE, "and everything?");
+    CINELERRA_ERROR_DEFINE(DERIVED, "convoluted exception");
+    
+    /** declare a specific Error class with parent class error::external */
+    CINELERRA_EXCEPTION_DECLARE (DerivedError, error::External,  CINELERRA_ERROR_DERIVED);
+    
+    
+    /**********************************************************
+     * Some aspects of C++ exception handling.
+     * Not to be confused with the basic C-style error value 
+     * mechanism used by the low-level parts of the backend.
+     * Both approaches are laregely orthogonal, but the
+     * C++ exception handling uses the C-style error constants.
+     * 
+     */
+    class ExceptionError_test : public Test
+      {
+        typedef ExceptionError_test test;
+        virtual void run (Arg arg)
+          {
+            if (0 < arg.size() && arg[1]=="terminate")
+              terminateUnknown ();
+            
+            
+            catcher (&test::throwSpecial,  "");
+            catcher (&test::throwDerived,  "test-1");
+            catcher (&test::throwFatal,    "test-2");
+            catcher (&test::throwInvalid,  "test-3");
+            catcher (&test::throwExternal, "test-4");
+            catcher (&test::throwRuntime,  "test-5");
+            catcher (&test::throwExceptn,  "test-6");
+            
+            catcher (&test::nestedThrower, "test-7");
+            catcher (&test::doubleNestedTh,"test-8");
+            
+            checkErrorIntegration();
+            checkRootCauseChaining();
+          }
+        
+        
+        
+        /** @test simply throw some exception and pass context info */
+        void throwSpecial (string _) { throw SpecificError(); }
+        void throwDerived (string _) { throw DerivedError(); }
+        void throwFatal   (string _) { throw error::Fatal(_); }
+        void throwInvalid (string _) { throw error::Invalid(_); }
+        void throwExternal(string _) { throw error::External(_); }
+        void throwRuntime (string _) { throw std::runtime_error(_); }
+        void throwExceptn (string _) { throw std::exception(error::State(_)); }
+
+        
+        /** @test catching, repackaging and rethrowing of errors.
+         *  This feature is important for passing exceptions transparentely
+         *  over several layers. The nested operation will throw an error::External,
+         *  which we are able to catch because it is derived from std::exception.
+         *  We don't need to know the exact type, but we can classify the error situation
+         *  as a "state error" and throw an error::State, passing on the root cause.
+         *  Some levels up, this error get caught and the root cause can be 
+         *  extracted successfully.
+         */
+        void nestedThrower (string msg) throw(Error)
+        {
+          try { throwExternal(msg); }
+          catch (std::exception& e)
+            {
+              cout << "intermediate handler caught: " << e.what() 
+                   << "....will rethrow as error::State\n";
+              throw error::State (e);
+            }
+        }
+        
+        /** @test repeated repackaging and rethrowing */
+        void doubleNestedTh (string msg) throw(error::Config)
+        {
+          try { nestedThrower (msg); }
+          catch (Error& e)
+            {
+              cout << "2nd intermediate handler caught: " << e.what() 
+                   << "....will rethrow as error::Config\n";
+              throw error::Config (e);
+            }
+        }
+        
+        
+        /** @test by constructing an cinelerra::Error object,
+         *  the corresponding cinelerra_error state is set automatically
+         */
+        void checkErrorIntegration()
+        {
+          cinelerra_error ();
+          ASSERT (!cinelerra_error ());
+          
+          Error err1;
+          Error err2("boo",CINELERRA_ERROR_DERIVED);
+          ASSERT (err1.getID () == cinelerra_error ()); // (we didn't clear the first one!)
+          
+          Error err3("boooo",CINELERRA_ERROR_DERIVED);
+          ASSERT (err3.getID () == cinelerra_error ());
+          
+          SpecificError err4;
+          ASSERT (err4.getID () == CINELERRA_ERROR_LIFE_AND_UNIVERSE);
+          ASSERT (err4.getID () == cinelerra_error ());
+          
+          ASSERT (!cinelerra_error ());
+        }
+        
+        /** @test the chaining of cinelerra::Exception objects
+         *  and the retrieval of the original root cause.
+         */
+        void checkRootCauseChaining()
+        {
+          error::Logic err1;
+          error::Config err2(err1);
+          error::Config err3(err2); //note: using copy ctor behaves like chaining
+          Error err4(err1);        // note: copy ctor
+          
+          std::runtime_error rerr("what a shame");
+          error::External err5(rerr);
+          Error err6(err5);
+          
+          ASSERT (err2.rootCause() == err1.what());
+          ASSERT (err3.rootCause() == err1.what());
+          ASSERT (err4.rootCause() == err1.what());
+
+          ASSERT (err5.rootCause() == rerr.what());
+          ASSERT (err6.rootCause() == rerr.what());
+        }
+        
+        
+        /** @test terminate the Application by throwing an undclared exception.
+         *        this should result in the global unknown() handler to be called,
+         *        so usually it will terminate the testrun. 
+         *  @note because we call Appconfig::instance(), our own unknown() handler
+         *        gets installed and invoked, which gives additional diagnostics.*/
+        void terminateUnknown ()  throw() 
+        {
+          cinelerra::Appconfig::instance();
+          // will cause initialisation of Appconfig,
+          
+          throw Error("You'll never get me, won't you?");
+        }
+        
+        
+        /** a very specific Exception class
+         *  local to this scope and with 
+         *  additional behaviour.
+         */ 
+        class SpecificError : public error::Invalid
+          {
+            int value;
+          public:
+            SpecificError () : Invalid("don't panic",CINELERRA_ERROR_LIFE_AND_UNIVERSE), value(42) {}
+            int revealIt ()   { return value; }
+          };
+        
+        
+        
+        /** helper: provides a bunch of catch-clauses and 
+         *  runs the given member functions within
+         */
+        void catcher (void (ExceptionError_test::*funky)(string), string context)
+        {
+          try
+            {
+              (this->*funky) (context);
+            }
+          
+          catch (SpecificError& e) { cout << "caught: " << e.what() << "..the answer is: " << e.revealIt() << "\n"; }                
+          catch (error::Logic&  e) { cout << "caught error::Logic: " << e.what() << "\n"; }                
+          catch (error::Invalid&e) { cout << "caught error::Invalid: " << e.what() << "\n"; }                
+          catch (Error&         e) { cout << "caught cinelerra::Error: " << e.what() << "\n"; }                
+          catch (runtime_error& e) { cout << "caught std::runtime_error: " << e.what() << "\n"; }                
+          catch (exception&     e) { cout << "caught std::exception: " << e.what() << "\n"; }
+          catch (...)              { cout << "caught an unknown exception\n"; }
+        }
+      };
+
+      
+      
+      /** register this test class... */
+      LAUNCHER (ExceptionError_test, "function common");
+
+      
+  } // namespace test
+    
+} // namespace util
+
diff --git a/tests/components/helloworldtest.cpp b/tests/components/helloworldtest.cpp
index c40511728..be1e07122 100644
--- a/tests/components/helloworldtest.cpp
+++ b/tests/components/helloworldtest.cpp
@@ -37,14 +37,14 @@ namespace cinelerra
     class HelloWorld_test : public Test
       {
         virtual void run(Arg arg) 
-          {
-            greeting();
-          } 
+        {
+          greeting();
+        } 
         
         void greeting() 
-          { 
-            std::cout << "This is how the world ends...\n"; 
-          }
+        { 
+          std::cout << "This is how the world ends...\n"; 
+        }
       };
     
       
diff --git a/tests/components/mainsuite.cpp b/tests/components/mainsuite.cpp
index c0de8a8ac..dac63ba7c 100644
--- a/tests/components/mainsuite.cpp
+++ b/tests/components/mainsuite.cpp
@@ -29,14 +29,14 @@
  *  Note: to ease debugging, we don't catch any exceptions. 
  */
 int main (int argc, char* argv[])
-  {
-    util::Cmdline args (argc,argv);
-    test::TestOption optparser (args);
-    test::Suite suite (optparser.getTestgroup());
-    
-    if (optparser.getDescribe())
-      suite.describe();
-    else
-      suite.run (args);
-    return 0;
-  }
+{
+  util::Cmdline args (argc,argv);
+  test::TestOption optparser (args);
+  test::Suite suite (optparser.getTestgroup());
+  
+  if (optparser.getDescribe())
+    suite.describe();
+  else
+    suite.run (args);
+  return 0;
+}

From 307945b6297e2742245b484ed0198221e79fb87f Mon Sep 17 00:00:00 2001
From: Ichthyostega 
Date: Wed, 29 Aug 2007 05:03:21 +0200
Subject: [PATCH 9/9] wrote test: object smart pointer creation factory

---
 src/common/error.hpp                    |   2 +-
 src/common/factory.hpp                  |   4 +-
 src/common/util.hpp                     |  18 ++--
 tests/50components.tests                |  32 ++++++-
 tests/components/common/factorytest.cpp | 120 ++++++++++++++++++++++--
 tests/components/helloworldtest.cpp     |  11 ++-
 wiki/support_library.html               |  22 ++++-
 7 files changed, 186 insertions(+), 23 deletions(-)

diff --git a/src/common/error.hpp b/src/common/error.hpp
index fff8490e4..f89b0a80a 100644
--- a/src/common/error.hpp
+++ b/src/common/error.hpp
@@ -115,7 +115,7 @@ namespace cinelerra
     CINELERRA_ERROR_DECLARE (LOGIC    );    ///< contradiction to internal logic assumptions detected  
     CINELERRA_ERROR_DECLARE (FATAL    );    ///< unable to cope with, internal logic floundered     
     CINELERRA_ERROR_DECLARE (CONFIG   );    ///< execution aborted due to misconfiguration
-    CINELERRA_ERROR_DECLARE (STATE    );    ///< unforseen internal state
+    CINELERRA_ERROR_DECLARE (STATE    );    ///< unforeseen internal state
     CINELERRA_ERROR_DECLARE (INVALID  );    ///< invalid input or parameters encountered
     CINELERRA_ERROR_DECLARE (EXTERNAL );    ///< failure in external service the application relies on
     CINELERRA_ERROR_DECLARE (ASSERTION);    ///< assertion failure
diff --git a/src/common/factory.hpp b/src/common/factory.hpp
index 8664675c8..bb2a28fee 100644
--- a/src/common/factory.hpp
+++ b/src/common/factory.hpp
@@ -60,6 +60,8 @@ namespace cinelerra
        *  Note: non-virtual.
        */
       SMP operator() (){ return SMP (new T ); };
+
+      typedef SMP ptype;
       
     private:
       void operator= (const Factory&); // copy prohibited
@@ -102,7 +104,7 @@ namespace cinelerra
          */
         static void destroy (T* victim) { delete victim; };
         
-    public:
+      public:
         shared_ptr operator() ()     { return shared_ptr (new T, &destroy ); }
       };
       
diff --git a/src/common/util.hpp b/src/common/util.hpp
index cf5f6ca55..a9a90e9b0 100644
--- a/src/common/util.hpp
+++ b/src/common/util.hpp
@@ -33,20 +33,24 @@ namespace util
   using std::string;
 
 
-  /** a family of util functions providing a "no value whatsoever" test */
-  inline bool isnil(const string& val)
+  /** a family of util functions providing a "no value whatsoever" test.
+      Works on strings and all STL containers, includes NULL test for pointers */
+  template 
+  inline bool isnil(const CONT& container)
   {
-    return 0 == val.length();
+    return container.empty();
   }
   
-  inline bool isnil(const string* pval)
+  template 
+  inline bool isnil(const CONT* pContainer)
   {
-    return !pval || 0 == pval->length();
+    return !pContainer || pContainer->empty();
   }
   
-  inline bool isnil(const char* pval)
+  template <>
+  inline bool isnil(const char* pCStr)
   {
-    return !pval || 0 == std::strlen(pval);
+    return !pCStr || 0 == std::strlen(pCStr);
   }
 
   
diff --git a/tests/50components.tests b/tests/50components.tests
index a0104a255..afab5f280 100644
--- a/tests/50components.tests
+++ b/tests/50components.tests
@@ -3,7 +3,9 @@ TESTING "Component Test Suite: ALL" ./test-components
 
 
 
-TEST "Hello test" HelloWorld_test < Testgroup=ALL
@@ -79,7 +105,3 @@ out: --> Testgroup=TestGroupID
 out: --> Test-ID  =SingleTestID
 out: --> remaining=SingleTestID spam --eggs
 END
-
-
-TEST "Factory_test" Factory_test <
+#include 
+#include 
+#include 
+
+using boost::algorithm::join;
+using boost::lexical_cast;
+using boost::format;
+using util::isnil;
+using std::string;
+using std::cout;
 
 
 namespace cinelerra
@@ -29,28 +43,122 @@ namespace cinelerra
   namespace test
     {
     
+    class ObjFactory;
+    
     /**
-     * Target object to be created by the Test-Factory
+     * Target object to be created by the Test-Factory.
+     * Allocates a variable amount of additional heap memory
+     * and prints diagnostic messages.
      */
     class TargetObj
       {
+        uint cnt_;
+        string* heapData_; 
+        string* heapArray_; 
+        
+        
+        TargetObj(uint num)
+          : cnt_ (num),
+            heapData_ (new string(num,'*')),
+            heapArray_ (new string[num])
+        {
+          for (uint i=0; i(i);
+          cout << format("ctor TargetObj(%i) successfull\n") % cnt_;
+        }
+        
+        
+        ~TargetObj()  throw()
+        {
+          delete heapData_;
+          delete[] heapArray_;
+          cout << format("dtor ~TargetObj(%i) successfull\n") % cnt_;
+        }
+        
+        friend class ObjFactory;
+        
+        
       public:
+        static ObjFactory create;
+        
+        operator string () const
+        {
+          string array_contents = "{";
+          for (uint i=0; i
+      {
+        static void destroy (TargetObj* victim) { delete victim; };
+      public:
+        /** specialized Factory method for creating TargetObj instances.
+         *  Here, we invoke a special constructor, but basically we could
+         *  do everything we want, creating instances of sub classes,
+         *  registering objects etc. Further, we could have used a
+         *  custom allocator or a special deleter function. 
+         */
+        ptype operator() (uint param){ return ptype (new TargetObj (param), &destroy); };
+      };
+    
+
+    /** shorthand for the created smart-pointer class, 
+     *  here it's a (refcounting) boost::shared_ptr
+     */
+    typedef ObjFactory::ptype pTarget;
+
+    ObjFactory TargetObj::create;
+    
+    
+    
+    
+    
+    
+    /*******************************************************************
+     * @test the basic object creation Factory behaviour: We declared
+     *       a static field TargetObj::create to be a ObjFactory. So,
+     *       by invoking this functor, we get a boost::shared_ptr
+     *       wrapping a new TargetObj instance. From this we copy
+     *       further shared-ptrs, invoke a member function and
+     *       finally, when leaving the scope, our TargetObj
+     *       will be destroyed automatically.
      * @see  cinelerra::Factory
-     * @todo still to be written...
      */
     class Factory_test : public Test
       {
         virtual void run(Arg arg) 
           {
+            uint num= isnil(arg)? 1 : lexical_cast(arg[1]);
+            
+            pTarget p1 (TargetObj::create (num));
+            pTarget p2 (p1);
+            pTarget p3 = p2;
+            
+            cout << "now the smart-ptr has refcount=" << p1.use_count() << "\n" 
+                 << string (*p3) << "\n";
           } 
       };
     
-    /** Register this test class to be invoked in some test groups (suites) */
-    Launch run_Factory_test("Factory_test","unit common");
+    
+    /** Register this test class... */
+    LAUNCHER (Factory_test, "unit common");
     
     
     
diff --git a/tests/components/helloworldtest.cpp b/tests/components/helloworldtest.cpp
index be1e07122..3a9011736 100644
--- a/tests/components/helloworldtest.cpp
+++ b/tests/components/helloworldtest.cpp
@@ -24,6 +24,12 @@
 #include 
 #include "common/test/run.hpp"
 
+#include "common/util.hpp"
+using util::isnil;
+
+#include 
+using boost::lexical_cast;
+
 
 namespace cinelerra
   {
@@ -38,7 +44,10 @@ namespace cinelerra
       {
         virtual void run(Arg arg) 
         {
-          greeting();
+          int num= isnil(arg)?  1 : lexical_cast (arg[1]);
+          
+          for ( ; 0 < num-- ; )
+            greeting();
         } 
         
         void greeting() 
diff --git a/wiki/support_library.html b/wiki/support_library.html
index 6e8b5a717..176405c64 100644
--- a/wiki/support_library.html
+++ b/wiki/support_library.html
@@ -707,7 +707,7 @@ The next point is allocation failures. These are possible by C/C++ standard but
 
 
-
+
Basically, the C++ error handling techniques are layered on top of the [[C solution|ErrorHandling-C]].
 !Proposal:
 We use a common base class for all our application specific exceptions. These exceptions can be thought of as a classification of error situations, thus the hierarchical approach. The purpose of throwing such a //classified exception// is
@@ -716,8 +716,26 @@ We use a common base class for all our application specific exceptions. These ex
 
 !!Requirements for the Exception Interface
 * a means for capturing and transporting detail informations
-* the possibility to link to the ''root cause'' of an exception, even after having passed several subsystem barriers.
+* the possibility to get at the ''root cause'' of an exception, even after having passed several subsystem barriers.
 * getting standardized error messages automatically
+
+!!provided features
+The C++ errorhandling Classes and functions can be found in {{{common/error.hpp}}} (not to be confused with the elementary C errorhandling of the cinelerra support lib {{{lib/error.h}}}. See also the "exceptionerrortest.cpp"
+* the constructor of the Exception base class will set the C-style error flag as well. Obviously, when an exception gets caught and handled, this error-flag should be reset (and this is the responsibility of the handler).
+* we add a __unknown()__ handler which will print additional diagnostics.
+* the Exception class has a diagnostic message intended for developers and a friendly message for the user. It is encouraged to write a detailed description of each error situation right into a string constant passed to the exception object ctor. This will serve the purpose of documenting the error situation in the source code and at the same time help diagnosis.
+* there is a variant of the constructor taking a reference to an std::exception. This is intended for //chained exceptions//. Whenever an handler catches an exception, but then decides to rethrow it with different classification, the original exception object should be passed on by using this constructor, so it's {{{what()}}} message can be preserved and will be included in the final log entry. While this may look like overkill in a small example, it is a very helpful facility in a larger layered application, where it is often difficult to spot the original cause of an exception encountered.
+* for each mayor category of Exception subclasses, we define a C-style error constant. The client code is free to define further detailed error constants and Exception subclasses.
+* to help defining Exception subclasses, a macro {{{CINELERRA_EXCEPTION_DECLARE}}} is provided.
+
+!!!basic Exception categories
+|!category|!description|
+|error::Logic| contradiction to internal logic assumptions detected|
+|error::Fatal| special subclass of Logic: situation can't be handled, internal logic floundered |
+|error::Config| execution aborted due to misconfiguration |
+|error::State| unforeseen internal state |
+|error::Invalid| invalid input or parameters encountered |
+|error::External| failure in external service the application relies on |