289 lines
10 KiB
C++
289 lines
10 KiB
C++
/*
|
|
* This file is open source software, licensed to you under the terms
|
|
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
|
* distributed with this work for additional information regarding copyright
|
|
* ownership. You may not use this file except in compliance with the License.
|
|
*
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
/*
|
|
* Copyright 2016 ScyllaDB
|
|
*/
|
|
|
|
#include <exception>
|
|
#define BOOST_TEST_MODULE core
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
#include <seastar/util/log.hh>
|
|
#include <seastar/util/backtrace.hh>
|
|
#include <ostream>
|
|
#include <regex>
|
|
|
|
|
|
using namespace seastar;
|
|
|
|
// a class which is not derived from std::exception
|
|
// to play the part of the unknown object in the logging
|
|
// function.
|
|
class unknown_obj {
|
|
sstring _message;
|
|
public:
|
|
unknown_obj(std::string message) : _message(message) {}
|
|
};
|
|
|
|
// This functions generates an exception chain nesting_level+1 deep
|
|
// for each nesting level it throws one of two types of objects, an
|
|
// unknown (non std::exception) object or a runtime error which is
|
|
// derived from std::exception, it chooses the type of thrown object
|
|
// according to the bit in the `nesting_level` place in the
|
|
// `tests_instance` paramter or in other words according to:
|
|
// bool(test_instance & (1<<nesting_level))
|
|
void exception_generator(uint32_t test_instance, int nesting_level) {
|
|
try {
|
|
if (nesting_level > 0) {
|
|
exception_generator(test_instance>>1, nesting_level-1);
|
|
}
|
|
} catch(...) {
|
|
auto msg = fmt::format("Exception Level {}", nesting_level);
|
|
if(test_instance&1) {
|
|
// Throw a non std::exception derived type
|
|
std::throw_with_nested(unknown_obj(msg));
|
|
} else {
|
|
std::throw_with_nested(std::runtime_error(msg));
|
|
}
|
|
}
|
|
if (nesting_level == 0) {
|
|
if (test_instance & 1) {
|
|
throw unknown_obj(fmt::format("Exception Level {}", nesting_level));
|
|
} else {
|
|
throw std::runtime_error(fmt::format("Exception Level {}", nesting_level));
|
|
}
|
|
}
|
|
}
|
|
|
|
// This function generates the expected logging output string of an exception
|
|
// thrown by the exception generator function with a specific output.
|
|
std::string exception_generator_str(uint32_t test_instance,int nesting_level) {
|
|
std::ostringstream ret;
|
|
const std::string runtime_err_str = "std::runtime_error";
|
|
const std::string unknown_obj_str = "unknown_obj";
|
|
const std::string nested_exception_with_unknown_obj_str = "std::_Nested_exception<unknown_obj>";
|
|
const std::string nested_exception_with_runtime_err_str = "std::_Nested_exception<std::runtime_error>";
|
|
|
|
for(; nesting_level > 0; nesting_level--) {
|
|
if (test_instance & 1) {
|
|
fmt::print(ret, "{}", nested_exception_with_unknown_obj_str);
|
|
} else {
|
|
fmt::print(ret, "{} (Exception Level {})", nested_exception_with_runtime_err_str,
|
|
nesting_level);
|
|
}
|
|
ret << ": ";
|
|
test_instance >>= 1;
|
|
}
|
|
|
|
|
|
if (test_instance & 1) {
|
|
fmt::print(ret, "{}", unknown_obj_str);
|
|
} else {
|
|
fmt::print(ret, "{} (Exception Level {})", runtime_err_str, nesting_level);
|
|
}
|
|
return ret.str();
|
|
}
|
|
|
|
// Test all variations of nested exceptions of some
|
|
// depth
|
|
BOOST_AUTO_TEST_CASE(nested_exception_logging1) {
|
|
|
|
constexpr int levels_to_test = 3;
|
|
|
|
for(int level = 0; level < levels_to_test; level++) {
|
|
for(int inst = (1 << (level + 1)) - 1; inst >= 0; inst--) {
|
|
std::ostringstream log_msg;
|
|
try {
|
|
exception_generator(inst, level);
|
|
} catch(...) {
|
|
log_msg << std::current_exception();
|
|
}
|
|
BOOST_REQUIRE_EQUAL(log_msg.str(), exception_generator_str(inst, level));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test logging of nested exception not mixed in with anything
|
|
BOOST_AUTO_TEST_CASE(nested_exception_logging2) {
|
|
std::ostringstream log_msg;
|
|
try {
|
|
throw std::nested_exception();
|
|
} catch(...) {
|
|
log_msg << std::current_exception();
|
|
}
|
|
|
|
BOOST_REQUIRE_EQUAL(log_msg.str(), std::string("std::nested_exception: <no exception>"));
|
|
}
|
|
|
|
class very_important_exception : public std::exception {
|
|
const char* my_name = "very important information";
|
|
|
|
public:
|
|
const char* what() const noexcept {
|
|
return my_name;
|
|
}
|
|
};
|
|
|
|
// Test logging of nested exception that have std::system_error mixed with other exceptions
|
|
// so that std::system_error is in the middle of the exception chain.
|
|
BOOST_AUTO_TEST_CASE(nested_exception_logging3) {
|
|
std::ostringstream log_msg;
|
|
|
|
try {
|
|
throw very_important_exception();
|
|
} catch (...) {
|
|
try {
|
|
std::throw_with_nested(std::system_error(1, std::generic_category(), "my error"));
|
|
} catch (...) {
|
|
try {
|
|
std::throw_with_nested(unknown_obj("This is an unknown object"));
|
|
} catch (...) {
|
|
log_msg << std::current_exception();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string expected_string("std::_Nested_exception<unknown_obj>: std::_Nested_exception<std::system_error> (error generic:1, my error: Operation not permitted): very_important_exception (very important information)");
|
|
|
|
BOOST_REQUIRE_EQUAL(log_msg.str(), expected_string);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(unknown_object_thrown_test) {
|
|
std::ostringstream log_msg;
|
|
try {
|
|
throw unknown_obj("This is an unknown object");
|
|
} catch(...) {
|
|
log_msg << std::current_exception();
|
|
}
|
|
|
|
BOOST_REQUIRE_EQUAL(log_msg.str(), std::string("unknown_obj"));
|
|
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(format_error_test) {
|
|
static seastar::logger l("format_error_test");
|
|
|
|
std::ostringstream log_msg;
|
|
l.set_ostream(log_msg);
|
|
|
|
const char* fmt = "bad format string: {}";
|
|
#ifdef SEASTAR_LOGGER_COMPILE_TIME_FMT
|
|
// {fmt} v8.0 and up comes with compile-time format string checking, so
|
|
// malformed format_string passed to `logger.error(format_string, args)`
|
|
// can be identified at compile time. but a runtime variable passed to
|
|
// `logger.error(msg)` cannot be considered as a format string anymore
|
|
// when compiled with {fmt} v8.0 and up. so we have to test with a runtime
|
|
// format string here
|
|
l.error(fmt::runtime(fmt));
|
|
#else
|
|
l.error(fmt);
|
|
#endif
|
|
|
|
BOOST_TEST_MESSAGE(log_msg.str());
|
|
BOOST_REQUIRE_NE(log_msg.str().find(__builtin_FILE()), std::string::npos);
|
|
BOOST_REQUIRE_NE(log_msg.str().find(__builtin_FUNCTION()), std::string::npos);
|
|
BOOST_REQUIRE_NE(log_msg.str().find(fmt), std::string::npos);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(throw_with_backtrace_exception_logging) {
|
|
std::ostringstream log_msg;
|
|
try {
|
|
throw_with_backtrace<std::runtime_error>("throw_with_backtrace_exception_logging");
|
|
} catch(...) {
|
|
log_msg << std::current_exception();
|
|
}
|
|
|
|
#ifndef SEASTAR_BACKTRACE_UNIMPLEMENTED
|
|
auto regex_str = "backtraced<std::runtime_error> \\(throw_with_backtrace_exception_logging Backtrace:(\\s+(\\S+\\+)?0x[0-9a-f]+)+\\)";
|
|
std::regex expected_msg_re(regex_str, std::regex_constants::ECMAScript | std::regex_constants::icase);
|
|
BOOST_REQUIRE(std::regex_search(log_msg.str(), expected_msg_re));
|
|
#endif
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(throw_with_backtrace_nested_exception_logging) {
|
|
std::ostringstream log_msg;
|
|
try {
|
|
throw_with_backtrace<std::runtime_error>("outer");
|
|
} catch(...) {
|
|
try {
|
|
std::throw_with_nested(unknown_obj("This is an unknown object"));
|
|
} catch (...) {
|
|
log_msg << std::current_exception();
|
|
}
|
|
}
|
|
|
|
#ifndef SEASTAR_BACKTRACE_UNIMPLEMENTED
|
|
auto regex_str = "std::_Nested_exception<unknown_obj>.*backtraced<std::runtime_error> \\(outer Backtrace:(\\s+(\\S+\\+)?0x[0-9a-f]+)+\\)";
|
|
std::regex expected_msg_re(regex_str, std::regex_constants::ECMAScript | std::regex_constants::icase);
|
|
BOOST_REQUIRE(std::regex_search(log_msg.str(), expected_msg_re));
|
|
#endif
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(throw_with_backtrace_seastar_nested_exception_logging) {
|
|
std::ostringstream log_msg;
|
|
try {
|
|
throw unknown_obj("This is an unknown object");
|
|
} catch (...) {
|
|
auto outer = std::current_exception();
|
|
try {
|
|
throw_with_backtrace<std::runtime_error>("inner");
|
|
} catch (...) {
|
|
auto inner = std::current_exception();
|
|
try {
|
|
throw seastar::nested_exception(std::move(inner), std::move(outer));
|
|
} catch (...) {
|
|
log_msg << std::current_exception();
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef SEASTAR_BACKTRACE_UNIMPLEMENTED
|
|
auto regex_str = "seastar::nested_exception:.*backtraced<std::runtime_error> \\(inner Backtrace:(\\s+(\\S+\\+)?0x[0-9a-f]+)+\\)"
|
|
" \\(while cleaning up after unknown_obj\\)";
|
|
std::regex expected_msg_re(regex_str, std::regex_constants::ECMAScript | std::regex_constants::icase);
|
|
BOOST_REQUIRE(std::regex_search(log_msg.str(), expected_msg_re));
|
|
#endif
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(double_throw_with_backtrace_seastar_nested_exception_logging) {
|
|
std::ostringstream log_msg;
|
|
try {
|
|
throw_with_backtrace<std::runtime_error>("outer");
|
|
} catch (...) {
|
|
auto outer = std::current_exception();
|
|
try {
|
|
throw_with_backtrace<std::runtime_error>("inner");
|
|
} catch (...) {
|
|
auto inner = std::current_exception();
|
|
try {
|
|
throw seastar::nested_exception(std::move(inner), std::move(outer));
|
|
} catch (...) {
|
|
log_msg << std::current_exception();
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef SEASTAR_BACKTRACE_UNIMPLEMENTED
|
|
auto regex_str = "seastar::nested_exception:.*backtraced<std::runtime_error> \\(inner Backtrace:(\\s+(\\S+\\+)?0x[0-9a-f]+)+\\)"
|
|
" \\(while cleaning up after .*backtraced<std::runtime_error> \\(outer Backtrace:(\\s+(\\S+\\+)?0x[0-9a-f]+)+\\)\\)";
|
|
std::regex expected_msg_re(regex_str, std::regex_constants::ECMAScript | std::regex_constants::icase);
|
|
BOOST_REQUIRE(std::regex_search(log_msg.str(), expected_msg_re));
|
|
#endif
|
|
}
|