While seemingly subtle, this is a ''deep change.'' Up to now, the project attempted to maintain two mutually disjoint systems of error reporting: C-style error flags and C++ exceptions. Most notably, an attempt was made to keep both error states synced. During the recent integration efforts, this increasingly turned out as an obstacle and source for insidious problems (like deadlocks). As a resolve, hereby the relation of both systems is **clarified**: * C-style error flags shall only be set and used by C code henceforth * C++ exceptions can (optionally) be thrown by retrieving the C-style error code * but the opposite is now ''discontinued'' : Exceptions ''do not set'' the error flag anymore
225 lines
7.7 KiB
C++
225 lines
7.7 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.
|
|
|
|
* *****************************************************/
|
|
|
|
/** @file exception-error-test.cpp
|
|
** unit test \ref ExceptionError_test
|
|
*/
|
|
|
|
|
|
|
|
#include "lib/error.h"
|
|
#include "lib/error.hpp"
|
|
#include "lib/test/run.hpp"
|
|
#include "lib/format-cout.hpp"
|
|
#include "lib/util.hpp"
|
|
|
|
#include <exception>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
using std::runtime_error;
|
|
using std::exception;
|
|
using std::string;
|
|
|
|
|
|
|
|
namespace lumiera {
|
|
namespace test {
|
|
|
|
LUMIERA_ERROR_DEFINE (LIFE_AND_UNIVERSE, "and everything?");
|
|
/** local specific error-constant for use in the
|
|
* constructor of the nested SpecificError class.
|
|
*/
|
|
LUMIERA_ERROR_DECLARE(DERIVED);
|
|
LUMIERA_ERROR_DEFINE (DERIVED, "convoluted exception");
|
|
|
|
/** define a specific Error subclass derived from error::external */
|
|
using DerivedError = error::LumieraError<LERR_(DERIVED), error::External>;
|
|
|
|
|
|
/******************************************************//**
|
|
* 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 vault.
|
|
* Both approaches are largely 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)
|
|
{
|
|
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");
|
|
|
|
checkErrorFlagPropagation();
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
try { nestedThrower (msg); }
|
|
catch (Error& e)
|
|
{
|
|
cout << "2nd intermediate handler caught: " << e.what()
|
|
<< "....will rethrow as error::Config\n";
|
|
throw error::Config (e);
|
|
}
|
|
}
|
|
|
|
|
|
void detectErrorflag (string) { throwOnError(); }
|
|
|
|
|
|
/** @test verify throwing of Exceptions
|
|
* based on a non-cleared C error flag
|
|
*/
|
|
void checkErrorFlagPropagation()
|
|
{
|
|
lumiera_error_set(LERR_(LIFE_AND_UNIVERSE), "what is the answer?");
|
|
CHECK (lumiera_error_peek());
|
|
|
|
catcher (&test::detectErrorflag);
|
|
CHECK (not lumiera_error_peek());
|
|
}// yet translating that into an exception also clears the error flag
|
|
|
|
|
|
/** @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);
|
|
Error err4(err1); // note: copy ctor
|
|
|
|
std::runtime_error rerr("what a shame");
|
|
error::External err5(rerr);
|
|
Error err6(err5);
|
|
|
|
CHECK (err2.rootCause() == err1.what());
|
|
CHECK (err3.rootCause() == err1.what());
|
|
CHECK (err4.rootCause() == err1.rootCause()); // mere copy is not a root cause
|
|
|
|
CHECK (err5.rootCause() == rerr.what());
|
|
CHECK (err6.rootCause() == rerr.what());
|
|
}
|
|
|
|
|
|
/** a very specific Exception class
|
|
* local to this scope and with
|
|
* additional behaviour.
|
|
*/
|
|
class SpecificError
|
|
: public error::Invalid
|
|
{
|
|
int value_;
|
|
public:
|
|
SpecificError()
|
|
: error::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
|
|
|