247 lines
8.6 KiB
C++
247 lines
8.6 KiB
C++
/*
|
|
* Copyright (C) 2022-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
|
*/
|
|
|
|
// Common definitions of test cases used in
|
|
// handle_exception_optimized_test.cc
|
|
// handle_exception_fallback_test.cc
|
|
|
|
#include <exception>
|
|
#include <stdexcept>
|
|
#include <type_traits>
|
|
#include <cxxabi.h>
|
|
#include <boost/test/unit_test_log.hpp>
|
|
#include <boost/test/tools/old/interface.hpp>
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
#include "seastarx.hh"
|
|
#include <seastar/core/future.hh>
|
|
#include <seastar/testing/test_case.hh>
|
|
#include <seastar/util/log.hh>
|
|
|
|
#include "utils/assert.hh"
|
|
#include "utils/exceptions.hh"
|
|
|
|
class base_exception : public std::exception {};
|
|
class derived_exception : public base_exception {};
|
|
|
|
static void dummy_fn(void*) {
|
|
//
|
|
}
|
|
|
|
template<typename T>
|
|
static std::exception_ptr maybe_wrap_eptr(T&& t) {
|
|
if constexpr (std::is_same_v<T, std::exception_ptr>) {
|
|
return std::move(t);
|
|
} else {
|
|
return std::make_exception_ptr(std::move(t));
|
|
}
|
|
}
|
|
|
|
static const std::type_info& eptr_typeid(std::exception_ptr eptr) {
|
|
try {
|
|
std::rethrow_exception(eptr);
|
|
} catch (...) {
|
|
return *abi::__cxa_current_exception_type();
|
|
}
|
|
}
|
|
|
|
template<typename Capture, typename Throw>
|
|
static void check_catch(Throw&& ex) {
|
|
auto eptr = maybe_wrap_eptr(std::move(ex));
|
|
BOOST_TEST_MESSAGE("Checking if " << seastar::pretty_type_name(eptr_typeid(eptr))
|
|
<< " is caught as " << seastar::pretty_type_name(typeid(Capture)));
|
|
|
|
auto typed_eptr = try_catch<Capture>(eptr);
|
|
BOOST_CHECK_NE(typed_eptr, nullptr);
|
|
|
|
// Verify that it's the same as what the usual throwing gives
|
|
// TODO: Does this check make sense? Does the standard guarantee
|
|
// that this will give the same pointer?
|
|
try {
|
|
std::rethrow_exception(eptr);
|
|
} catch (Capture& t) {
|
|
BOOST_CHECK_EQUAL(typed_eptr, &t);
|
|
} catch (...) {
|
|
// Can happen if the first check fails, just skip
|
|
SCYLLA_ASSERT(typed_eptr == nullptr);
|
|
}
|
|
}
|
|
|
|
template<typename Capture, typename Throw>
|
|
static void check_no_catch(Throw&& ex) {
|
|
auto eptr = maybe_wrap_eptr(std::move(ex));
|
|
BOOST_TEST_MESSAGE("Checking if " << seastar::pretty_type_name(eptr_typeid(eptr))
|
|
<< " is NOT caught as " << seastar::pretty_type_name(typeid(Capture)));
|
|
|
|
auto typed_eptr = try_catch<Capture>(eptr);
|
|
BOOST_CHECK_EQUAL(typed_eptr, nullptr);
|
|
}
|
|
|
|
template<typename A, typename B>
|
|
static std::exception_ptr make_nested_eptr(A&& a, B&& b) {
|
|
try {
|
|
throw std::move(b);
|
|
} catch (...) {
|
|
try {
|
|
std::throw_with_nested(std::move(a));
|
|
} catch (...) {
|
|
return std::current_exception();
|
|
}
|
|
}
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_try_catch) {
|
|
// Some standard examples, throwing exceptions derived from std::exception
|
|
// and catching them through their base types
|
|
|
|
check_catch<derived_exception>(derived_exception());
|
|
check_catch<base_exception>(derived_exception());
|
|
check_catch<std::exception>(derived_exception());
|
|
check_no_catch<std::runtime_error>(derived_exception());
|
|
|
|
check_no_catch<derived_exception>(base_exception());
|
|
check_catch<base_exception>(base_exception());
|
|
check_catch<std::exception>(base_exception());
|
|
check_no_catch<std::runtime_error>(base_exception());
|
|
|
|
// Catching nested exceptions
|
|
check_catch<base_exception>(make_nested_eptr(base_exception(), derived_exception()));
|
|
check_catch<std::nested_exception>(make_nested_eptr(base_exception(), derived_exception()));
|
|
check_no_catch<derived_exception>(make_nested_eptr(base_exception(), derived_exception()));
|
|
|
|
// Check that everything works if we throw some crazy stuff
|
|
check_catch<int>(int(1));
|
|
check_no_catch<std::exception>(int(1));
|
|
|
|
check_no_catch<int>(nullptr);
|
|
check_no_catch<std::exception>(nullptr);
|
|
|
|
// Catching pointers is not supported, but nothing should break if they are
|
|
// being thrown
|
|
derived_exception exc;
|
|
check_no_catch<int>(&exc);
|
|
check_no_catch<std::exception>(&exc);
|
|
|
|
check_no_catch<int>(&dummy_fn);
|
|
check_no_catch<std::exception>(&dummy_fn);
|
|
|
|
check_no_catch<int>(&std::exception::what);
|
|
check_no_catch<std::exception>(&std::exception::what);
|
|
|
|
return make_ready_future<>();
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_try_catch_nested) {
|
|
using nested_type = std::logic_error;
|
|
using nesting_type = utils::internal::default_nested_exception_type;
|
|
|
|
// Not nested exception
|
|
{
|
|
auto inner = std::make_exception_ptr(nested_type("inner"));
|
|
auto* inner_exception = try_catch_nested<nested_type>(inner);
|
|
BOOST_REQUIRE_NE(inner_exception, nullptr);
|
|
BOOST_REQUIRE_EQUAL(inner_exception->what(), "inner");
|
|
}
|
|
|
|
// Not nested exception, wrong type
|
|
{
|
|
auto inner = std::make_exception_ptr(std::string("inner"));
|
|
auto* inner_exception = try_catch_nested<nested_type>(inner);
|
|
BOOST_REQUIRE_EQUAL(inner_exception, nullptr);
|
|
}
|
|
|
|
// 1 level
|
|
{
|
|
auto inner = std::make_exception_ptr(nested_type("inner"));
|
|
auto outer = make_nested_exception_ptr(nesting_type("outer"), std::move(inner));
|
|
auto* exception = try_catch_nested<nested_type>(outer);
|
|
BOOST_REQUIRE_NE(exception, nullptr);
|
|
BOOST_REQUIRE_EQUAL(exception->what(), "inner");
|
|
}
|
|
|
|
// 2 levels
|
|
{
|
|
auto inner = std::make_exception_ptr(nested_type("inner"));
|
|
auto outer1 = make_nested_exception_ptr(nesting_type("outer"), std::move(inner));
|
|
auto outer2 = make_nested_exception_ptr(utils::internal::default_nested_exception_type("outer2"), std::move(outer1));
|
|
auto* exception = try_catch_nested<nested_type>(outer2);
|
|
BOOST_REQUIRE_NE(exception, nullptr);
|
|
BOOST_REQUIRE_EQUAL(exception->what(), "inner");
|
|
}
|
|
|
|
// 3 levels
|
|
{
|
|
auto inner = std::make_exception_ptr(nested_type("inner"));
|
|
auto outer1 = make_nested_exception_ptr(nesting_type("outer"), std::move(inner));
|
|
auto outer2 = make_nested_exception_ptr(utils::internal::default_nested_exception_type("outer2"), std::move(outer1));
|
|
auto outer3 = make_nested_exception_ptr(utils::internal::default_nested_exception_type("outer3"), std::move(outer2));
|
|
auto* exception = try_catch_nested<nested_type>(outer3);
|
|
BOOST_REQUIRE_EQUAL(exception->what(), "inner");
|
|
}
|
|
|
|
// Negative case, wrong type
|
|
{
|
|
auto inner = std::make_exception_ptr(std::string("inner"));
|
|
auto outer = make_nested_exception_ptr(nesting_type("outer"), std::move(inner));
|
|
auto* exception = try_catch_nested<nested_type>(outer);
|
|
BOOST_REQUIRE_EQUAL(exception, nullptr);
|
|
}
|
|
|
|
// Negative case - null nested exception
|
|
{
|
|
std::exception_ptr inner;
|
|
auto outer = std::make_exception_ptr(utils::internal::nested_exception(nesting_type("outer"), std::move(inner)));
|
|
auto* exception = try_catch_nested<nested_type>(outer);
|
|
BOOST_REQUIRE_EQUAL(exception, nullptr);
|
|
}
|
|
|
|
return make_ready_future<>();
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_make_nested_exception_ptr) {
|
|
auto inner = std::make_exception_ptr(std::runtime_error("inner"));
|
|
auto outer = make_nested_exception_ptr(std::runtime_error("outer"), inner);
|
|
|
|
try {
|
|
std::rethrow_exception(outer);
|
|
} catch (const std::runtime_error& ex) {
|
|
BOOST_REQUIRE_EQUAL(std::string(ex.what()), "outer");
|
|
auto* nested = dynamic_cast<const std::nested_exception*>(&ex);
|
|
BOOST_REQUIRE_NE(nested, nullptr);
|
|
BOOST_REQUIRE_EQUAL(nested->nested_ptr(), inner);
|
|
}
|
|
|
|
try {
|
|
std::rethrow_exception(outer);
|
|
} catch (const std::nested_exception& ex) {
|
|
BOOST_REQUIRE_EQUAL(ex.nested_ptr(), inner);
|
|
}
|
|
|
|
// Not a class
|
|
BOOST_REQUIRE_THROW(std::rethrow_exception(make_nested_exception_ptr(int(123), inner)), int);
|
|
|
|
// Final, so cannot add the std::nested_exception mixin to it
|
|
struct ultimate_exception final : public std::exception {};
|
|
BOOST_REQUIRE_THROW(std::rethrow_exception(make_nested_exception_ptr(ultimate_exception(), inner)), ultimate_exception);
|
|
|
|
// Already derived from nested exception, so cannot put more to it
|
|
struct already_nested_exception : public std::exception, public std::nested_exception {};
|
|
auto inner2 = std::make_exception_ptr(std::runtime_error("inner2"));
|
|
try {
|
|
std::rethrow_exception(inner2);
|
|
} catch (const std::runtime_error&) {
|
|
try {
|
|
std::rethrow_exception(make_nested_exception_ptr(already_nested_exception(), inner));
|
|
} catch (const already_nested_exception& ex) {
|
|
BOOST_REQUIRE_EQUAL(ex.nested_ptr(), inner2);
|
|
}
|
|
}
|
|
|
|
return make_ready_future<>();
|
|
}
|