324 lines
12 KiB
C++
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
|