diff --git a/.gitignore b/.gitignore index 141d31bb7..d55b1ba9f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,12 @@ *~ *.tar.* .[^.]* +*.os Buildhelper.pyc optcache Makefile.in build/* +bin/* autom4te.cache/* scripts/* configure diff --git a/SConstruct b/SConstruct index 9b11e379e..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 @@ -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' ) @@ -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 @@ -174,6 +175,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') @@ -183,15 +185,22 @@ 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.' 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 (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) + # create new env containing the finished configuration return conf.Finish() @@ -235,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']) ##################################################################### @@ -278,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/bin/DIR_INFO b/bin/DIR_INFO similarity index 100% rename from src/bin/DIR_INFO rename to bin/DIR_INFO diff --git a/doc/devel/Doxyfile b/doc/devel/Doxyfile index 5702db723..21e8d51c1 100644 --- a/doc/devel/Doxyfile +++ b/doc/devel/Doxyfile @@ -33,7 +33,7 @@ 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 = YES 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.cpp b/src/common/appconfig.cpp index 769cde604..d9475bc97 100644 --- a/src/common/appconfig.cpp +++ b/src/common/appconfig.cpp @@ -29,6 +29,8 @@ #include "nobugcfg.h" #undef NOBUG_INIT_DEFS_ +#include + using util::isnil; @@ -41,10 +43,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 @@ -55,38 +57,40 @@ namespace cinelerra */ Appconfig::Appconfig() : configParam_ (new Configmap) - { - ////////// - NOBUG_INIT; - ////////// - - INFO(config, "Basic application configuration triggered."); - (*configParam_)["version"] = 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); + } + + + - - - /** 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()); - } - } + { + 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..84ea317ec 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 (); - return *theApp_; - } - + { + 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. + /** @todo the following is just placeholder code! + * 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/error.cpp b/src/common/error.cpp index 0e869fe93..4c7b5b656 100644 --- a/src/common/error.cpp +++ b/src/common/error.cpp @@ -21,33 +21,174 @@ * *****************************************************/ + #include "common/error.hpp" +#include "common/util.hpp" + +#include +#include +#include + +using util::isnil; +using std::exception; + namespace cinelerra { + + 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() - { - } + 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() { + 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 973fa1d8a..f89b0a80a 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,35 +99,74 @@ 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 ); ///< 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 + +/** 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 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 + + + +/****************************************************** + * if NoBug is used, redefine some macros + * to rather throw Cinelerra Errors instead of aborting + */ +#ifdef NOBUG_ABORT +#undef NOBUG_ABORT +#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 // CINELERRA_ERROR_HPP_ 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/tests/components/helper/run.hpp b/src/common/test/run.hpp similarity index 86% rename from tests/components/helper/run.hpp rename to src/common/test/run.hpp index 518ff37d2..668f29645 100644 --- a/tests/components/helper/run.hpp +++ b/src/common/test/run.hpp @@ -28,7 +28,8 @@ #include #include -#include "helper/suite.hpp" +#include "common/test/suite.hpp" +#include "common/util.hpp" namespace test @@ -37,7 +38,7 @@ namespace test using std::string; using std::auto_ptr; - typedef std::vector * Arg; + typedef std::vector & Arg; @@ -89,4 +90,10 @@ 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_); + + #endif diff --git a/src/common/test/suite.cpp b/src/common/test/suite.cpp new file mode 100644 index 000000000..b2cfcfea8 --- /dev/null +++ b/src/common/test/suite.cpp @@ -0,0 +1,213 @@ +/* + 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 +#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" + + + +namespace test + { + using std::map; + 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; + typedef map GroupMap; + + + + /** helper to collect and manage the test cases. + * Every testcase class should create a Launch instance + * which causes a call to Suite::enroll(), so we can add a + * pointer to this Launcher into a map indexed by the + * provided testIDs and groupIDs. + * This enables us to build a Suite instance for any + * requested group and then instantiiate and invoke + * individual testcases accordingly. + */ + class Registry + { + auto_ptr groups; + public: + Registry() : groups(new GroupMap ) {}; + PTestMap& getGroup (string grpID) { return (*groups)[grpID]; }; + void add2group (Launcher* test, string testID, string groupID); + }; + + 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; + } + + Registry testcases; + + + + + /** register the given test-launcher, so it can be later accessed + * 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) + { + 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"; + + + + /** create a suite comprised of all the testcases + * previously @link #enroll() registered @endlink with this + * this group. + * @see #run() running tests in a Suite + */ + 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! + } + +#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. + * 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 (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 + } + } + + + /** 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"; + } + } + + + +} // namespace test diff --git a/tests/components/helper/suite.hpp b/src/common/test/suite.hpp similarity index 76% rename from tests/components/helper/suite.hpp rename to src/common/test/suite.hpp index 8060aaab3..acf9d270e 100644 --- a/tests/components/helper/suite.hpp +++ b/src/common/test/suite.hpp @@ -24,6 +24,7 @@ #ifndef TESTHELPER_SUITE_H #define TESTHELPER_SUITE_H +#include #include @@ -35,12 +36,16 @@ namespace test // Forward decls needed for run.hpp class Test; class Launcher; - + + typedef std::vector & Arg; /** - * Helper class for running a collection of tests. - * + * Enables running a collection of tests. + * 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. */ class Suite { @@ -48,13 +53,13 @@ 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; }; - - - + + } // namespace test #endif diff --git a/src/common/test/testoption.cpp b/src/common/test/testoption.cpp new file mode 100644 index 000000000..7fa0d7020 --- /dev/null +++ b/src/common/test/testoption.cpp @@ -0,0 +1,126 @@ +/* + 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 "common/test/testoption.hpp" +#include "common/test/suite.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 + { + + + /** set up an options parser to use the current commandline. + * reconizes the following options + * \code + * --help + * --group + * \endcode + */ + TestOption::TestOption (util::Cmdline& cmdline) + : syntax("Run a collection of test cases. Supported parameters"), + parameters() + { + 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; + } + + + + + /** @return the Tests-Group as given on cmdline, or Suite::ALLGROUP as default + */ + const string + TestOption::getTestgroup () + { + ASSERT (parameters.count ("group")); + return parameters["group"].as(); + } + + /** @return ID of a single test to run, empty string if not specified + */ + const string + TestOption::getTestID () + { + 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(); + } + + + + ostream& + operator<< (ostream& os, const TestOption& to) + { + return os << to.syntax; + } + + + +} // namespace test diff --git a/src/common/test/testoption.hpp b/src/common/test/testoption.hpp new file mode 100644 index 000000000..ca50bebed --- /dev/null +++ b/src/common/test/testoption.hpp @@ -0,0 +1,75 @@ +/* + 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 "common/cmdline.hpp" + +#include +#include +#include +#include + + + +namespace test + { + using std::string; + using std::ostream; + + + + /** + * Support for selecting and configuring testcases + * via commandline arguments. A preconfigured wrapper + * around boost::program_options, with the ability + * 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 : private boost::noncopyable + { + public: + TestOption (util::Cmdline& cmdline); + const string getTestgroup (); + const string getTestID (); + const bool getDescribe (); + + private: + boost::program_options::options_description syntax; + boost::program_options::variables_map parameters; + + friend ostream& operator<< (ostream&, const TestOption&); + }; + + + /** for outputting the help messages. Forward accummulated + * help messages from all contained option defintions */ + ostream& operator<< (ostream& os, const TestOption& to); + + +} // namespace test +#endif diff --git a/src/common/util.hpp b/src/common/util.hpp index f0a8bfc2f..a9a90e9b0 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 @@ -33,21 +33,60 @@ namespace util using std::string; - /** a family of util functions providing a "no value whatsoever" test */ - inline bool isnil(const string& val) - { - return 0 == val.length(); - } - - inline bool isnil(const string* pval) - { - return !pval || 0 == pval->length(); - } - - inline bool isnil(const char* pval) - { - return !pval || 0 == std::strlen(pval); - } + /** 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 container.empty(); + } + + template + inline bool isnil(const CONT* pContainer) + { + return !pContainer || pContainer->empty(); + } + + template <> + inline bool isnil(const char* pCStr) + { + return !pCStr || 0 == std::strlen(pCStr); + } + + /** 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/lib/error.h b/src/lib/error.h index 359b91f66..b5df1720e 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 @@ -78,5 +84,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/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/.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..afab5f280 100644 --- a/tests/50components.tests +++ b/tests/50components.tests @@ -1,12 +1,107 @@ +TESTING "Component Test Suite: ALL" ./test-components -TESTING "Component Test Suite" ./test-components -TEST "Fac test" Factory_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 "ExceptionError_test" ExceptionError_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 diff --git a/tests/SConscript b/tests/SConscript index 95ecdc0a7..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('hello_1', '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 @@ -46,7 +51,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 +66,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 +77,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']) diff --git a/tests/components/common/appconfigtest.cpp b/tests/components/common/appconfigtest.cpp new file mode 100644 index 000000000..748e05a72 --- /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 + 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/common/factorytest.cpp b/tests/components/common/factorytest.cpp index 37bc66587..b3038faf4 100644 --- a/tests/components/common/factorytest.cpp +++ b/tests/components/common/factorytest.cpp @@ -21,7 +21,21 @@ * *****************************************************/ -#include "helper/run.hpp" +#include "common/test/run.hpp" +#include "common/factory.hpp" +#include "common/util.hpp" + +#include +#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,23 +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 + */ 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/common/test/cmdlinewrappertest.cpp b/tests/components/common/test/cmdlinewrappertest.cpp new file mode 100644 index 000000000..66547535f --- /dev/null +++ b/tests/components/common/test/cmdlinewrappertest.cpp @@ -0,0 +1,100 @@ +/* + Cmdlinewrapper(Test) - build vector of tokens from cmdline, various conversions + + 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/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 + { + + + /** @test for util::Cmdline, wrapping various example cmdlines */ + 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++]); + } + + /** @test wrapping a (albeit faked) standard commandline + * given as (argc, argv[]) + */ + 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 new file mode 100644 index 000000000..afd141477 --- /dev/null +++ b/tests/components/common/test/testoptiontest.cpp @@ -0,0 +1,83 @@ +/* + TestOption(Test) - parsing of cmd line options for running Testcases + + 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 "common/test/run.hpp" +#include "common/test/testoption.hpp" +#include "common/util.hpp" + +using util::Cmdline; +using util::isnil; +using std::endl; + +namespace test + { + + /**************************************************************** + * 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) + { + noOptions(); + help(); + groupID(); + singleTest(); + groupFilter1(); + groupFilter2(); + additionalCmd(); + 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; + + Cmdline args(cmdline); + TestOption optparser (args); + const string testID = optparser.getTestID(); + std::cout << "--> Testgroup=" << optparser.getTestgroup() << endl; + std::cout << "--> Test-ID =" << (isnil(testID)? "--missing--" : testID ) << endl; + std::cout << "--> remaining=" << args << 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 70% rename from tests/components/common/helloworldtest.cpp rename to tests/components/helloworldtest.cpp index d22ad619d..3a9011736 100644 --- a/tests/components/common/helloworldtest.cpp +++ b/tests/components/helloworldtest.cpp @@ -22,7 +22,13 @@ #include -#include "helper/run.hpp" +#include "common/test/run.hpp" + +#include "common/util.hpp" +using util::isnil; + +#include +using boost::lexical_cast; namespace cinelerra @@ -30,18 +36,24 @@ namespace cinelerra namespace test { - + /****************************************** + * Helooooooo to the world of TDD + * @test demo of using the test framework + */ class HelloWorld_test : public Test { virtual void run(Arg arg) - { + { + int num= isnil(arg)? 1 : lexical_cast (arg[1]); + + for ( ; 0 < num-- ; ) greeting(); - } + } void greeting() - { - std::cout << "This is how the world ends...\n"; - } + { + std::cout << "This is how the world ends...\n"; + } }; @@ -49,6 +61,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/helper/suite.cpp b/tests/components/helper/suite.cpp deleted file mode 100644 index 6d8250cac..000000000 --- a/tests/components/helper/suite.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/* - 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 - -#include "helper/suite.hpp" -#include "helper/run.hpp" -#include "common/error.hpp" -#include "common/util.hpp" - -#include "nobugcfg.h" - - -namespace test - { - using std::map; - using std::vector; - using std::auto_ptr; - using std::tr1::shared_ptr; - - using util::isnil; - - typedef map TestMap; - typedef shared_ptr PTestMap; - typedef map GroupMap; - - - - /** helper to collect and manage the test cases. - * Every testcase class should create a Launch instance - * which causes a call to Suite::enroll(), so we can add a - * pointer to this Launcher into a map indexed by the - * provided testIDs and groupIDs. - * This enables us to build a Suite instance for any - * requested group and then instantiiate and invoke - * individual testcases accordingly. - */ - class Registry - { - auto_ptr groups; - public: - Registry() : groups(new GroupMap ) {}; - PTestMap& getGroup (string grpID) { return (*groups)[grpID]; }; - void add2group (Launcher* test, string testID, string groupID); - }; - - 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; - } - - Registry testcases; - - - - - /** register the given test-launcher, so it can be later accessed - * 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 groups List of group-IDs selected by whitespace - */ - 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); - } - - /** "magic" groupID containing all registered testcases */ - const string Suite::ALLGROUP = "ALL"; - - - - /** create a suite comprised of all the testcases - * previously @link #enroll() registered @endlink with this - * this group. - * @see #run() running tests in a Suite - */ - 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! - } - - - /** run all testcases contained in this Suite. - * The first argument in the commandline, if present, will select - * one single testcase with a matching ID. - */ - void - Suite::run (int argc, char* argv[]) - { - - PTestMap tests = testcases.getGroup(groupID_); - if (!tests) - throw cinelerra::error::Invalid (); - - if (argc >= 2) - { - if (Launcher* test = (*tests)[argv[1]]) - { - // 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 - vector arglist(argc-2); - for ( int i=2; irun(&arglist); - } - else - (*test)()->run(0); // without additional argumens - - 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); - test()->run(0); // without cmdline arguments - } - - } - - - -} // namespace test diff --git a/tests/components/mainsuite.cpp b/tests/components/mainsuite.cpp index 9ed0831fe..dac63ba7c 100644 --- a/tests/components/mainsuite.cpp +++ b/tests/components/mainsuite.cpp @@ -21,16 +21,22 @@ */ -#include "helper/suite.hpp" - +#include "common/test/suite.hpp" +#include "common/test/testoption.hpp" /** run all tests or any single test specified in the first * cmd line argument. * Note: to ease debugging, we don't catch any exceptions. */ int main (int argc, char* argv[]) - { - test::Suite suite (test::Suite::ALLGROUP); - suite.run(argc,argv); - 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; +} diff --git a/wiki/index.html b/wiki/index.html index b41cbfcc9..cfd0de1ef 100755 --- a/wiki/index.html +++ b/wiki/index.html @@ -747,14 +747,19 @@ 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''
+** libboost-regex-dev (=1.34.1-2)
+** libboost-regex1.34.1 (=1.34.1-2) ''binary..''
 //usually, newer versions are OK//
 
 for __Running__
@@ -3549,7 +3554,7 @@ Here is the order suggested:
-
+
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.
@@ -3561,6 +3566,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 
diff --git a/wiki/support_library.html b/wiki/support_library.html
index 144de95db..ac753035f 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 |