Merge branch 'master' of git://git.pipapo.org/cinelerra3/ichthyo

This commit is contained in:
Christian Thaeter 2007-08-30 01:42:06 +02:00
commit 5832cfeae2
34 changed files with 1864 additions and 386 deletions

2
.gitignore vendored
View file

@ -2,10 +2,12 @@
*~ *~
*.tar.* *.tar.*
.[^.]* .[^.]*
*.os
Buildhelper.pyc Buildhelper.pyc
optcache optcache
Makefile.in Makefile.in
build/* build/*
bin/*
autom4te.cache/* autom4te.cache/*
scripts/* scripts/*
configure configure

View file

@ -31,7 +31,7 @@ from Buildhelper import *
OPTIONSCACHEFILE = 'optcache' OPTIONSCACHEFILE = 'optcache'
CUSTOPTIONSFILE = 'custom-options' CUSTOPTIONSFILE = 'custom-options'
SRCDIR = 'src' SRCDIR = 'src'
BINDIR = 'src/bin' BINDIR = 'bin'
TESTDIR = 'tests' TESTDIR = 'tests'
VERSION = '3+alpha.01' VERSION = '3+alpha.01'
#-----------------------------------Configuration #-----------------------------------Configuration
@ -61,7 +61,7 @@ def setupBasicEnvironment():
, SRCDIR=SRCDIR , SRCDIR=SRCDIR
, BINDIR=BINDIR , BINDIR=BINDIR
, CPPPATH=["#"+SRCDIR] # used to find includes, "#" means always absolute to build-root , 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' , CCFLAGS='-Wall'
) )
@ -137,6 +137,7 @@ Special Targets:
build : just compile and link build : just compile and link
testcode: additionally compile the Testsuite testcode: additionally compile the Testsuite
check : build and run the Testsuite check : build and run the Testsuite
doc : generate documetation (Doxygen)
install : install created artifacts at PREFIX install : install created artifacts at PREFIX
src.tar : create source tarball src.tar : create source tarball
doc.tar : create developer doc 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.' print 'Did not find the pthread lib or pthread.h, exiting.'
else: else:
conf.env.Append(CPPFLAGS = ' -DHAVE_PTHREAD_H') conf.env.Append(CPPFLAGS = ' -DHAVE_PTHREAD_H')
conf.env.Append(CCFLAGS = ' -pthread')
if conf.CheckCHeader('execinfo.h'): if conf.CheckCHeader('execinfo.h'):
conf.env.Append(CPPFLAGS = ' -DHAS_EXECINFO_H') conf.env.Append(CPPFLAGS = ' -DHAS_EXECINFO_H')
@ -183,15 +185,22 @@ def configurePlatform(env):
if not conf.CheckCXXHeader('tr1/memory'): if not conf.CheckCXXHeader('tr1/memory'):
print 'We rely on the std::tr1 proposed standard extension for shared_ptr.' print 'We rely on the std::tr1 proposed standard extension for shared_ptr.'
Exit(1) Exit(1)
if not conf.CheckCXXHeader('boost/config.hpp'): if not conf.CheckCXXHeader('boost/config.hpp'):
print 'We need the C++ boost-lib.' print 'We need the C++ boost-lib.'
Exit(1) Exit(1)
else:
if not conf.CheckCXXHeader('boost/shared_ptr.hpp'): if not conf.CheckCXXHeader('boost/shared_ptr.hpp'):
print 'We need boost::shared_ptr (shared_ptr.hpp).' print 'We need boost::shared_ptr (shared_ptr.hpp).'
Exit(1) 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 # create new env containing the finished configuration
return conf.Finish() return conf.Finish()
@ -235,22 +244,41 @@ def defineBuildTargets(env, artifacts):
SConscript(dirs=[TESTDIR], exports='env artifacts corelib') 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. 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/bin', source=artifacts['cinelerra'])
env.Install(dir = '$DESTDIR/lib', source=artifacts['plugins']) env.Install(dir = '$DESTDIR/lib', source=artifacts['plugins'])
env.Install(dir = '$DESTDIR/bin', source=artifacts['tools']) env.Install(dir = '$DESTDIR/bin', source=artifacts['tools'])
ib = env.Alias('install-bin', '$DESTDIR/bin') env.Install(dir = '$DESTDIR/share/doc/cinelerra$VERSION/devel', source=artifacts['doxydoc'])
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'])
##################################################################### #####################################################################
@ -278,5 +306,6 @@ artifacts = {}
definePackagingTargets(env, artifacts) definePackagingTargets(env, artifacts)
defineBuildTargets(env, artifacts) defineBuildTargets(env, artifacts)
definePostBuildTargets(env, artifacts)
defineInstallTargets(env, artifacts) defineInstallTargets(env, artifacts)

View file

@ -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 """ convienience wrapper: scans the given subtree, which is
relative to the current SConscript, find all source files and relative to the current SConscript, find all source files and
declare them as Static or SharedObjects for compilation declare them as Static or SharedObjects for compilation
""" """
root = env.subst(tree) # expand Construction Vars root = env.subst(tree) # expand Construction Vars
if isShared: if not builder:
builder = lambda f: env.SharedObject(f, **args) if isShared:
else: builder = lambda f: env.SharedObject(f, **args)
builder = lambda f: env.Object(f, **args) else:
builder = lambda f: env.Object(f, **args)
return [builder(f) for f in scanSrcSubtree(root)] return [builder(f) for f in scanSrcSubtree(root)]

View file

@ -33,7 +33,7 @@ MULTILINE_CPP_IS_BRIEF = NO
DETAILS_AT_TOP = NO DETAILS_AT_TOP = NO
INHERIT_DOCS = YES INHERIT_DOCS = YES
SEPARATE_MEMBER_PAGES = NO SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 8 TAB_SIZE = 4
ALIASES = ALIASES =
OPTIMIZE_OUTPUT_FOR_C = NO OPTIMIZE_OUTPUT_FOR_C = NO
OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_OUTPUT_JAVA = NO
@ -52,7 +52,7 @@ HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO HIDE_UNDOC_CLASSES = NO
HIDE_FRIEND_COMPOUNDS = NO HIDE_FRIEND_COMPOUNDS = NO
HIDE_IN_BODY_DOCS = NO HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO INTERNAL_DOCS = YES
CASE_SENSE_NAMES = YES CASE_SENSE_NAMES = YES
HIDE_SCOPE_NAMES = NO HIDE_SCOPE_NAMES = NO
SHOW_INCLUDE_FILES = YES SHOW_INCLUDE_FILES = YES
@ -74,7 +74,7 @@ FILE_VERSION_FILTER =
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
QUIET = NO QUIET = NO
WARNINGS = YES WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES WARN_IF_UNDOCUMENTED = NO
WARN_IF_DOC_ERROR = YES WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = YES WARN_NO_PARAMDOC = YES
WARN_FORMAT = "$file:$line: $text" WARN_FORMAT = "$file:$line: $text"
@ -82,7 +82,8 @@ WARN_LOGFILE =
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# configuration options related to the input files # configuration options related to the input files
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
INPUT = ../../src/ INPUT = ../../src/ \
../../tests/
FILE_PATTERNS = *.c \ FILE_PATTERNS = *.c \
*.cc \ *.cc \
*.cxx \ *.cxx \

View file

@ -29,6 +29,8 @@
#include "nobugcfg.h" #include "nobugcfg.h"
#undef NOBUG_INIT_DEFS_ #undef NOBUG_INIT_DEFS_
#include <exception>
using util::isnil; using util::isnil;
@ -41,10 +43,10 @@ namespace cinelerra
* Appconfig::instance() probably already has been called * Appconfig::instance() probably already has been called
* by another compilation unit. This is ugliy, but preferable * by another compilation unit. This is ugliy, but preferable
* to beeing dependant on inclusion order of headers. */ * to beeing dependant on inclusion order of headers. */
Appconfig* Appconfig::theApp_ ; // scoped_ptr<Appconfig> Appconfig::theApp_;
#ifndef CINELERRA_VERSION #ifndef CINELERRA_VERSION
#define CINELERRA_VERSION "3++devel" #define CINELERRA_VERSION 3++devel
#endif #endif
@ -55,38 +57,40 @@ namespace cinelerra
*/ */
Appconfig::Appconfig() Appconfig::Appconfig()
: configParam_ (new Configmap) : configParam_ (new Configmap)
{ {
////////// //////////
NOBUG_INIT; NOBUG_INIT;
////////// //////////
INFO(config, "Basic application configuration triggered."); INFO(config, "Basic application configuration triggered.");
(*configParam_)["version"] = CINELERRA_VERSION;
} // 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. /** access the configuation value for a given key.
* @return empty string for unknown keys, else the corresponding configuration value * @return empty string for unknown keys, else the corresponding configuration value
*/ */
const string & const string &
Appconfig::get (const string & key) throw() Appconfig::get (const string & key) throw()
{ {
try
try {
{ const string& val = (*instance().configParam_)[key];
const string& val = (*instance().configParam_)[key]; WARN_IF( isnil(val), config, "undefined config parameter \"%s\" requested.", key.c_str());
WARN_IF( isnil(val), config, "undefined config parameter \"%s\" requested.", key.c_str()); return val;
return val; }
catch (...)
} {
catch (...) ERROR(config, "error while accessing configuration parameter \"%s\".", key.c_str());
{ throw cinelerra::error::Fatal ();
ERROR(config, "error while accessing configuration parameter \"%s\".", key.c_str()); } }
}
}

View file

@ -38,48 +38,50 @@
#include <map> #include <map>
#include <string> #include <string>
#include <memory> #include <boost/scoped_ptr.hpp>
#include "nobugcfg.h" #include "nobugcfg.h"
using std::string;
using std::auto_ptr;
namespace cinelerra namespace cinelerra
{ {
using std::string;
using boost::scoped_ptr;
/** /**
* Singleton to hold inevitable global flags and constants * Singleton to hold inevitable global flags and constants
* and for performing early (static) global initialization tasks. * 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 class Appconfig
{ {
private: private:
/** holds the single instance and triggers initialization */
static Appconfig* theApp_;
/** perform initialization on first access. /** perform initialization on first access.
* A call is placed in static initialization code * @see #instance() for Lifecycle */
* included via cinelerra.h (see below), Appconfig ();
* thus it will happen rather early.
*/ Appconfig (const Appconfig&); ///< copy prohibited, not implemented
Appconfig () ; ~Appconfig () throw() {}; ///< deletion prohibited
friend void boost::checked_delete<Appconfig>(Appconfig*);
public: public:
/** get the (single) Appconfig instance.
* Implemented as Meyers singleton.
* @warning don't use it in destruction code!
*/
static Appconfig& instance() static Appconfig& instance()
{ {
if (!theApp_) theApp_ = new Appconfig (); static scoped_ptr<Appconfig> theApp_ (0);
return *theApp_; if (!theApp_) theApp_.reset (new Appconfig ());
} return *theApp_;
}
/** access the configuation value for a given key. /** access the configuation value for a given key.
* @return empty string for unknown keys, config value else * @return empty string for unknown keys, config value else
* @todo do we need such a facility? * @todo do we need such a facility?
@ -89,10 +91,11 @@ namespace cinelerra
private: private:
typedef std::map<string,string> Configmap; typedef std::map<string,string> Configmap;
typedef auto_ptr<Configmap> PConfig; typedef std::auto_ptr<Configmap> PConfig;
/** @TODO <b>the following is just placeholder code!</b> /** @todo <b>the following is just placeholder code!</b>
* Appconfig <i>could</i> do such things if necessary. * Appconfig <i>could</i> do such things if necessary,
* or provide similar "allways available" services.
*/ */
PConfig configParam_; PConfig configParam_;
@ -100,12 +103,5 @@ namespace cinelerra
namespace
{
/** "magic code" to cause early static initialization */
Appconfig& init (Appconfig::instance ());
}
} // namespace cinelerra } // namespace cinelerra
#endif #endif

85
src/common/cmdline.cpp Normal file
View file

@ -0,0 +1,85 @@
/*
Cmdline - abstraction of the usual commandline, a sequence of strings
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
#include "common/cmdline.hpp"
#include "common/util.hpp"
#include "nobugcfg.h"
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
using boost::algorithm::split;
using boost::algorithm::join;
using boost::algorithm::is_any_of;
using boost::algorithm::token_compress_on;
#include <iostream>
namespace util
{
/** create as a tokenized <i>copy</i> of the current commandline.
* Note that argv[0] is allways ignored. */
Cmdline::Cmdline (int argc, char* argv[])
: vector<string> (noneg(argc-1))
{
for (int i=1; i<argc; ++i)
{
ASSERT (argv[i]);
(*this)[i-1] = argv[i];
}
}
/** create by tokenizing a string
* (e.g. "fake" cmdline, separated by whitespace)
*/
Cmdline::Cmdline (const string cmdline)
{
boost::regex tokendef("[^ \r\n\t]+");
boost::smatch match;
string::const_iterator it = cmdline.begin();
string::const_iterator end = cmdline.end();
while (boost::regex_search(it, end, match, tokendef))
{
string ss(match[0]);
this->push_back(ss);
it = match[0].second;
}
}
/** conversion to string by joining the tokens */
Cmdline::operator string () const
{
return join(*this," ");
}
} // namespace cinelerra

70
src/common/cmdline.hpp Normal file
View file

@ -0,0 +1,70 @@
/*
CMDLINE.hpp - abstraction of the usual commandline, a sequence of strings
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef UTIL_CMDLINE_H
#define UTIL_CMDLINE_H
#include <vector>
#include <string>
#include <iostream>
namespace util
{
using std::string;
using std::vector;
using std::ostream;
typedef vector<string> VectS;
/**
* Abstraction of the usual "int argc, int** argv"-Commandline,
* to be able to treat it as a vector of strings. Inherits from
* vector<std::string>, 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 <class In>
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

View file

@ -21,33 +21,174 @@
* *****************************************************/ * *****************************************************/
#include "common/error.hpp" #include "common/error.hpp"
#include "common/util.hpp"
#include <exception>
#include <typeinfo>
#include <iostream>
using util::isnil;
using std::exception;
namespace cinelerra 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 /** Description of the problem, including the internal char constant
* in accordance to cinelerras error identification scheme. * in accordance to cinelerra's error identification scheme.
* If a ::rootCause() can be obtained, this will be included in the * If a root cause can be obtained, this will be included in the
* generated output as well. * generated output as well.
*/ */
const char* 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, /** @internal get at the description message of the
* return the first one registered in this throw sequence. * first exception encountered in a chain of exceptions
* This works only, if every exceptions thrown as a consequence
* of another exception is propperly constructed by passing
* the original exception to the constructor
*/ */
std::exception const string
Error::rootCause() const throw() Error::extractCauseMsg (const exception& cause) throw()
{
const Error* err=dynamic_cast<const Error*> (&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<Error*> (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 } // namespace cinelerra

View file

@ -21,44 +21,75 @@
*/ */
#ifndef CINELERRA_ERROR_H #ifndef CINELERRA_ERROR_HPP_
#define CINELERRA_ERROR_H #define CINELERRA_ERROR_HPP_
#include <exception> #include <string>
#include "nobugcfg.h"
#include "lib/error.h"
namespace cinelerra namespace cinelerra
{ {
using std::string;
/** error-ID for unspecified exceptions */
CINELERRA_ERROR_DECLARE(EXCEPTION);
/** /**
* Interface and Baseclass of all Exceptions thrown * Interface and Baseclass of all Exceptions thrown
* from within cinelerra (C++) code. Common operations * from within cinelerra (C++) code. Common operations
* for getting an diagnostic message and for obtaining * 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. * in a chain of exceptions.
*/ */
class Error : public std::exception class Error : public std::exception
{ {
public: 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() {}; virtual ~Error () throw() {};
/** yield a diagnostig message characterizing the problem */ /** yield a diagnostic message characterizing the problem */
virtual const char* what () const throw(); 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, /** If this exception was caused by a chain of further exceptions,
* return the first one registered in this throw sequence. * return the description of the first one registered in this throw sequence.
* This works only, if every exceptions thrown as a consequence * This works only if every exceptions thrown as a consequence of another exception
* of another exception is propperly constructed by passing * is propperly constructed by passing the original exception to the constructor
* 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: private:
/** a copy of the first exception encountered in this exception chain */ const char* id_; ///< an CINELERRA_ERROR id, which is set as errorstate on construction
std::exception cause; 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 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-----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);
class State : public Error CINELERRA_EXCEPTION_DECLARE (Invalid, Error, CINELERRA_ERROR_INVALID);
{ CINELERRA_EXCEPTION_DECLARE (External, Error, CINELERRA_ERROR_EXTERNAL);
};
class Invalid : public Error
{
};
class External : public Error
{
};
} // namespace error } // namespace error
} // namespace cinelerra } // 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
#endif // CINELERRA_ERROR_HPP_

View file

@ -60,6 +60,8 @@ namespace cinelerra
* Note: non-virtual. * Note: non-virtual.
*/ */
SMP<T> operator() (){ return SMP<T> (new T ); }; SMP<T> operator() (){ return SMP<T> (new T ); };
typedef SMP<T> ptype;
private: private:
void operator= (const Factory&); // copy prohibited void operator= (const Factory&); // copy prohibited
@ -102,7 +104,7 @@ namespace cinelerra
*/ */
static void destroy (T* victim) { delete victim; }; static void destroy (T* victim) { delete victim; };
public: public:
shared_ptr<T> operator() () { return shared_ptr<T> (new T, &destroy ); } shared_ptr<T> operator() () { return shared_ptr<T> (new T, &destroy ); }
}; };

View file

@ -28,7 +28,8 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include "helper/suite.hpp" #include "common/test/suite.hpp"
#include "common/util.hpp"
namespace test namespace test
@ -37,7 +38,7 @@ namespace test
using std::string; using std::string;
using std::auto_ptr; using std::auto_ptr;
typedef std::vector<string> * Arg; typedef std::vector<string> & Arg;
@ -89,4 +90,10 @@ using ::test::Arg;
using ::test::Test; using ::test::Test;
using ::test::Launch; 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 #endif

213
src/common/test/suite.cpp Normal file
View file

@ -0,0 +1,213 @@
/*
Suite - helper class for running collections of tests
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
#include <map>
#include <vector>
#include <memory>
#include <tr1/memory>
#include <iostream>
#include <sstream>
#include <boost/algorithm/string.hpp>
#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<string, Launcher*> TestMap;
typedef shared_ptr<TestMap> PTestMap;
typedef map<string,PTestMap> 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<GroupMap> 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 \""<<key<<"\" "<<key<<" <<END\n";
Launcher* test = (i->second);
VALID (test, i->first);
(*test)()->run(noCmdline); // run it to insert test generated output
std::cout << "END\n";
}
}
} // namespace test

View file

@ -24,6 +24,7 @@
#ifndef TESTHELPER_SUITE_H #ifndef TESTHELPER_SUITE_H
#define TESTHELPER_SUITE_H #define TESTHELPER_SUITE_H
#include <vector>
#include <string> #include <string>
@ -35,12 +36,16 @@ namespace test
// Forward decls needed for run.hpp // Forward decls needed for run.hpp
class Test; class Test;
class Launcher; class Launcher;
typedef std::vector<string> & 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 class Suite
{ {
@ -48,13 +53,13 @@ namespace test
public: public:
Suite (string groupID); 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 void enroll (Launcher *test, string testID, string groups);
static const string ALLGROUP; static const string ALLGROUP;
}; };
} // namespace test } // namespace test
#endif #endif

View file

@ -0,0 +1,126 @@
/*
Suite - helper class for running collections of tests
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
#include "common/test/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 <groupID>
* \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<string>()->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<VectS>(),
"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<string>();
}
/** @return ID of a single test to run, empty string if not specified
*/
const string
TestOption::getTestID ()
{
if (parameters.count ("id") &&
parameters["id"].as<VectS>().size() > 0)
return parameters["id"].as<VectS>()[0];
else
return string ();
}
/** @return \c true if --describe switch was given */
const bool
TestOption::getDescribe ()
{
return parameters["describe"].as<bool>();
}
ostream&
operator<< (ostream& os, const TestOption& to)
{
return os << to.syntax;
}
} // namespace test

View file

@ -0,0 +1,75 @@
/*
TESTOPTION.hpp - handle cmdline for invoking Testsuite
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef TESTHELPER_TESTOPTION_H
#define TESTHELPER_TESTOPTION_H
#include "common/cmdline.hpp"
#include <string>
#include <iostream>
#include <boost/program_options.hpp>
#include <boost/utility.hpp>
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

View file

@ -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 Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org> 2007, Christian Thaeter <ct@pipapo.org>
@ -33,21 +33,60 @@ namespace util
using std::string; using std::string;
/** a family of util functions providing a "no value whatsoever" test */ /** a family of util functions providing a "no value whatsoever" test.
inline bool isnil(const string& val) Works on strings and all STL containers, includes NULL test for pointers */
{ template <class CONT>
return 0 == val.length(); inline bool isnil(const CONT& container)
} {
return container.empty();
inline bool isnil(const string* pval) }
{
return !pval || 0 == pval->length(); template <class CONT>
} inline bool isnil(const CONT* pContainer)
{
inline bool isnil(const char* pval) return !pContainer || pContainer->empty();
{ }
return !pval || 0 == std::strlen(pval);
} template <>
inline bool isnil(const char* pCStr)
{
return !pCStr || 0 == std::strlen(pCStr);
}
/** cut a numeric value to be >=0 */
template <typename NUM>
inline NUM noneg (NUM val)
{
return (0<val? val : 0);
}
/** shortcut for containment test on a map */
template <typename MAP>
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 <typename Container, typename Oper>
inline Oper
for_each (Container& c, Oper& doIt)
{
return std::for_each (c.begin(),c.end(), doIt);
}
} // namespace util } // 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_*/ #endif /*UTIL_HPP_*/

View file

@ -21,6 +21,12 @@
#ifndef CINELERRA_ERROR_H #ifndef CINELERRA_ERROR_H
#define CINELERRA_ERROR_H #define CINELERRA_ERROR_H
#ifdef __cplusplus
extern "C" {
#elif 0
} /*eek, fixes emacs indenting for now*/
#endif
#include <nobug.h> #include <nobug.h>
#include <stdlib.h> #include <stdlib.h>
@ -78,5 +84,7 @@ cinelerra_error_set (const char * err);
const char* const char*
cinelerra_error (); cinelerra_error ();
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* CINELERRA_ERROR_H */ #endif /* CINELERRA_ERROR_H */

View file

@ -31,9 +31,9 @@ using cinelerra::Appconfig;
int main (int argc, char* argv[]) int main (int argc, char* argv[])
{ {
cout << "*** Cinelerra NLE for Linux ***" << endl cout << "*** Cinelerra NLE for Linux ***" << endl
<< " Version: " << Appconfig::get("version") << endl; << " Version: " << Appconfig::get("version") << endl;
assert(true); assert(true);
return 0; return 0;
} }

View file

@ -75,7 +75,8 @@
NOBUG_CPP_DEFINE_FLAG(config); NOBUG_CPP_DEFINE_FLAG(config);
NOBUG_CPP_DEFINE_FLAG(test); NOBUG_CPP_DEFINE_FLAG(test);
#include "common/error.hpp"
#endif /* ===================== (End) C++-Part ============= */ #endif /* ===================== (End) C++-Part ============= */

1
tests/.gitignore vendored
View file

@ -1,4 +1,5 @@
,* ,*
test-*
mainsuite mainsuite
errortest errortest
plugin-example plugin-example

View file

@ -1,12 +1,107 @@
TESTING "Component Test Suite: ALL" ./test-components
TESTING "Component Test Suite" ./test-components
TEST "Fac test" Factory_test <<END
return: 0
END
TEST "Hello test" HelloWorld_test <<END
TEST "Hello test" HelloWorld_test 3 <<END
out: This is how the world ends...
out: This is how the world ends...
out: This is how the world ends... out: This is how the world ends...
return: 0 return: 0
END END
TEST "Appconfig_test" Appconfig_test <<END
return: 0
END
TEST "CmdlineWrapper_test" CmdlineWrapper_test <<END
out: wrapping cmdline:...
out: -->
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 <<END
out: caught: CINELERRA_ERROR_LIFE_AND_UNIVERSE:and everything? (don't panic)...the answer is: 42
out: caught cinelerra::Error: CINELERRA_ERROR_DERIVED:convoluted exception
out: caught error::Logic: CINELERRA_ERROR_FATAL:floundered (test-2).
out: caught error::Invalid: CINELERRA_ERROR_INVALID:invalid input or parameters (test-3).
out: caught cinelerra::Error: CINELERRA_ERROR_EXTERNAL:failure in external service (test-4).
out: caught std::runtime_error: test-5
out: caught std::exception: St9exception
out: intermediate handler caught: CINELERRA_ERROR_EXTERNAL:failure in external service (test-7).....will rethrow as error::State
out: caught cinelerra::Error: CINELERRA_ERROR_STATE:unforseen state -- caused by: CINELERRA_ERROR_EXTERNAL:failure in external service (test-7).
out: intermediate handler caught: CINELERRA_ERROR_EXTERNAL:failure in external service (test-8).....will rethrow as error::State
out: 2nd intermediate handler caught: CINELERRA_ERROR_STATE:unforseen state -- caused by: CINELERRA_ERROR_EXTERNAL:failure in external service (test-8).....will rethrow as error::Config
out: caught cinelerra::Error: CINELERRA_ERROR_CONFIG:misconfiguration -- caused by: CINELERRA_ERROR_EXTERNAL:failure in external service (test-8).
END
TEST "Factory_test" Factory_test 5 <<END
out: ctor TargetObj(5) successfull
out: now the smart-ptr has refcount=3
out: .....TargetObj(5): data="*****", array[5]={0,1,2,3,4,}
out: dtor ~TargetObj(5) successfull
END
TEST "TestOption_test" TestOption_test <<END
out: Testing invocation with cmdline: ...
out: --> 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

View file

@ -26,8 +26,13 @@ def treatPluginTestcase(env):
""" Special case: the test-plugin executable """ Special case: the test-plugin executable
""" """
env = env.Clone() env = env.Clone()
env.Append(CPPPATH='plugin') env.Append(CPPPATH='plugin')
testplugin = env.SharedLibrary('hello_1', 'plugin/example_plugin.c', SHLIBPREFIX='') 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) testExe = env.Program('test-plugin', ['plugin/plugin_main.c'] + corelib)
env.Depends(testExe, testplugin) env.Depends(testExe, testplugin)
return testExe return testExe
@ -46,7 +51,6 @@ moduledirs = globRootdirs('*')
isnt_plugin = lambda dir : dir!='plugin' isnt_plugin = lambda dir : dir!='plugin'
moduledirs = filter(isnt_plugin, moduledirs) moduledirs = filter(isnt_plugin, moduledirs)
pluginExe = treatPluginTestcase(env) pluginExe = treatPluginTestcase(env)
print 'moduledirs: %s' %moduledirs
artifacts['testsuite'] = ts = [ SingleTestExecutableSubdir(env, dir) for dir in moduledirs] + pluginExe 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 # - it depends on all artifacts defined as "ts" above
# #
runTs = env.Command(',testlog', ts, "./test.sh", chdir=1) 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('testcode', ts )
env.Alias('check', runTs ) env.Alias('check', runTs )
# declare tempfiles of test.sh as cleanable
env.Clean ('check', [',testlog.pre',',expect_stdout',',stdout',',stderr',',testtmp','.libs'])

View file

@ -0,0 +1,64 @@
/*
Appconfig(Test) - accessing the allwasy-available Appconfig singleton
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
#include "nobugcfg.h"
#include "common/appconfig.hpp"
#include "common/test/run.hpp"
#include "common/util.hpp"
#include <iostream>
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

View file

@ -0,0 +1,239 @@
/*
Exceptionhandlin(Test) - throwing and catching our exception type
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
#include "lib/error.h"
#include "common/error.hpp"
#include "common/appconfig.hpp"
#include "common/test/run.hpp"
#include "common/util.hpp"
#include <string>
#include <iostream>
#include <exception>
#include <stdexcept>
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

View file

@ -21,7 +21,21 @@
* *****************************************************/ * *****************************************************/
#include "helper/run.hpp" #include "common/test/run.hpp"
#include "common/factory.hpp"
#include "common/util.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
#include <iostream>
using boost::algorithm::join;
using boost::lexical_cast;
using boost::format;
using util::isnil;
using std::string;
using std::cout;
namespace cinelerra namespace cinelerra
@ -29,23 +43,122 @@ namespace cinelerra
namespace test 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 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<cnt_; ++i)
heapArray_[i] = lexical_cast<string>(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: public:
static ObjFactory create;
operator string () const
{
string array_contents = "{";
for (uint i=0; i<cnt_; ++i)
array_contents += heapArray_[i]+",";
array_contents+="}";
return str (format(".....TargetObj(%1%): data=\"%2%\", array[%1%]=%3%")
% cnt_
% *heapData_
% array_contents
);
}
}; };
/** Test-Factory specialized to create TargetObj instances
* using the 1-argument constructor TargetObj::TargetObj(int).
* It will create boost::shared_ptr instances, because
* factory::RefcountPtr was parametrized with this smart pointer type.
* @note ObjFactory can use the private constructor because it's a friend.
*/
class ObjFactory : public factory::RefcountPtr<TargetObj>
{
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 class Factory_test : public Test
{ {
virtual void run(Arg arg) virtual void run(Arg arg)
{ {
uint num= isnil(arg)? 1 : lexical_cast<uint>(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<Factory_test> run_Factory_test("Factory_test","unit common"); /** Register this test class... */
LAUNCHER (Factory_test, "unit common");

View file

@ -0,0 +1,100 @@
/*
Cmdlinewrapper(Test) - build vector of tokens from cmdline, various conversions
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
#include "nobugcfg.h"
#include "common/test/run.hpp"
#include "common/cmdline.hpp"
#include "common/util.hpp"
#include <iostream>
#include <sstream>
#include <boost/lambda/lambda.hpp>
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

View file

@ -0,0 +1,83 @@
/*
TestOption(Test) - parsing of cmd line options for running Testcases
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
#include <iostream>
#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

View file

@ -22,7 +22,13 @@
#include <iostream> #include <iostream>
#include "helper/run.hpp" #include "common/test/run.hpp"
#include "common/util.hpp"
using util::isnil;
#include <boost/lexical_cast.hpp>
using boost::lexical_cast;
namespace cinelerra namespace cinelerra
@ -30,18 +36,24 @@ namespace cinelerra
namespace test namespace test
{ {
/******************************************
* Helooooooo to the world of TDD
* @test demo of using the test framework
*/
class HelloWorld_test : public Test class HelloWorld_test : public Test
{ {
virtual void run(Arg arg) virtual void run(Arg arg)
{ {
int num= isnil(arg)? 1 : lexical_cast<int> (arg[1]);
for ( ; 0 < num-- ; )
greeting(); greeting();
} }
void 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) */ /** Register this test class to be invoked in some test groups (suites) */
Launch<HelloWorld_test> run_HelloWorld_test("HelloWorld_test","unit common"); Launch<HelloWorld_test> run_HelloWorld_test("HelloWorld_test","unit common");
// NOTE: you may use the Macro "LAUNCHER" in run.hpp to simplify this Registration
} // namespace test } // namespace test

View file

@ -1,182 +0,0 @@
/*
Suite - helper class for running collections of tests
Copyright (C) CinelerraCV
2007, Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
#include <map>
#include <vector>
#include <memory>
#include <tr1/memory>
#include <iostream>
#include <sstream>
#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<string, Launcher*> TestMap;
typedef shared_ptr<TestMap> PTestMap;
typedef map<string,PTestMap> 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<GroupMap> 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<string> arglist(argc-2);
for ( int i=2; i<argc; ++i )
arglist.push_back(string(argv[i]));
// run single Testcase with provided arguments
(*test)()->run(&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

View file

@ -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 /** run all tests or any single test specified in the first
* cmd line argument. * cmd line argument.
* Note: to ease debugging, we don't catch any exceptions. * Note: to ease debugging, we don't catch any exceptions.
*/ */
int main (int argc, char* argv[]) int main (int argc, char* argv[])
{ {
test::Suite suite (test::Suite::ALLGROUP); util::Cmdline args (argc,argv);
suite.run(argc,argv); test::TestOption optparser (args);
return 0; test::Suite suite (optparser.getTestgroup());
}
if (optparser.getDescribe())
suite.describe();
else
suite.run (args);
return 0;
}

View file

@ -747,14 +747,19 @@ config.macros.timeline.handler = function(place,macroName,params,wikifier,paramS
} }
//}}}</pre> //}}}</pre>
</div> </div>
<div title="BuildDependenceis" modifier="Ichthyostega" modified="200708140618" created="200708120103" tags="organization buildsys" changecount="5"> <div title="BuildDependenceis" modifier="Ichthyostega" modified="200708210341" created="200708120103" tags="organization buildsys" changecount="7">
<pre>for __Building__ <pre>for __Building__
* gcc (4.1), glibc6 (2.3), libstdc++6 (4.1) * gcc (4.1), glibc6 (2.3), libstdc++6 (4.1)
* [[build system|BuildSystem]] dependencies: SCons (0.96.90), Python (2.3) * [[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 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 (&amp;rarr; glibc) * ~NoBug needs [[valgrind|Valgrind]] (3.2), execinfo.h and libpthread (&amp;rarr; glibc)
* std::tr1 &amp;mdash; esp. for the former BOOST::shared_ptr (which is now proposed standard) * std::tr1 &amp;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// //usually, newer versions are OK//
for __Running__</pre> for __Running__</pre>
@ -3549,7 +3554,7 @@ Here is the order suggested:
</pre> </pre>
</div> </div>
<div title="TestSuite" modifier="Ichthyostega" modified="200708180218" created="200708120254" tags="buildsys organization testsuite" changecount="11"> <div title="TestSuite" modifier="Ichthyostega" modified="200708231706" created="200708120254" tags="buildsys organization testsuite" changecount="16">
<pre>For running the automatic Tests, we use Cehteh's simple [[test.sh|TestSh]]. <pre>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. 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. * 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 * 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 &lt;iostream&gt;
#include &quot;common/test/run.hpp&quot;
class HelloWorld_test : public Test
{
virtual void run(Arg arg)
{
greeting();
}
void greeting()
{
std::cout &lt;&lt; &quot;This is how the world ends...\n&quot;;
}
};
/** Register this test class to be invoked in some test groups (suites) */
LAUNCHER (HelloWorld_test, &quot;unit function common&quot;);
}}}
Notes:
* type Arg is {{{typedef std::vector&lt;string&gt; &amp; 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 &quot;LAUNCHER&quot; expands to {{{Launch&lt;HelloWorld_test&gt; run_HelloWorld_test(&quot;HelloWorld_test&quot;,&quot;unit function common&quot;);}}}
* 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:
|&gt;|!{{{./test-components --group &lt;groupID&gt; [testID [arguments...]]}}}|
|{{{--help}}}| options summary|
|{{{--group|-g &lt;groupID&gt;}}}| 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 !conventions for the Buildsystem
to help with automating the build, ichthyo would appreciate to have the following conventions. to help with automating the build, ichthyo would appreciate to have the following conventions.
* in the {{{tests}}} directory are * in the {{{tests}}} directory are

View file

@ -707,7 +707,7 @@ The next point is allocation failures. These are possible by C/C++ standard but
</pre> </pre>
</div> </div>
<div title="ErrorHandling-Cpp" modifier="Ichthyostega" created="200708140640" changecount="1"> <div title="ErrorHandling-Cpp" modifier="Ichthyostega" modified="200708290301" created="200708140640" changecount="3">
<pre>Basically, the C++ error handling techniques are layered on top of the [[C solution|ErrorHandling-C]]. <pre>Basically, the C++ error handling techniques are layered on top of the [[C solution|ErrorHandling-C]].
!Proposal: !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 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 !!Requirements for the Exception Interface
* a means for capturing and transporting detail informations * 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 * 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 &quot;exceptionerrortest.cpp&quot;
* 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 |
</pre> </pre>
</div> </div>
<div title="FrameAndTime" modifier="CehTeh" modified="200708282024" created="200708261938" changecount="2"> <div title="FrameAndTime" modifier="CehTeh" modified="200708282024" created="200708261938" changecount="2">