LUMIERA.clone/tests/lib/exceptionerrortest.cpp

234 lines
8.4 KiB
C++

/*
ExceptionError(Test) - throwing and catching our exception type
Copyright (C) Lumiera.org
2008, Hermann Vosseler <Ichthyostega@web.de>
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 "lib/error.hpp"
#include "lib/test/run.hpp"
#include "lib/util.hpp"
#include <string>
#include <iostream>
#include <exception>
#include <stdexcept>
using std::runtime_error;
using std::exception;
using std::string;
using std::cout;
namespace lumiera {
namespace test {
/** local specific error-constant for use in the
* constructor of the nested SpecificError class.
*/
LUMIERA_ERROR_DEFINE(LIFE_AND_UNIVERSE, "and everything?");
LUMIERA_ERROR_DEFINE(DERIVED, "convoluted exception");
/** declare a specific Error class with parent class error::external */
LUMIERA_EXCEPTION_DECLARE (DerivedError, error::External, LUMIERA_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(); }
/** @test catching, repackaging and rethrowing of errors.
* This feature is important for passing exceptions transparently
* 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 lumiera::Error object,
* the corresponding lumiera_error state is set automatically
*/
void checkErrorIntegration()
{
lumiera_error ();
ASSERT (!lumiera_error ());
Error err1;
Error err2("boo",LUMIERA_ERROR_DERIVED);
ASSERT (err1.getID () == lumiera_error ()); // (we didn't clear the first one!)
Error err3("boooo",LUMIERA_ERROR_DERIVED);
ASSERT (err3.getID () == lumiera_error ());
SpecificError err4;
ASSERT (err4.getID () == LUMIERA_ERROR_LIFE_AND_UNIVERSE);
ASSERT (err4.getID () == lumiera_error ());
ASSERT (!lumiera_error ());
}
/** @test the chaining of lumiera::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 undeclared exception.
* this should result in the global unknown() handler to be called,
* so usually it will terminate the test run.
* @note inside error.hpp, an initialisation hook has been installed into
* AppState, causing our own unknown() handler to be installed and
* invoked, which gives additional diagnostics.*/
void terminateUnknown () throw()
{
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",LUMIERA_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 lumiera::Error: " << e.what() << "\n"; }
catch (runtime_error& e) { cout << "caught std::runtime_error: " << e.what() << "\n"; }
catch (exception& e) { cout << "caught std::exception. (unspecific)" << "\n"; }
catch (...) { cout << "caught an unknown exception\n"; }
}
};
/** register this test class... */
LAUNCHER (ExceptionError_test, "function common");
} // namespace test
} // namespace util