polardbxengine/router/tests/component/test_logging.cc

2075 lines
79 KiB
C++

/*
Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "dim.h"
#include "mock_server_rest_client.h"
#include "mock_server_testutils.h"
#include "mysql/harness/logging/logging.h"
#include "mysql_session.h"
#include "mysqlrouter/utils.h"
#include "random_generator.h"
#include "router_component_test.h"
#include "tcp_port_pool.h"
#ifndef _WIN32
#include <signal.h>
#endif
#include <condition_variable>
#include <fstream>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
/**
* @file
* @brief Component Tests for loggers
*/
using mysql_harness::logging::LogLevel;
using mysql_harness::logging::LogTimestampPrecision;
using testing::HasSubstr;
using testing::Not;
using testing::StartsWith;
using namespace std::chrono_literals;
class RouterLoggingTest : public RouterComponentTest {
protected:
TcpPortPool port_pool_;
};
/** @test This test verifies that fatal error messages thrown before switching
* to logger specified in config file (before Loader::run() runs
* logger_plugin.cc:init()) are properly logged to STDERR
*/
TEST_F(RouterLoggingTest, log_startup_failure_to_console) {
auto conf_params = get_DEFAULT_defaults();
// we want to log to the console
conf_params["logging_folder"] = "";
TempDirectory conf_dir("conf");
const std::string conf_file =
create_config_file(conf_dir.name(), "[invalid]", &conf_params);
// run the router and wait for it to exit
auto &router = launch_router({"-c", conf_file}, 1);
check_exit_code(router, EXIT_FAILURE);
// expect something like this to appear on STDERR
// plugin 'invalid' failed to
// load: ./plugin_output_directory/invalid.so: cannot open shared object
// file: No such file or directory
const std::string out = router.get_full_output();
EXPECT_THAT(out.c_str(), HasSubstr("plugin 'invalid' failed to load"));
}
/** @test This test is similar to log_startup_failure_to_logfile(), but the
* failure message is expected to be logged into a logfile
*/
TEST_F(RouterLoggingTest, log_startup_failure_to_logfile) {
// create tmp dir where we will log
TempDirectory logging_folder;
// create config with logging_folder set to that directory
std::map<std::string, std::string> params = get_DEFAULT_defaults();
params.at("logging_folder") = logging_folder.name();
TempDirectory conf_dir("conf");
const std::string conf_file =
create_config_file(conf_dir.name(), "[routing]", &params);
// run the router and wait for it to exit
auto &router = launch_router({"-c", conf_file}, 1);
check_exit_code(router, EXIT_FAILURE);
// expect something like this to appear in log:
// 2018-12-19 03:54:04 main ERROR [7f539f628780] Configuration error: option
// destinations in [routing] is required
auto matcher = [](const std::string &line) -> bool {
return line.find(
"Configuration error: option destinations in [routing] is "
"required") != line.npos;
};
EXPECT_TRUE(find_in_file(logging_folder.name() + "/mysqlrouter.log", matcher))
<< "log:"
<< router.get_full_logfile("mysqlrouter.log", logging_folder.name());
}
/** @test This test verifies that invalid logging_folder is properly handled and
* appropriate message is printed on STDERR. Router tries to
* mkdir(logging_folder) if it doesn't exist, then write its log inside of it.
*/
TEST_F(RouterLoggingTest, bad_logging_folder) {
// create tmp dir to contain our tests
TempDirectory tmp_dir;
// unfortunately it's not (reasonably) possible to make folders read-only on
// Windows, therefore we can run the following 2 tests only on Unix
// https://support.microsoft.com/en-us/help/326549/you-cannot-view-or-change-the-read-only-or-the-system-attributes-of-fo
#ifndef _WIN32
// make tmp dir read-only
chmod(tmp_dir.name().c_str(),
S_IRUSR | S_IXUSR); // r-x for the user (aka 500)
// logging_folder doesn't exist and can't be created
{
const std::string logging_dir = tmp_dir.name() + "/some_dir";
// create Router config
std::map<std::string, std::string> params = get_DEFAULT_defaults();
params.at("logging_folder") = logging_dir;
TempDirectory conf_dir("conf");
const std::string conf_file =
create_config_file(conf_dir.name(), "[keepalive]\n", &params);
// run the router and wait for it to exit
auto &router = launch_router({"-c", conf_file}, 1);
check_exit_code(router, EXIT_FAILURE);
// expect something like this to appear on STDERR
// Error: Error when creating dir '/bla': 13
const std::string out = router.get_full_output();
EXPECT_THAT(
out.c_str(),
HasSubstr("plugin 'logger' init failed: Error when creating dir '" +
logging_dir + "': 13"));
}
// logging_folder exists but is not writeable
{
const std::string logging_dir = tmp_dir.name();
// create Router config
std::map<std::string, std::string> params = get_DEFAULT_defaults();
params.at("logging_folder") = logging_dir;
TempDirectory conf_dir("conf");
const std::string conf_file =
create_config_file(conf_dir.name(), "[keepalive]\n", &params);
// run the router and wait for it to exit
auto &router = launch_router({"-c", conf_file}, 1);
check_exit_code(router, EXIT_FAILURE);
// expect something like this to appear on STDERR
// Error: Cannot create file in directory //mysqlrouter.log: Permission
// denied
const std::string out = router.get_full_output();
#ifndef _WIN32
EXPECT_THAT(
out.c_str(),
HasSubstr(
"plugin 'logger' init failed: Cannot create file in directory " +
logging_dir + ": Permission denied\n"));
#endif
}
// restore writability to tmp dir
chmod(tmp_dir.name().c_str(),
S_IRUSR | S_IWUSR | S_IXUSR); // rwx for the user (aka 700)
#endif // #ifndef _WIN32
// logging_folder is really a file
{
const std::string logging_dir = tmp_dir.name() + "/some_file";
// create that file
{
std::ofstream some_file(logging_dir);
EXPECT_TRUE(some_file.good());
}
// create Router config
std::map<std::string, std::string> params = get_DEFAULT_defaults();
params.at("logging_folder") = logging_dir;
TempDirectory conf_dir("conf");
const std::string conf_file =
create_config_file(conf_dir.name(), "[keepalive]\n", &params);
// run the router and wait for it to exit
auto &router = launch_router({"-c", conf_file}, 1);
check_exit_code(router, EXIT_FAILURE);
// expect something like this to appear on STDERR
// Error: Cannot create file in directory /etc/passwd/mysqlrouter.log: Not a
// directory
const std::string out = router.get_full_output();
#ifndef _WIN32
EXPECT_THAT(out.c_str(), HasSubstr("Cannot create file in directory " +
logging_dir + ": Not a directory\n"));
#else
// on Windows emulate (wine) we get ENOTDIR
// with native windows we get ENOENT
EXPECT_THAT(
out.c_str(),
::testing::AllOf(
::testing::HasSubstr("Cannot create file in directory " +
logging_dir),
::testing::AnyOf(
::testing::EndsWith("Directory name invalid.\n\n"),
::testing::EndsWith(
"The system cannot find the path specified.\n\n"))));
#endif
}
}
TEST_F(RouterLoggingTest, multiple_logger_sections) {
// This test verifies that multiple [logger] sections are handled properly.
// Router should report the error on STDERR and exit
auto conf_params = get_DEFAULT_defaults();
// we want to log to the console
conf_params["logging_folder"] = "";
TempDirectory conf_dir("conf");
const std::string conf_file =
create_config_file(conf_dir.name(), "[logger]\n[logger]\n", &conf_params);
// run the router and wait for it to exit
auto &router = launch_router({"-c", conf_file}, 1);
check_exit_code(router, EXIT_FAILURE);
// expect something like this to appear on STDERR
// Error: Configuration error: Section 'logger' already exists
const std::string out = router.get_full_output();
EXPECT_THAT(
out.c_str(),
::testing::HasSubstr(
"Error: Configuration error: Section 'logger' already exists"));
}
TEST_F(RouterLoggingTest, logger_section_with_key) {
// This test verifies that [logger:with_some_key] section is handled properly
// Router should report the error on STDERR and exit
auto conf_params = get_DEFAULT_defaults();
// we want to log to the console
conf_params["logging_folder"] = "";
TempDirectory conf_dir("conf");
const std::string conf_file =
create_config_file(conf_dir.name(), "[logger:some_key]\n", &conf_params);
// run the router and wait for it to exit
auto &router = launch_router({"-c", conf_file}, 1);
check_exit_code(router, EXIT_FAILURE);
// expect something like this to appear on STDERR
// Error: Section 'logger' does not support key
const std::string out = router.get_full_output();
EXPECT_THAT(out.c_str(),
HasSubstr("Error: Section 'logger' does not support keys"));
}
TEST_F(RouterLoggingTest, bad_loglevel) {
// This test verifies that bad log level in [logger] section is handled
// properly. Router should report the error on STDERR and exit
auto conf_params = get_DEFAULT_defaults();
// we want to log to the console
conf_params["logging_folder"] = "";
TempDirectory conf_dir("conf");
const std::string conf_file = create_config_file(
conf_dir.name(), "[logger]\nlevel = UNKNOWN\n", &conf_params);
// run the router and wait for it to exit
auto &router = launch_router({"-c", conf_file}, 1);
check_exit_code(router, EXIT_FAILURE);
// expect something like this to appear on STDERR
// Configuration error: Log level 'unknown' is not valid. Valid values are:
// debug, error, fatal, info, and warning
const std::string out = router.get_full_output();
EXPECT_THAT(
out.c_str(),
HasSubstr("Configuration error: Log level 'unknown' is not valid. Valid "
"values are: debug, error, fatal, info, and warning"));
}
/**************************************************/
/* Tests for valid logger configurations */
/**************************************************/
struct LoggingConfigOkParams {
std::string logger_config;
bool logging_folder_empty;
LogLevel consolelog_expected_level;
LogLevel filelog_expected_level;
LogTimestampPrecision consolelog_expected_timestamp_precision;
LogTimestampPrecision filelog_expected_timestamp_precision;
LoggingConfigOkParams(const std::string &logger_config_,
const bool logging_folder_empty_,
const LogLevel consolelog_expected_level_,
const LogLevel filelog_expected_level_)
: logger_config(logger_config_),
logging_folder_empty(logging_folder_empty_),
consolelog_expected_level(consolelog_expected_level_),
filelog_expected_level(filelog_expected_level_),
consolelog_expected_timestamp_precision(LogTimestampPrecision::kNotSet),
filelog_expected_timestamp_precision(LogTimestampPrecision::kNotSet) {}
LoggingConfigOkParams(
const std::string &logger_config_, const bool logging_folder_empty_,
const LogLevel consolelog_expected_level_,
const LogLevel filelog_expected_level_,
const LogTimestampPrecision consolelog_expected_timestamp_precision_,
const LogTimestampPrecision filelog_expected_timestamp_precision_)
: logger_config(logger_config_),
logging_folder_empty(logging_folder_empty_),
consolelog_expected_level(consolelog_expected_level_),
filelog_expected_level(filelog_expected_level_),
consolelog_expected_timestamp_precision(
consolelog_expected_timestamp_precision_),
filelog_expected_timestamp_precision(
filelog_expected_timestamp_precision_) {}
};
::std::ostream &operator<<(::std::ostream &os,
const LoggingConfigOkParams &ltp) {
return os << "config=" << ltp.logger_config
<< ", logging_folder_empty=" << ltp.logging_folder_empty;
}
class RouterLoggingTestConfig
: public RouterComponentTest,
public ::testing::WithParamInterface<LoggingConfigOkParams> {};
/** @test This test verifies that a proper loggs are written to selected sinks
* for various sinks/levels combinations.
*/
TEST_P(RouterLoggingTestConfig, LoggingTestConfig) {
auto test_params = GetParam();
TempDirectory tmp_dir;
TcpPortPool port_pool;
const auto router_port = port_pool.get_next_available();
const auto server_port = port_pool.get_next_available();
// These are different level log entries that are expected to get logged after
// the logger plugin has been initialized
const std::string kDebugLogEntry = "plugin 'logger:' doesn't implement start";
const std::string kInfoLogEntry = "[routing] started: listening on 127.0.0.1";
const std::string kWarningLogEntry =
"Can't connect to remote MySQL server for client";
// to trigger the warning entry in the log
const std::string kRoutingConfig =
"[routing]\n"
"bind_address=127.0.0.1:" +
std::to_string(router_port) +
"\n"
"destinations=localhost:" +
std::to_string(server_port) +
"\n"
"routing_strategy=round-robin\n";
auto conf_params = get_DEFAULT_defaults();
conf_params["logging_folder"] =
test_params.logging_folder_empty ? "" : tmp_dir.name();
TempDirectory conf_dir("conf");
const std::string conf_text =
test_params.logger_config + "\n" + kRoutingConfig;
const std::string conf_file =
create_config_file(conf_dir.name(), conf_text, &conf_params);
auto &router = launch_router({"-c", conf_file});
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
// try to make a connection; this will fail but should generate a warning in
// the logs
mysqlrouter::MySQLSession client;
try {
client.connect("127.0.0.1", router_port, "username", "password", "", "");
} catch (const std::exception &exc) {
if (std::string(exc.what()).find("Error connecting to MySQL server") !=
std::string::npos) {
// that's what we expect
} else
throw;
}
const std::string console_log_txt = router.get_full_output();
// check the console log if it contains what's expected
if (test_params.consolelog_expected_level >= LogLevel::kDebug &&
test_params.consolelog_expected_level != LogLevel::kNotSet) {
EXPECT_THAT(console_log_txt, HasSubstr(kDebugLogEntry)) << "console:\n"
<< console_log_txt;
} else {
EXPECT_THAT(console_log_txt, Not(HasSubstr(kDebugLogEntry)))
<< "console:\n"
<< console_log_txt;
}
if (test_params.consolelog_expected_level >= LogLevel::kInfo &&
test_params.consolelog_expected_level != LogLevel::kNotSet) {
EXPECT_THAT(console_log_txt, HasSubstr(kInfoLogEntry)) << "console:\n"
<< console_log_txt;
} else {
EXPECT_THAT(console_log_txt, Not(HasSubstr(kInfoLogEntry)))
<< "console:\n"
<< console_log_txt;
}
if (test_params.consolelog_expected_level >= LogLevel::kWarning &&
test_params.consolelog_expected_level != LogLevel::kNotSet) {
EXPECT_THAT(console_log_txt, HasSubstr(kWarningLogEntry))
<< "console:\n"
<< console_log_txt;
} else {
EXPECT_THAT(console_log_txt, Not(HasSubstr(kWarningLogEntry)))
<< "console:\n"
<< console_log_txt;
}
// check the file log if it contains what's expected
const std::string file_log_txt =
router.get_full_logfile("mysqlrouter.log", tmp_dir.name());
if (test_params.filelog_expected_level >= LogLevel::kDebug &&
test_params.filelog_expected_level != LogLevel::kNotSet) {
EXPECT_THAT(file_log_txt, HasSubstr(kDebugLogEntry))
<< "file:\n"
<< file_log_txt << "\nconsole:\n"
<< console_log_txt;
} else {
EXPECT_THAT(file_log_txt, Not(HasSubstr(kDebugLogEntry)))
<< "file:\n"
<< file_log_txt << "\nconsole:\n"
<< console_log_txt;
}
if (test_params.filelog_expected_level >= LogLevel::kInfo &&
test_params.filelog_expected_level != LogLevel::kNotSet) {
EXPECT_THAT(file_log_txt, HasSubstr(kInfoLogEntry))
<< "file:\n"
<< file_log_txt << "\nconsole:\n"
<< console_log_txt;
} else {
EXPECT_THAT(file_log_txt, Not(HasSubstr(kInfoLogEntry)))
<< "file:\n"
<< file_log_txt << "\nconsole:\n"
<< console_log_txt;
}
if (test_params.filelog_expected_level >= LogLevel::kWarning &&
test_params.filelog_expected_level != LogLevel::kNotSet) {
EXPECT_THAT(file_log_txt, HasSubstr(kWarningLogEntry))
<< "file:\n"
<< file_log_txt << "\nconsole:\n"
<< console_log_txt;
} else {
EXPECT_THAT(file_log_txt, Not(HasSubstr(kWarningLogEntry)))
<< "file:\n"
<< file_log_txt << "\nconsole:\n"
<< console_log_txt;
}
}
INSTANTIATE_TEST_CASE_P(
LoggingConfigTest, RouterLoggingTestConfig,
::testing::Values(
// no logger section, no sinks sections
// logging_folder not empty so we are expected to log to the file
// with a warning level so info and debug logs will not be there
/*0*/ LoggingConfigOkParams(
"",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kNotSet,
/* filelog_expected_level = */ LogLevel::kWarning),
// no logger section, no sinks sections
// logging_folder empty so we are expected to log to the console
// with a warning level so info and debug logs will not be there
/*1*/
LoggingConfigOkParams(
"",
/* logging_folder_empty = */ true,
/* consolelog_expected_level = */ LogLevel::kWarning,
/* filelog_expected_level = */ LogLevel::kNotSet),
// logger section, no sinks sections
// logging_folder not empty so we are expected to log to the file
// with a warning level as level is not redefined in the [logger]
// section
/*2*/
LoggingConfigOkParams(
"[logger]",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kNotSet,
/* filelog_expected_level = */ LogLevel::kWarning),
// logger section, no sinks sections
// logging_folder not empty so we are expected to log to the file
// with a level defined in the logger section
/*3*/
LoggingConfigOkParams(
"[logger]\n"
"level=info\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kNotSet,
/* filelog_expected_level = */ LogLevel::kInfo),
// logger section, no sinks sections; logging_folder is empty so we are
// expected to log to the console with a level defined in the logger
// section
/*4*/
LoggingConfigOkParams(
"[logger]\n"
"level=info\n",
/* logging_folder_empty = */ true,
/* consolelog_expected_level = */ LogLevel::kInfo,
/* filelog_expected_level = */ LogLevel::kNotSet),
// consolelog configured as a sink; it does not have its section in the
// config but that is not an error; even though the logging folder is
// not empty, we still don't log to the file as sinks= setting wants use
// the console
/*5*/
LoggingConfigOkParams(
"[logger]\n"
"level=debug\n"
"sinks=consolelog\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kNotSet),
// 2 sinks have sections but consolelog is not defined as a sink in the
// [logger] section so there should be no logging to the console (after
// [logger] is initialised; prior to that all is logged to the console
// by default)
/*6*/
LoggingConfigOkParams(
"[logger]\n"
"sinks=filelog\n"
"level=debug\n"
"[filelog]\n"
"[consolelog]\n"
"level=debug\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kNotSet,
/* filelog_expected_level = */ LogLevel::kDebug),
// 2 sinks, both should inherit log level from [logger] section (which
// is debug)
/*7*/
LoggingConfigOkParams(
"[logger]\n"
"sinks=filelog,consolelog\n"
"level=debug\n"
"[filelog]\n"
"[consolelog]\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug),
// 2 sinks, both should inherit log level from [logger] section (which
// is info); debug logs are not expected for both sinks
/*8*/
LoggingConfigOkParams(
"[logger]\n"
"sinks=filelog,consolelog\n"
"level=info\n"
"[filelog]\n"
"[consolelog]\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kInfo,
/* filelog_expected_level = */ LogLevel::kInfo),
// 2 sinks, both should inherit log level from [logger] section (which
// is warning); neither debug not info logs are not expected for both
// sinks
/*9*/
LoggingConfigOkParams(
"[logger]\n"
"sinks=filelog,consolelog\n"
"level=warning\n"
"[filelog]\n"
"[consolelog]\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kWarning,
/* filelog_expected_level = */ LogLevel::kWarning),
// 2 sinks, one overwrites the default log level, the other inherits
// default from [logger] section
/*10*/
LoggingConfigOkParams(
"[logger]\n"
"sinks=filelog,consolelog\n"
"level=info\n"
"[filelog]\n"
"level=debug\n"
"[consolelog]\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kInfo,
/* filelog_expected_level = */ LogLevel::kDebug),
// 2 sinks, each defines its own custom log level that overwrites the
// default from [logger] section
/*11*/
LoggingConfigOkParams(
"[logger]\n"
"sinks=filelog,consolelog\n"
"level=info\n"
"[filelog]\n"
"level=debug\n"
"[consolelog]\n"
"level=warning\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kWarning,
/* filelog_expected_level = */ LogLevel::kDebug),
// 2 sinks, each defines its own custom log level that overwrites the
// default from [logger] section
/*12*/
LoggingConfigOkParams(
"[logger]\n"
"sinks=filelog,consolelog\n"
"level=warning\n"
"[filelog]\n"
"level=info\n"
"[consolelog]\n"
"level=warning\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kWarning,
/* filelog_expected_level = */ LogLevel::kInfo),
// 2 sinks, each defines its own custom log level (that is more strict)
// that overwrites the default from [logger] section
/*13*/
LoggingConfigOkParams(
"[logger]\n"
"sinks=filelog,consolelog\n"
"level=debug\n"
"[filelog]\n"
"level=info\n"
"[consolelog]\n"
"level=warning\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kWarning,
/* filelog_expected_level = */ LogLevel::kInfo),
// 2 sinks,no level in the [logger] section and no level in the sinks
// sections; default log level should be used (which is warning)
/*14*/
LoggingConfigOkParams(
"[logger]\n"
"sinks=filelog,consolelog\n"
"[filelog]\n"
"[consolelog]\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kWarning,
/* filelog_expected_level = */ LogLevel::kWarning),
// 2 sinks, level in the [logger] section is warning; it should be
// used by the sinks as they don't redefine it in their sections
/*15*/
LoggingConfigOkParams(
"[logger]\n"
"level=warning\n"
"sinks=filelog,consolelog\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kWarning,
/* filelog_expected_level = */ LogLevel::kWarning),
// 2 sinks, level in the [logger] section is error; it should be used
// by the sinks as they don't redefine it in their sections
/*16*/
LoggingConfigOkParams(
"[logger]\n"
"level=error\n"
"sinks=filelog,consolelog\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kError,
/* filelog_expected_level = */ LogLevel::kError),
// 2 sinks, no level in the [logger] section, each defines it's own
// level
/*17*/
LoggingConfigOkParams(
"[logger]\n"
"sinks=filelog,consolelog\n"
"[filelog]\n"
"level=error\n"
"[consolelog]\n"
"level=debug\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kError),
// 2 sinks, no level in the [logger] section, one defines it's own
// level, the other expected to go with default (warning)
/*18*/
LoggingConfigOkParams(
"[logger]\n"
"sinks=filelog,consolelog\n"
"[filelog]\n"
"level=error\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kWarning,
/* filelog_expected_level = */ LogLevel::kError)));
/**************************************************/
/* Tests for logger configuration errors */
/**************************************************/
struct LoggingConfigErrorParams {
std::string logger_config;
bool logging_folder_empty;
std::string expected_error;
LoggingConfigErrorParams(const std::string &logger_config_,
const bool logging_folder_empty_,
const std::string &expected_error_)
: logger_config(logger_config_),
logging_folder_empty(logging_folder_empty_),
expected_error(expected_error_) {}
};
::std::ostream &operator<<(::std::ostream &os,
const LoggingConfigErrorParams &ltp) {
return os << "config=" << ltp.logger_config
<< ", logging_folder_empty=" << ltp.logging_folder_empty;
}
class RouterLoggingConfigError
: public RouterComponentTest,
public ::testing::WithParamInterface<LoggingConfigErrorParams> {};
/** @test This test verifies that a proper error gets printed on the console for
* a particular logging configuration
*/
TEST_P(RouterLoggingConfigError, LoggingConfigError) {
auto test_params = GetParam();
TempDirectory tmp_dir;
auto conf_params = get_DEFAULT_defaults();
conf_params["logging_folder"] =
test_params.logging_folder_empty ? "" : tmp_dir.name();
TempDirectory conf_dir("conf");
const std::string conf_text = "[keepalive]\n" + test_params.logger_config;
const std::string conf_file =
create_config_file(conf_dir.name(), conf_text, &conf_params);
auto &router = launch_router({"-c", conf_file}, 1);
check_exit_code(router, EXIT_FAILURE);
// the error happens during the logger initialization so we expect the message
// on the console which is the default sink until we switch to the
// configuration from the config file
const std::string console_log_txt = router.get_full_output();
EXPECT_THAT(console_log_txt, HasSubstr(test_params.expected_error))
<< "\nconsole:\n"
<< console_log_txt;
}
INSTANTIATE_TEST_CASE_P(
LoggingConfigError, RouterLoggingConfigError,
::testing::Values(
// Unknown sink name in the [logger] section
/*0*/ LoggingConfigErrorParams(
"[logger]\n"
"sinks=unknown\n"
"level=debug\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"Configuration error: Unsupported logger sink type: 'unknown'"),
// Empty sinks option
/*1*/
LoggingConfigErrorParams(
"[logger]\n"
"sinks=\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"plugin 'logger' init failed: sinks option does not contain any "
"valid sink name, was ''"),
// Empty sinks list
/*2*/
LoggingConfigErrorParams(
"[logger]\n"
"sinks=,\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"plugin 'logger' init failed: Unsupported logger sink type: ''"),
// Leading comma on a sinks list
/*3*/
LoggingConfigErrorParams(
"[logger]\n"
"sinks=,consolelog\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"plugin 'logger' init failed: Unsupported logger sink type: ''"),
// Terminating comma on a sinks list
/*4*/
LoggingConfigErrorParams(
"[logger]\n"
"sinks=consolelog,\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"plugin 'logger' init failed: Unsupported logger sink type: ''"),
// Two commas separating sinks
/*5*/
LoggingConfigErrorParams(
"[logger]\n"
"sinks=consolelog,,filelog\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"plugin 'logger' init failed: Unsupported logger sink type: ''"),
// Empty space as a sink name
/*6*/
LoggingConfigErrorParams(
"[logger]\n"
"sinks= \n",
/* logging_folder_empty = */ false,
/* expected_error = */
"plugin 'logger' init failed: sinks option does not contain any "
"valid sink name, was ''"),
// Invalid log level in the [logger] section
/*7*/
LoggingConfigErrorParams(
"[logger]\n"
"sinks=consolelog\n"
"level=invalid\n"
"[consolelog]\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"Configuration error: Log level 'invalid' is not valid. Valid "
"values are: debug, error, fatal, info, and warning"),
// Invalid log level in the sink section
/*8*/
LoggingConfigErrorParams(
"[logger]\n"
"sinks=consolelog\n"
"[consolelog]\n"
"level=invalid\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"Configuration error: Log level 'invalid' is not valid. Valid "
"values are: debug, error, fatal, info, and warning"),
// Both level and sinks valuse invalid in the [logger] section
/*9*/
LoggingConfigErrorParams(
"[logger]\n"
"sinks=invalid\n"
"level=invalid\n"
"[consolelog]\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"Configuration error: Log level 'invalid' is not valid. Valid "
"values are: debug, error, fatal, info, and warning"),
// Logging folder is empty but we request filelog as sink
/*10*/
LoggingConfigErrorParams(
"[logger]\n"
"sinks=filelog\n",
/* logging_folder_empty = */ true,
/* expected_error = */
"plugin 'logger' init failed: filelog sink configured but the "
"logging_folder is empty")));
#ifndef _WIN32
INSTANTIATE_TEST_CASE_P(
LoggingConfigErrorUnix, RouterLoggingConfigError,
::testing::Values(
// We can't reliably check if the syslog logging is working with a
// component test as this is too operating system intrusive and we are
// supposed to run on pb2 environment. Let's at least check that this
// sink type is supported
LoggingConfigErrorParams(
"[logger]\n"
"sinks=syslog\n"
"[syslog]\n"
"level=invalid\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"Configuration error: Log level 'invalid' is not valid. Valid "
"values are: debug, error, fatal, info, and warning"),
// Let's also check that the eventlog is NOT supported
LoggingConfigErrorParams("[logger]\n"
"sinks=eventlog\n"
"[eventlog]\n"
"level=invalid\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"plugin 'eventlog' failed to load")));
#else
INSTANTIATE_TEST_CASE_P(
LoggingConfigErrorWindows, RouterLoggingConfigError,
::testing::Values(
// We can't reliably check if the eventlog logging is working with a
// component test as this is too operating system intrusive and also
// requires admin priviledges to setup and we are supposed to run on pb2
// environment. Let's at least check that this sink type is supported
LoggingConfigErrorParams(
"[logger]\n"
"sinks=eventlog\n"
"[eventlog]\n"
"level=invalid\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"Configuration error: Log level 'invalid' is not valid. Valid "
"values are: debug, error, fatal, info, and warning"),
// Let's also check that the syslog is NOT supported
LoggingConfigErrorParams("[logger]\n"
"sinks=syslog\n"
"[syslog]\n"
"level=invalid\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"plugin 'syslog' failed to load")));
#endif
class RouterLoggingTestTimestampPrecisionConfig
: public RouterComponentTest,
public ::testing::WithParamInterface<LoggingConfigOkParams> {};
#define DATE_REGEX "[0-9]{4}-[0-9]{2}-[0-9]{2}"
#define TIME_REGEX "[0-9]{2}:[0-9]{2}:[0-9]{2}"
#define TS_MSEC_REGEX ".[0-9]{3}"
#define TS_USEC_REGEX ".[0-9]{6}"
#define TS_NSEC_REGEX ".[0-9]{9}"
#define TS_REGEX DATE_REGEX " " TIME_REGEX
const std::string kTimestampSecRegex = TS_REGEX " ";
const std::string kTimestampMillisecRegex = TS_REGEX TS_MSEC_REGEX " ";
const std::string kTimestampMicrosecRegex = TS_REGEX TS_USEC_REGEX " ";
const std::string kTimestampNanosecRegex = TS_REGEX TS_NSEC_REGEX " ";
/** @test This test verifies that a proper loggs are written to selected sinks
* for various sinks/levels combinations.
*/
TEST_P(RouterLoggingTestTimestampPrecisionConfig,
LoggingTestTimestampPrecisionConfig) {
auto test_params = GetParam();
TempDirectory tmp_dir;
TcpPortPool port_pool;
const auto router_port = port_pool.get_next_available();
const auto server_port = port_pool.get_next_available();
// Different log entries that are expected for different levels, but we only
// care that something is logged, not what, when checking timestamps.
// to trigger the warning entry in the log
const std::string kRoutingConfig =
"[routing]\n"
"bind_address=127.0.0.1:" +
std::to_string(router_port) +
"\n"
"destinations=localhost:" +
std::to_string(server_port) +
"\n"
"routing_strategy=round-robin\n";
auto conf_params = get_DEFAULT_defaults();
conf_params["logging_folder"] =
test_params.logging_folder_empty ? "" : tmp_dir.name();
TempDirectory conf_dir("conf");
const std::string conf_text =
test_params.logger_config + "\n" + kRoutingConfig;
const std::string conf_file =
create_config_file(conf_dir.name(), conf_text, &conf_params);
auto &router = launch_router({"-c", conf_file});
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
// try to make a connection; this will fail but should generate a warning in
// the logs
mysqlrouter::MySQLSession client;
try {
client.connect("127.0.0.1", router_port, "username", "password", "", "");
} catch (const std::exception &exc) {
if (std::string(exc.what()).find("Error connecting to MySQL server") !=
std::string::npos) {
// that's what we expect
} else
throw;
}
// check the console log if it contains what's expected
std::string console_log_txt = router.get_full_output();
// strip first line before checking if needed
const std::string prefix = "logging facility initialized";
if (std::mismatch(console_log_txt.begin(), console_log_txt.end(),
prefix.begin(), prefix.end())
.second == prefix.end()) {
console_log_txt.erase(0, console_log_txt.find("\n") + 1);
}
if (test_params.consolelog_expected_level != LogLevel::kNotSet) {
switch (test_params.consolelog_expected_timestamp_precision) {
case LogTimestampPrecision::kNotSet:
case LogTimestampPrecision::kSec:
// EXPECT 12:00:00
EXPECT_TRUE(pattern_found(console_log_txt, kTimestampSecRegex))
<< console_log_txt;
break;
case LogTimestampPrecision::kMilliSec:
// EXPECT 12:00:00.000
EXPECT_TRUE(pattern_found(console_log_txt, kTimestampMillisecRegex))
<< console_log_txt;
break;
case LogTimestampPrecision::kMicroSec:
// EXPECT 12:00:00.000000
EXPECT_TRUE(pattern_found(console_log_txt, kTimestampMicrosecRegex))
<< console_log_txt;
break;
case LogTimestampPrecision::kNanoSec:
// EXPECT 12:00:00.000000000
EXPECT_TRUE(pattern_found(console_log_txt, kTimestampNanosecRegex))
<< console_log_txt;
break;
}
}
// check the file log if it contains what's expected
std::string file_log_txt =
router.get_full_logfile("mysqlrouter.log", tmp_dir.name());
// strip first line before checking if needed
if (std::mismatch(file_log_txt.begin(), file_log_txt.end(), prefix.begin(),
prefix.end())
.second == prefix.end()) {
file_log_txt.erase(0, file_log_txt.find("\n") + 1);
}
if (test_params.filelog_expected_level != LogLevel::kNotSet) {
switch (test_params.filelog_expected_timestamp_precision) {
case LogTimestampPrecision::kNotSet:
case LogTimestampPrecision::kSec:
// EXPECT 12:00:00
EXPECT_TRUE(pattern_found(file_log_txt, kTimestampSecRegex))
<< file_log_txt;
break;
case LogTimestampPrecision::kMilliSec:
// EXPECT 12:00:00.000
EXPECT_TRUE(pattern_found(file_log_txt, kTimestampMillisecRegex))
<< file_log_txt;
break;
case LogTimestampPrecision::kMicroSec:
// EXPECT 12:00:00.000000
EXPECT_TRUE(pattern_found(file_log_txt, kTimestampMicrosecRegex))
<< file_log_txt;
break;
case LogTimestampPrecision::kNanoSec:
// EXPECT 12:00:00.000000000
EXPECT_TRUE(pattern_found(file_log_txt, kTimestampNanosecRegex))
<< file_log_txt;
break;
}
}
}
#define TS_FR1_1_STR(x) \
"[logger]\n" \
"level=debug\n" \
"sinks=consolelog,filelog\n" \
"timestamp_precision=" x \
"\n" \
"[consolelog]\n\n[filelog]\n\n"
#define TS_FR1_2_STR(x) TS_FR1_1_STR(x)
#define TS_FR1_3_STR(x) TS_FR1_1_STR(x)
INSTANTIATE_TEST_CASE_P(
LoggingConfigTimestampPrecisionTest,
RouterLoggingTestTimestampPrecisionConfig,
::testing::Values(
// no logger section, no sinks sections
// logging_folder not empty so we are expected to log to the file
// with a warning level so info and debug logs will not be there
/*0*/ LoggingConfigOkParams(
"",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kNotSet,
/* filelog_expected_level = */ LogLevel::kWarning,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kNotSet,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kNotSet),
// Two sinks, common timestamp_precision
/*** TS_FR1_1 ***/
/*1*/ /*TS_FR1_1.1*/
LoggingConfigOkParams(
TS_FR1_1_STR("second"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec),
/*2*/ /*TS_FR1_1.2*/
LoggingConfigOkParams(
TS_FR1_1_STR("Second"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec),
/*3*/ /*TS_FR1_1.3*/
LoggingConfigOkParams(
TS_FR1_1_STR("sec"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec),
/*4*/ /*TS_FR1_1.4*/
LoggingConfigOkParams(
TS_FR1_1_STR("SEC"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec),
/*5*/ /*TS_FR1_1.5*/
LoggingConfigOkParams(
TS_FR1_1_STR("s"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec),
/*6*/ /*TS_FR1_1.6*/
LoggingConfigOkParams(
TS_FR1_1_STR("S"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec),
/*** TS_FR1_2 ***/
/*7*/ /*TS_FR1_2.1*/
LoggingConfigOkParams(
TS_FR1_2_STR("millisecond"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec),
/*8*/ /*TS_FR1_2.2*/
LoggingConfigOkParams(
TS_FR1_2_STR("MILLISECOND"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec),
/*9*/ /*TS_FR1_2.3*/
LoggingConfigOkParams(
TS_FR1_2_STR("msec"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec),
/*10*/ /*TS_FR1_2.4*/
LoggingConfigOkParams(
TS_FR1_2_STR("MSEC"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec),
/*11*/ /*TS_FR1_2.5*/
LoggingConfigOkParams(
TS_FR1_2_STR("ms"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec),
/*12*/ /*TS_FR1_2.6*/
LoggingConfigOkParams(
TS_FR1_2_STR("MS"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec),
/*** TS_FR1_3 ***/
/*13*/ /*TS_FR1_3.1*/
LoggingConfigOkParams(
TS_FR1_3_STR("microsecond"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec),
/*14*/ /*TS_FR1_3.2*/
LoggingConfigOkParams(
TS_FR1_3_STR("Microsecond"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec),
/*15*/ /*TS_FR1_3.3*/
LoggingConfigOkParams(
TS_FR1_3_STR("usec"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec),
/*16*/ /*TS_FR1_3.4*/
LoggingConfigOkParams(
TS_FR1_3_STR("UsEC"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec),
/*17*/ /*TS_FR1_3.5*/
LoggingConfigOkParams(
TS_FR1_3_STR("us"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec),
/*18*/ /*TS_FR1_3.5*/
LoggingConfigOkParams(
TS_FR1_3_STR("US"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMicroSec),
/*** TS_FR1_4 ***/
/*19*/ /*TS_FR1_4.1*/
LoggingConfigOkParams(
TS_FR1_3_STR("nanosecond"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec),
/*20*/ /*TS_FR1_4.2*/
LoggingConfigOkParams(
TS_FR1_3_STR("NANOSECOND"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec),
/*21*/ /*TS_FR1_4.3*/
LoggingConfigOkParams(
TS_FR1_3_STR("nsec"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec),
/*22*/ /*TS_FR1_4.4*/
LoggingConfigOkParams(
TS_FR1_3_STR("nSEC"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec),
/*23*/ /*TS_FR1_4.5*/
LoggingConfigOkParams(
TS_FR1_3_STR("ns"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec),
/*24*/ /*TS_FR1_4.6*/
LoggingConfigOkParams(
TS_FR1_3_STR("NS"),
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec),
/*25*/ /*TS_FR4_2*/
LoggingConfigOkParams(
"[logger]\n"
"level=debug\n"
"sinks=filelog\n"
"[filelog]\n"
"timestamp_precision=ms\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kNotSet,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kNotSet,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kMilliSec),
/*26*/ /*TS_FR4_3*/
LoggingConfigOkParams(
"[logger]\n"
"level=debug\n"
"sinks=filelog,consolelog\n"
"[consolelog]\n"
"timestamp_precision=ns\n",
/* logging_folder_empty = */ false,
/* consolelog_expected_level = */ LogLevel::kDebug,
/* filelog_expected_level = */ LogLevel::kDebug,
/* consolelog_expected_timestamp_precision = */
LogTimestampPrecision::kNanoSec,
/* filelog_expected_timestamp_precision = */
LogTimestampPrecision::kSec)));
INSTANTIATE_TEST_CASE_P(
LoggingConfigTimestampPrecisionError, RouterLoggingConfigError,
::testing::Values(
// Unknown timestamp_precision value in a sink
/*0*/ /*TS_FR3_1*/ LoggingConfigErrorParams(
"[logger]\n"
"sinks=consolelog\n"
"[consolelog]\n"
"timestamp_precision=unknown\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"Configuration error: Timestamp precision 'unknown' is not valid. "
"Valid values are: microsecond, millisecond, ms, msec, nanosecond, "
"ns, nsec, s, sec, second, us, and usec"),
// Unknown timestamp_precision value in the [logger] section
/*1*/ /*TS_FR3_1*/
LoggingConfigErrorParams(
"[logger]\n"
"sinks=consolelog,filelog\n"
"timestamp_precision=unknown\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"Configuration error: Timestamp precision 'unknown' is not valid. "
"Valid values are: microsecond, millisecond, ms, msec, nanosecond, "
"ns, nsec, s, sec, second, us, and usec"),
/*2*/ /*TS_FR4_1*/
LoggingConfigErrorParams("[logger]\n"
"sinks=consolelog,filelog\n"
"timestamp_precision=ms\n"
"timestamp_precision=ns\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"Configuration error: Option "
"'timestamp_precision' already defined.")));
#ifndef _WIN32
INSTANTIATE_TEST_CASE_P(
LoggingConfigTimestampPrecisionErrorUnix, RouterLoggingConfigError,
::testing::Values(
/*0*/ /* TS_HLD_1 */
LoggingConfigErrorParams("[logger]\n"
"sinks=syslog\n"
"[syslog]\n"
"timestamp_precision=ms\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"Configuration error: timestamp_precision not "
"valid for 'syslog'")));
#else
INSTANTIATE_TEST_CASE_P(
LoggingConfigTimestampPrecisionErrorWindows, RouterLoggingConfigError,
::testing::Values(
/*0*/ /* TS_HLD_3 */
LoggingConfigErrorParams("[logger]\n"
"sinks=eventlog\n"
"[eventlog]\n"
"timestamp_precision=ms\n",
/* logging_folder_empty = */ false,
/* expected_error = */
"Configuration error: timestamp_precision not "
"valid for 'eventlog'")));
#endif
TEST_F(RouterLoggingTest, very_long_router_name_gets_properly_logged) {
// This test verifies that a very long router name gets truncated in the
// logged message (this is done because if it doesn't happen, the entire
// message will exceed log message max length, and then the ENTIRE message
// will get truncated instead. It's better to truncate the long name rather
// than the stuff that follows it).
// Router should report the error on STDERR and exit
const std::string json_stmts = get_data_dir().join("bootstrap.js").str();
TempDirectory bootstrap_dir;
const auto server_port = port_pool_.get_next_available();
// launch mock server and wait for it to start accepting connections
auto &server_mock = launch_mysql_server_mock(json_stmts, server_port);
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
constexpr char name[] =
"veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
"veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
"veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
"veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
"verylongname";
static_assert(
sizeof(name) > 255,
"too long"); // log message max length is 256, we want something that
// guarrantees the limit would be exceeded
// launch the router in bootstrap mode
auto &router = launch_router(
{
"--bootstrap=127.0.0.1:" + std::to_string(server_port),
"--name",
name,
"-d",
bootstrap_dir.name(),
},
1);
// add login hook
router.register_response("Please enter MySQL password for root: ",
"fake-pass\n");
// wait for router to exit
check_exit_code(router, EXIT_FAILURE);
// expect something like this to appear on STDERR
// Error: Router name
// 'veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryv...'
// too long (max 255).
const std::string out = router.get_full_output();
EXPECT_THAT(out.c_str(),
HasSubstr("Error: Router name "
"'veryveryveryveryveryveryveryveryveryveryveryveryveryv"
"eryveryveryveryveryveryv...' too long (max 255)."));
}
/**
* @test verify that debug logs are not written to console during boostrap if
* bootstrap configuration file is not provided.
*/
TEST_F(RouterLoggingTest, is_debug_logs_disabled_if_no_bootstrap_config_file) {
const std::string json_stmts = get_data_dir().join("bootstrap.js").str();
TempDirectory bootstrap_dir;
const auto server_port = port_pool_.get_next_available();
// launch mock server and wait for it to start accepting connections
auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
// launch the router in bootstrap mode
auto &router = launch_router({
"--bootstrap=127.0.0.1:" + std::to_string(server_port),
"--report-host",
"dont.query.dns",
"-d",
bootstrap_dir.name(),
});
// add login hook
router.register_response("Please enter MySQL password for root: ",
"fake-pass\n");
// check if the bootstraping was successful
check_exit_code(router, EXIT_SUCCESS);
EXPECT_THAT(router.get_full_output(),
testing::Not(testing::HasSubstr("Executing query:")));
}
/**
* @test verify that debug logs are written to console during boostrap if
* log_level is set to DEBUG in bootstrap configuration file.
*/
TEST_F(RouterLoggingTest, is_debug_logs_enabled_if_bootstrap_config_file) {
const std::string json_stmts = get_data_dir().join("bootstrap.js").str();
TempDirectory bootstrap_dir;
TempDirectory bootstrap_conf;
const auto server_port = port_pool_.get_next_available();
// launch mock server and wait for it to start accepting connections
auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
// launch the router in bootstrap mode
std::string logger_section = "[logger]\nlevel = DEBUG\n";
auto conf_params = get_DEFAULT_defaults();
// we want to log to the console
conf_params["logging_folder"] = "";
std::string conf_file = create_config_file(
bootstrap_conf.name(), logger_section, &conf_params, "bootstrap.conf");
auto &router = launch_router({
"--bootstrap=127.0.0.1:" + std::to_string(server_port),
"--report-host",
"dont.query.dns",
"--force",
"-d",
bootstrap_dir.name(),
"-c",
conf_file,
});
// add login hook
router.register_response("Please enter MySQL password for root: ",
"fake-pass\n");
// check if the bootstraping was successful
check_exit_code(router, EXIT_SUCCESS);
EXPECT_THAT(router.get_full_output(), testing::HasSubstr("Executing query:"));
}
/**
* @test verify that debug logs are written to mysqlrouter.log file during
* bootstrap if loggin_folder is provided in bootstrap configuration file
*/
TEST_F(RouterLoggingTest, is_debug_logs_written_to_file_if_logging_folder) {
const std::string json_stmts = get_data_dir().join("bootstrap.js").str();
TempDirectory bootstrap_dir;
TempDirectory bootstrap_conf;
const auto server_port = port_pool_.get_next_available();
// launch mock server and wait for it to start accepting connections
auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
// create config with logging_folder set to that directory
std::map<std::string, std::string> params = {{"logging_folder", ""}};
params.at("logging_folder") = bootstrap_conf.name();
TempDirectory conf_dir("conf");
const std::string conf_file =
create_config_file(conf_dir.name(), "[logger]\nlevel = DEBUG\n", &params);
auto &router = launch_router({
"--bootstrap=127.0.0.1:" + std::to_string(server_port),
"--report-host",
"dont.query.dns",
"--force",
"-d",
bootstrap_dir.name(),
"-c",
conf_file,
});
// add login hook
router.register_response("Please enter MySQL password for root: ",
"fake-pass\n");
// check if the bootstraping was successful
check_exit_code(router, EXIT_SUCCESS);
auto matcher = [](const std::string &line) -> bool {
return line.find("Executing query:") != line.npos;
};
EXPECT_TRUE(find_in_file(bootstrap_conf.name() + "/mysqlrouter.log", matcher,
std::chrono::milliseconds(5000)))
<< router.get_full_logfile("mysqlrouter.log", bootstrap_conf.name());
}
/**
* @test verify that normal output is written to stdout during bootstrap if
* logging_folder is not provided in bootstrap configuration file.
*
* @test verify that logs are not written to stdout during bootstrap.
*/
TEST_F(RouterLoggingTest, bootstrap_normal_logs_written_to_stdout) {
const std::string json_stmts = get_data_dir().join("bootstrap.js").str();
TempDirectory bootstrap_dir;
TempDirectory bootstrap_conf;
const auto server_port = port_pool_.get_next_available();
// launch mock server and wait for it to start accepting connections
auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
// launch the router in bootstrap mode
std::string logger_section = "[logger]\nlevel = DEBUG\n";
auto conf_params = get_DEFAULT_defaults();
// we want to log to the console
conf_params["logging_folder"] = "";
std::string conf_file = create_config_file(
bootstrap_conf.name(), logger_section, &conf_params, "bootstrap.conf");
auto &router = launch_router(
{
"--bootstrap=127.0.0.1:" + std::to_string(server_port),
"--report-host",
"dont.query.dns",
"--force",
"-d",
bootstrap_dir.name(),
"-c",
conf_file,
},
0, /* expected error code */
false /*false = capture only stdout*/);
// add login hook
router.register_response("Please enter MySQL password for root: ",
"fake-pass\n");
// check if the bootstraping was successful
check_exit_code(router, EXIT_SUCCESS);
// check if logs are not written to output
EXPECT_THAT(router.get_full_output(),
testing::Not(testing::HasSubstr("Executing query:")));
// check if normal output is written to output
EXPECT_THAT(router.get_full_output(),
testing::HasSubstr("After this MySQL Router has been started "
"with the generated configuration"));
EXPECT_THAT(router.get_full_output(),
testing::HasSubstr("MySQL Classic protocol"));
EXPECT_THAT(router.get_full_output(), testing::HasSubstr("MySQL X protocol"));
}
class MetadataCacheLoggingTest : public RouterLoggingTest {
protected:
void SetUp() override {
RouterLoggingTest::SetUp();
mysql_harness::DIM &dim = mysql_harness::DIM::instance();
// RandomGenerator
dim.set_RandomGenerator(
[]() {
static mysql_harness::RandomGenerator rg;
return &rg;
},
[](mysql_harness::RandomGeneratorInterface *) {});
cluster_nodes_ports = {port_pool_.get_next_available(),
port_pool_.get_next_available(),
port_pool_.get_next_available()};
cluster_nodes_http_ports = {port_pool_.get_next_available(),
port_pool_.get_next_available(),
port_pool_.get_next_available()};
router_port = port_pool_.get_next_available();
metadata_cache_section = get_metadata_cache_section(cluster_nodes_ports);
routing_section =
get_metadata_cache_routing_section("PRIMARY", "round-robin", "");
}
std::string get_metadata_cache_section(std::vector<uint16_t> ports) {
std::string metadata_caches = "bootstrap_server_addresses=";
for (auto it = ports.begin(); it != ports.end(); ++it) {
metadata_caches += (it == ports.begin()) ? "" : ",";
metadata_caches += "mysql://localhost:" + std::to_string(*it);
}
metadata_caches += "\n";
return "[metadata_cache:test]\n"
"router_id=1\n" +
metadata_caches +
"user=mysql_router1_user\n"
"metadata_cluster=test\n"
"connect_timeout=1\n"
"ttl=0.1\n\n";
}
std::string get_metadata_cache_routing_section(const std::string &role,
const std::string &strategy,
const std::string &mode = "") {
std::string result =
"[routing:test_default]\n"
"bind_port=" +
std::to_string(router_port) + "\n" +
"destinations=metadata-cache://test/default?role=" + role + "\n" +
"protocol=classic\n";
if (!strategy.empty())
result += std::string("routing_strategy=" + strategy + "\n");
if (!mode.empty()) result += std::string("mode=" + mode + "\n");
return result;
}
std::string init_keyring_and_config_file(const std::string &conf_dir,
bool log_to_console = false) {
auto default_section = get_DEFAULT_defaults();
init_keyring(default_section, temp_test_dir.name());
default_section["logging_folder"] =
log_to_console ? "" : get_logging_dir().str();
return create_config_file(
conf_dir,
"[logger]\nlevel = DEBUG\n" + metadata_cache_section + routing_section,
&default_section);
}
TempDirectory temp_test_dir;
std::vector<uint16_t> cluster_nodes_ports;
std::vector<uint16_t> cluster_nodes_http_ports;
uint16_t router_port;
std::string metadata_cache_section;
std::string routing_section;
};
/**
* @test verify if error message is logged if router cannot connect to any
* metadata server.
*/
TEST_F(MetadataCacheLoggingTest,
log_error_when_cannot_connect_to_any_metadata_server) {
TempDirectory conf_dir;
// launch the router with metadata-cache configuration
auto &router = ProcessManager::launch_router(
{"-c", init_keyring_and_config_file(conf_dir.name())});
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
// expect something like this to appear on STDERR
// 2017-12-21 17:22:35 metadata_cache ERROR [7ff0bb001700] Failed connecting
// with any of the 3 metadata servers
auto matcher = [](const std::string &line) -> bool {
return line.find("metadata_cache ERROR") != line.npos &&
line.find(
"Failed fetching metadata from any of the 3 metadata servers") !=
line.npos;
};
auto log_file = get_logging_dir();
log_file.append("mysqlrouter.log");
EXPECT_TRUE(
find_in_file(log_file.str(), matcher, std::chrono::milliseconds(5000)))
<< router.get_full_logfile();
}
/**
* @test verify if appropriate warning messages are logged when cannot connect
* to first metadata server, but can connect to another one.
*/
TEST_F(MetadataCacheLoggingTest,
log_warning_when_cannot_connect_to_first_metadata_server) {
TempDirectory conf_dir("conf");
// launch second metadata server
const auto http_port = cluster_nodes_http_ports[1];
auto &server = launch_mysql_server_mock(
get_data_dir().join("metadata_3_nodes_first_not_accessible.js").str(),
cluster_nodes_ports[1], EXIT_SUCCESS, false, http_port);
ASSERT_NO_FATAL_FAILURE(check_port_ready(server, cluster_nodes_ports[1]));
EXPECT_TRUE(MockServerRestClient(http_port).wait_for_rest_endpoint_ready());
set_mock_metadata(http_port, "", cluster_nodes_ports);
// launch the router with metadata-cache configuration
auto &router = ProcessManager::launch_router(
{"-c", init_keyring_and_config_file(conf_dir.name())});
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
// expect something like this to appear on STDERR
// 2017-12-21 17:22:35 metadata_cache WARNING [7ff0bb001700] Failed connecting
// with Metadata Server 127.0.0.1:7002: Can't connect to MySQL server on
// '127.0.0.1' (111) (2003)
auto info_matcher = [&](const std::string &line) -> bool {
return line.find("metadata_cache WARNING") != line.npos &&
line.find("Failed connecting with Metadata Server 127.0.0.1:" +
std::to_string(cluster_nodes_ports[0])) != line.npos;
};
EXPECT_TRUE(find_in_file(get_logging_dir().str() + "/mysqlrouter.log",
info_matcher, 10000ms))
<< router.get_full_logfile();
auto warning_matcher = [](const std::string &line) -> bool {
return line.find("metadata_cache WARNING") != line.npos &&
line.find(
"While updating metadata, could not establish a connection to "
"replicaset") != line.npos;
};
EXPECT_TRUE(find_in_file(get_logging_dir().str() + "/mysqlrouter.log",
warning_matcher, 10000ms))
<< router.get_full_logfile();
}
#ifndef _WIN32
/**
* @test Checks that the logs rotation works (meaning Router will recreate
* it's log file when it was moved and HUP singnal was sent to the Router).
*/
TEST_F(MetadataCacheLoggingTest, log_rotation_by_HUP_signal) {
TempDirectory conf_dir;
// launch the router with metadata-cache configuration
auto &router = ProcessManager::launch_router(
{"-c", init_keyring_and_config_file(conf_dir.name())});
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
std::this_thread::sleep_for(500ms);
auto log_file = get_logging_dir();
log_file.append("mysqlrouter.log");
EXPECT_TRUE(log_file.exists());
// now let's simulate what logrotate script does
// move the log_file appending '.1' to its name
auto log_file_1 = get_logging_dir();
log_file_1.append("mysqlrouter.log.1");
mysqlrouter::rename_file(log_file.str(), log_file_1.str());
const auto pid = static_cast<pid_t>(router.get_pid());
::kill(pid, SIGHUP);
// let's wait until something new gets logged (metadata cache TTL has
// expired), to be sure the default file that we moved is back.
// Now both old and new files should exist
unsigned retries = 10;
const auto kSleep = 100ms;
do {
std::this_thread::sleep_for(kSleep);
} while ((--retries > 0) && !log_file.exists());
EXPECT_TRUE(log_file.exists()) << router.get_full_logfile();
EXPECT_TRUE(log_file_1.exists());
}
/**
* @test Checks that the Router continues to log to the file when the
* SIGHUP gets sent to it and no file replacement is done.
*/
TEST_F(MetadataCacheLoggingTest, log_rotation_by_HUP_signal_no_file_move) {
TempDirectory conf_dir;
// launch the router with metadata-cache configuration
auto &router = ProcessManager::launch_router(
{"-c", init_keyring_and_config_file(conf_dir.name())});
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
std::this_thread::sleep_for(500ms);
auto log_file = get_logging_dir();
log_file.append("mysqlrouter.log");
EXPECT_TRUE(log_file.exists());
// grab the current log content
const std::string log_content = router.get_full_logfile();
// send the log-rotate signal
const auto pid = static_cast<pid_t>(router.get_pid());
::kill(pid, SIGHUP);
// wait until something new gets logged;
std::string log_content_2;
unsigned step = 0;
do {
std::this_thread::sleep_for(100ms);
log_content_2 = router.get_full_logfile();
} while ((log_content_2 == log_content) && (step++ < 20));
// The logfile should still exist
EXPECT_TRUE(log_file.exists());
// It should still contain what was there before and more (Router should keep
// logging)
EXPECT_THAT(log_content_2, StartsWith(log_content));
EXPECT_STRNE(log_content_2.c_str(), log_content.c_str());
}
/**
* @test Checks that the logs Router continues to log to the file when the
* SIGHUP gets sent to it and no file replacement is done.
*/
TEST_F(MetadataCacheLoggingTest, log_rotation_when_router_restarts) {
TempDirectory conf_dir;
// launch the router with metadata-cache configuration
auto &router = ProcessManager::launch_router(
{"-c", init_keyring_and_config_file(conf_dir.name())});
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
std::this_thread::sleep_for(500ms);
auto log_file = get_logging_dir();
log_file.append("mysqlrouter.log");
EXPECT_TRUE(log_file.exists());
// now stop the router
int res = router.kill();
EXPECT_EQ(EXIT_SUCCESS, res) << router.get_full_output();
// move the log_file appending '.1' to its name
auto log_file_1 = get_logging_dir();
log_file_1.append("mysqlrouter.log.1");
mysqlrouter::rename_file(log_file.str(), log_file_1.str());
// make the new file read-only
chmod(log_file_1.c_str(), S_IRUSR);
// start the router again and check that the new log file got created
auto &router2 = ProcessManager::launch_router(
{"-c", init_keyring_and_config_file(conf_dir.name())});
ASSERT_NO_FATAL_FAILURE(check_port_ready(router2, router_port, 10000ms));
std::this_thread::sleep_for(500ms);
EXPECT_TRUE(log_file.exists());
}
/**
* @test Checks that the logs Router continues to log to the file when the
* SIGHUP gets sent to it and no file replacement is done.
*/
TEST_F(MetadataCacheLoggingTest, log_rotation_read_only) {
TempDirectory conf_dir;
// launch the router with metadata-cache configuration
auto &router = ProcessManager::launch_router(
{"-c", init_keyring_and_config_file(conf_dir.name())}, EXIT_FAILURE);
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
auto log_file = get_logging_dir();
log_file.append("mysqlrouter.log");
unsigned retries = 5;
const auto kSleep = 100ms;
do {
std::this_thread::sleep_for(kSleep);
} while ((--retries > 0) && !log_file.exists());
EXPECT_TRUE(log_file.exists());
// move the log_file appending '.1' to its name
auto log_file_1 = get_logging_dir();
log_file_1.append("mysqlrouter.log.1");
mysqlrouter::rename_file(log_file.str(), log_file_1.str());
// "manually" recreate the log file and make it read only
{
std::ofstream logf(log_file.str());
EXPECT_TRUE(logf.good());
}
chmod(log_file.c_str(), S_IRUSR);
// send the log-rotate signal
const auto pid = static_cast<pid_t>(router.get_pid());
::kill(pid, SIGHUP);
// we expect the router to exit,
// as the logfile is no longer usable it will fallback to logging to the
// stderr
check_exit_code(router, EXIT_FAILURE);
EXPECT_THAT(router.get_full_output(),
HasSubstr("File exists, but cannot open for writing"));
EXPECT_THAT(router.get_full_output(), HasSubstr("Unloading all plugins."));
}
/**
* @test Checks that the logs rotation does not cause any crash in case of
* not logging to the file (logging_foler empty == logging to the std:cerr)
*/
TEST_F(MetadataCacheLoggingTest, log_rotation_stdout) {
TempDirectory conf_dir;
// launch the router with metadata-cache configuration
auto &router = ProcessManager::launch_router(
{"-c",
init_keyring_and_config_file(conf_dir.name(), /*log_to_console=*/true)});
ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
std::this_thread::sleep_for(200ms);
const auto pid = static_cast<pid_t>(router.get_pid());
::kill(pid, SIGHUP);
std::this_thread::sleep_for(200ms);
}
#endif
int main(int argc, char *argv[]) {
init_windows_sockets();
ProcessManager::set_origin(Path(argv[0]).dirname());
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}