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