phoenix-firestorm/indra/llcommon/tests/llexception_test.cpp

324 lines
12 KiB
C++

/**
* @file llexception_test.cpp
* @author Nat Goodspeed
* @date 2016-08-12
* @brief Tests for throwing exceptions.
*
* This isn't a regression test: it doesn't need to be run every build, which
* is why the corresponding line in llcommon/CMakeLists.txt is commented out.
* Rather it's a head-to-head test of what kind of exception information we
* can collect from various combinations of exception base classes, type of
* throw verb and sequences of catch clauses.
*
* This "test" makes no ensure() calls: its output goes to stdout for human
* examination.
*
* As of 2016-08-12 with Boost 1.57, we come to the following conclusions.
* These should probably be re-examined from time to time as we update Boost.
*
* - It is indisputably beneficial to use BOOST_THROW_EXCEPTION() rather than
* plain throw. The macro annotates the exception object with the filename,
* line number and function name from which the exception was thrown.
*
* - That being the case, deriving only from boost::exception isn't an option.
* Every exception object passed to BOOST_THROW_EXCEPTION() must be derived
* directly or indirectly from std::exception. The only question is whether
* to also derive from boost::exception. We decided to derive LLException
* from both, as it makes message output slightly cleaner, but this is a
* trivial reason: if a strong reason emerges to prefer single inheritance,
* dropping the boost::exception base class shouldn't be a problem.
*
* - (As you will have guessed, ridiculous things like a char* or int or a
* class derived from neither boost::exception nor std::exception can only
* be caught by that specific type or (...), and
* boost::current_exception_diagnostic_information() simply throws up its
* hands and confesses utter ignorance. Stay away from such nonsense.)
*
* - But if you derive from std::exception, to nat's surprise,
* boost::current_exception_diagnostic_information() gives as much
* information about exceptions in a catch (...) clause as you can get from
* a specific catch (const std::exception&) clause, notably the concrete
* exception class and the what() string. So instead of a sequence like
*
* try { ... }
* catch (const boost::exception& e) { ... boost-flavored logging ... }
* catch (const std::exception& e) { ... std::exception logging ... }
* catch (...) { ... generic logging ... }
*
* we should be able to get away with only a catch (...) clause that logs
* boost::current_exception_diagnostic_information().
*
* - Going further: boost::current_exception_diagnostic_information() provides
* just as much information even within a std::set_terminate() handler. So
* it might not even be strictly necessary to include a catch (...) clause
* since the viewer does use std::set_terminate().
*
* - (We might consider adding a catch (int) clause because Kakadu internally
* throws ints, and who knows if one of those might leak out. If it does,
* boost::current_exception_diagnostic_information() can do nothing with it.
* A catch (int) clause could at least log the value and rethrow.)
*
* $LicenseInfo:firstyear=2016&license=viewerlgpl$
* Copyright (c) 2016, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "llexception.h"
// STL headers
// std headers
#include <typeinfo>
// external library headers
#include <boost/throw_exception.hpp>
// other Linden headers
#include "../test/lltut.h"
// helper for display output
// usage: std::cout << center(some string value, fill char, width) << std::endl;
// (assumes it's the only thing on that particular line)
struct center
{
center(const std::string& label, char fill, std::size_t width):
mLabel(label),
mFill(fill),
mWidth(width)
{}
// Use friend declaration not because we need to grant access, but because
// it lets us declare a free operator like a member function.
friend std::ostream& operator<<(std::ostream& out, const center& ctr)
{
std::size_t padded = ctr.mLabel.length() + 2;
std::size_t left = (ctr.mWidth - padded) / 2;
std::size_t right = ctr.mWidth - left - padded;
return out << std::string(left, ctr.mFill) << ' ' << ctr.mLabel << ' '
<< std::string(right, ctr.mFill);
}
std::string mLabel;
char mFill;
std::size_t mWidth;
};
/*****************************************************************************
* Four kinds of exceptions: derived from boost::exception, from
* std::exception, from both, from neither
*****************************************************************************/
// Interestingly, we can't use this variant with BOOST_THROW_EXCEPTION()
// (which we want) -- we reach a failure topped by this comment:
// //All boost exceptions are required to derive from std::exception,
// //to ensure compatibility with BOOST_NO_EXCEPTIONS.
struct FromBoost: public boost::exception
{
FromBoost(const std::string& what): mWhat(what) {}
~FromBoost() throw() {}
std::string what() const { return mWhat; }
std::string mWhat;
};
struct FromStd: public std::runtime_error
{
FromStd(const std::string& what): std::runtime_error(what) {}
};
struct FromBoth: public boost::exception, public std::runtime_error
{
FromBoth(const std::string& what): std::runtime_error(what) {}
};
// Same deal with FromNeither: can't use with BOOST_THROW_EXCEPTION().
struct FromNeither
{
FromNeither(const std::string& what): mWhat(what) {}
std::string what() const { return mWhat; }
std::string mWhat;
};
/*****************************************************************************
* Two kinds of throws: plain throw and BOOST_THROW_EXCEPTION()
*****************************************************************************/
template <typename EXC>
void plain_throw(const std::string& what)
{
throw EXC(what);
}
template <typename EXC>
void boost_throw(const std::string& what)
{
BOOST_THROW_EXCEPTION(EXC(what));
}
// Okay, for completeness, functions that throw non-class values. We wouldn't
// even deign to consider these if we hadn't found examples in our own source
// code! (Note that Kakadu's internal exception support is still based on
// throwing ints.)
void throw_char_ptr(const std::string& what)
{
throw what.c_str(); // umm...
}
void throw_int(const std::string& what)
{
throw int(what.length());
}
/*****************************************************************************
* Three sequences of catch clauses:
* boost::exception then ...,
* std::exception then ...,
* or just ...
*****************************************************************************/
void catch_boost_dotdotdot(void (*thrower)(const std::string&), const std::string& what)
{
try
{
thrower(what);
}
catch (const boost::exception& e)
{
std::cout << "catch (const boost::exception& e)" << std::endl;
std::cout << "e is " << typeid(e).name() << std::endl;
std::cout << "boost::diagnostic_information(e):\n'"
<< boost::diagnostic_information(e) << "'" << std::endl;
// no way to report e.what()
}
catch (...)
{
std::cout << "catch (...)" << std::endl;
std::cout << "boost::current_exception_diagnostic_information():\n'"
<< boost::current_exception_diagnostic_information() << "'"
<< std::endl;
}
}
void catch_std_dotdotdot(void (*thrower)(const std::string&), const std::string& what)
{
try
{
thrower(what);
}
catch (const std::exception& e)
{
std::cout << "catch (const std::exception& e)" << std::endl;
std::cout << "e is " << typeid(e).name() << std::endl;
std::cout << "boost::diagnostic_information(e):\n'"
<< boost::diagnostic_information(e) << "'" << std::endl;
std::cout << "e.what: '"
<< e.what() << "'" << std::endl;
}
catch (...)
{
std::cout << "catch (...)" << std::endl;
std::cout << "boost::current_exception_diagnostic_information():\n'"
<< boost::current_exception_diagnostic_information() << "'"
<< std::endl;
}
}
void catch_dotdotdot(void (*thrower)(const std::string&), const std::string& what)
{
try
{
thrower(what);
}
catch (...)
{
std::cout << "catch (...)" << std::endl;
std::cout << "boost::current_exception_diagnostic_information():\n'"
<< boost::current_exception_diagnostic_information() << "'"
<< std::endl;
}
}
/*****************************************************************************
* Try a particular kind of throw against each of three catch sequences
*****************************************************************************/
void catch_several(void (*thrower)(const std::string&), const std::string& what)
{
std::cout << std::string(20, '-') << "catch_boost_dotdotdot(" << what << ")" << std::endl;
catch_boost_dotdotdot(thrower, "catch_boost_dotdotdot(" + what + ")");
std::cout << std::string(20, '-') << "catch_std_dotdotdot(" << what << ")" << std::endl;
catch_std_dotdotdot(thrower, "catch_std_dotdotdot(" + what + ")");
std::cout << std::string(20, '-') << "catch_dotdotdot(" << what << ")" << std::endl;
catch_dotdotdot(thrower, "catch_dotdotdot(" + what + ")");
}
/*****************************************************************************
* For a particular kind of exception, try both kinds of throw against all
* three catch sequences
*****************************************************************************/
template <typename EXC>
void catch_both_several(const std::string& what)
{
std::cout << std::string(20, '*') << "plain_throw<" << what << ">" << std::endl;
catch_several(plain_throw<EXC>, "plain_throw<" + what + ">");
std::cout << std::string(20, '*') << "boost_throw<" << what << ">" << std::endl;
catch_several(boost_throw<EXC>, "boost_throw<" + what + ">");
}
/*****************************************************************************
* TUT
*****************************************************************************/
namespace tut
{
struct llexception_data
{
};
typedef test_group<llexception_data> llexception_group;
typedef llexception_group::object object;
llexception_group llexceptiongrp("llexception");
template<> template<>
void object::test<1>()
{
set_test_name("throwing exceptions");
// For each kind of exception, try both kinds of throw against all
// three catch sequences
std::size_t margin = 72;
std::cout << center("FromStd", '=', margin) << std::endl;
catch_both_several<FromStd>("FromStd");
std::cout << center("FromBoth", '=', margin) << std::endl;
catch_both_several<FromBoth>("FromBoth");
std::cout << center("FromBoost", '=', margin) << std::endl;
// can't throw with BOOST_THROW_EXCEPTION(), just use catch_several()
catch_several(plain_throw<FromBoost>, "plain_throw<FromBoost>");
std::cout << center("FromNeither", '=', margin) << std::endl;
// can't throw this with BOOST_THROW_EXCEPTION() either
catch_several(plain_throw<FromNeither>, "plain_throw<FromNeither>");
std::cout << center("const char*", '=', margin) << std::endl;
// We don't expect BOOST_THROW_EXCEPTION() to throw anything so daft
// as a const char* or an int, so don't bother with
// catch_both_several() -- just catch_several().
catch_several(throw_char_ptr, "throw_char_ptr");
std::cout << center("int", '=', margin) << std::endl;
catch_several(throw_int, "throw_int");
}
template<> template<>
void object::test<2>()
{
set_test_name("reporting exceptions");
try
{
LLTHROW(LLException("badness"));
}
catch (...)
{
LOG_UNHANDLED_EXCEPTION("llexception test<2>()");
}
}
} // namespace tut