LUMIERA.clone/tests/library/exception-error-test.cpp
Ichthyostega a90b9e5f16 Library: uniform definition scheme for error-IDs
In the Lumiera code base, we use C-String constants as unique error-IDs.
Basically this allows to create new unique error IDs anywhere in the code.

However, definition of such IDs in arbitrary namespaces tends to create
slight confusion and ambiguities, while maintaining the proper use statements
requires some manual work.

Thus I introduce a new **standard scheme**
 * Error-IDs for widespread use shall be defined _exclusively_ into `namespace lumiera::error`
 * The shorthand-Macro `LERR_()` can now be used to simplify inclusion and referral
 * (for local or single-usage errors, a local or even hidden definition is OK)
2024-03-21 19:57:34 +01:00

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<LUMIERA_ERROR_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(LUMIERA_ERROR_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