agent-enviroments/builder/libs/seastar/tests/unit/exception_logging_test.cc
2024-09-10 17:06:08 +03:00

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
}