1
0
mirror of https://github.com/codership/wsrep-lib.git synced 2025-04-19 21:02:17 +03:00
wsrep-lib/test/reporter_test.cpp
Teemu Ollakka de3d7b63ea Add report_event() method into reporter object
Report event will write json formatted event into report
file.

Include Boost headers as system headers to avoid generating
excessive warnings. Enable extra tests for selected compilers
in actions.
2022-12-05 17:05:14 +02:00

656 lines
19 KiB
C++

/*
* Copyright (C) 2021 Codership Oy <info@codership.com>
*
* This file is part of wsrep-lib.
*
* Wsrep-lib is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Wsrep-lib 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 wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
*/
#include "wsrep/reporter.hpp"
#include <boost/test/unit_test.hpp>
#include <boost/json/src.hpp>
#include <fstream>
#include <deque>
#include <vector>
#include <unistd.h> // unlink() for cleanup
namespace json = boost::json;
//////// HELPERS ///////
static inline double
timestamp()
{
struct timespec time;
clock_gettime(CLOCK_REALTIME, &time);
return (double(time.tv_sec) + double(time.tv_nsec)*1.0e-9);
}
static std::string make_progress_string(int const from, int const to,
int const total,int const done,
int const indefinite)
{
std::ostringstream os;
os << "{ \"from\": " << from << ", "
<< "\"to\": " << to << ", "
<< "\"total\": " << total << ", "
<< "\"done\": " << done << ", "
<< "\"indefinite\": " << indefinite << " }";
return os.str();
}
static json::value
read_file(const char* const filename)
{
std::ifstream input(filename, std::ios::binary);
std::vector<char> buffer(std::istreambuf_iterator<char>(input), {});
json::stream_parser parser;
json::error_code err;
parser.write(buffer.data(), buffer.size(), err);
if (err)
{
assert(0);
return nullptr;
}
parser.finish(err);
if (err)
{
assert(0);
return nullptr;
}
return parser.release();
}
struct logs
{
std::deque<double> tstamp_;
std::deque<std::string> msg_;
};
struct progress
{
int from_;
int to_;
int total_;
int done_;
int indefinite_;
bool indefinite() const { return total_ == indefinite_; }
bool steady() const { return total_ == 0; }
progress(int const from, int const to, int const total, int const done,
int const indefinite)
: from_(from)
, to_(to)
, total_(total)
, done_(done)
, indefinite_(indefinite)
{}
};
static bool
operator==(const progress& left, const progress& right)
{
if (left.indefinite() && right.indefinite()) return true;
if (left.steady() && right.steady()) return true;
return (left.from_ == right.from_ &&
left.to_ == right.to_ &&
left.total_ == right.total_ &&
left.done_ == right.done_ &&
left.indefinite_ == right.indefinite_);
}
static bool
operator!=(const progress& left, const progress& right)
{
return !(left == right);
}
static std::ostream&
operator<<(std::ostream& os, const progress& p)
{
os << make_progress_string(p.from_, p.to_, p.total_, p.done_,p.indefinite_);
return os;
}
struct result
{
logs errors_;
logs warnings_;
struct
{
std::string state_;
std::string comment_;
progress progress_;
} status_;
};
static void
parse_result(const json::value& value, struct result& res,
const std::string& path = "")
{
//std::cout << "Parsing " << path << ": " << value << ": " << value.kind() << std::endl;
switch (value.kind())
{
case json::kind::object:
{
auto const obj(value.get_object());
if (!obj.empty())
{
for (auto it = obj.begin(); it != obj.end(); ++it)
{
std::string const key(it->key().data(), it->key().length());
parse_result(it->value(), res, path + "." + key);
}
}
return;
}
case json::kind::array:
{
auto const arr(value.get_array());
if (!arr.empty())
{
for (auto it = arr.begin(); it != arr.end(); ++it)
{
parse_result(*it, res, path + ".[]");
}
}
return;
}
case json::kind::string:
{
auto const val(value.get_string().c_str());
if (path == ".errors.[].msg")
{
res.errors_.msg_.push_back(val);
}
else if (path == ".warnings.[].msg")
{
res.warnings_.msg_.push_back(val);
}
else if (path == ".status.state")
{
res.status_.state_ = val;
}
else if (path == ".status.comment")
{
res.status_.comment_ = val;
}
return;
}
case json::kind::uint64:
WSREP_FALLTHROUGH;
case json::kind::int64:
if (path == ".status.progress.from")
{
res.status_.progress_.from_ = int(value.get_int64());
}
else if (path == ".status.progress.to")
{
res.status_.progress_.to_ = int(value.get_int64());
}
else if (path == ".status.progress.total")
{
res.status_.progress_.total_ = int(value.get_int64());
}
else if (path == ".status.progress.done")
{
res.status_.progress_.done_ = int(value.get_int64());
}
else if (path == ".status.progress.indefinite")
{
res.status_.progress_.indefinite_ = int(value.get_int64());
}
return;
case json::kind::double_:
if (path == ".errors.[].timestamp")
{
res.errors_.tstamp_.push_back(value.get_double());
}
else if (path == ".warnings.[].timestamp")
{
res.warnings_.tstamp_.push_back(value.get_double());
}
return;
case json::kind::bool_:
return;
case json::kind::null:
return;
}
assert(0);
}
static bool
equal(const std::string& left, const std::string& right)
{
return left == right;
}
static bool
equal(double const left, double const right)
{
return ::fabs(left - right) < 0.0001; // we are looking for ms precision
}
template <typename T>
static bool
operator!=(const std::deque<T>& left, const std::deque<T>& right)
{
if (left.size() != right.size()) return true;
for (size_t i(0); i < left.size(); ++i)
if (!equal(left[i], right[i])) return true;
return false;
}
static bool
operator!=(const logs& left, const logs& right)
{
if (left.tstamp_ != right.tstamp_) return true;
return (left.msg_ != right.msg_);
}
static bool
operator==(const result& left, const result& right)
{
if (left.errors_ != right.errors_) return false;
if (left.warnings_ != right.warnings_) return false;
if (left.status_.state_ != right.status_.state_) return false;
if (left.status_.comment_ != right.status_.comment_) return false;
if (left.status_.progress_ != right.status_.progress_) return false;
return true;
}
template <typename T>
static void
print_deque(std::ostream& os,const std::deque<T> left,const std::deque<T> right)
{
auto const max(std::max(left.size(), right.size()));
for (size_t i(0); i < max; ++i)
{
os << "|\t'";
if (i < left.size())
os << left[i] << "'";
else
os << "'\t";
if (i < right.size())
os << "\t'" << right[i] << "'";
else
os << "\t''";
if (!equal(left[i], right[i])) os << "\t!!!";
os << "\n";
}
}
static void
print_logs(std::ostream& os, const logs& left, const logs& right)
{
os << "|\t" << left.msg_.size() << "\t" << right.msg_.size() << "\n";
print_deque(os, left.tstamp_, right.tstamp_);
print_deque(os, left.msg_, right.msg_);
}
// print two results against each other
template <typename Iteration>
static std::string
print(const result& left, const result& right, Iteration it)
{
std::ostringstream os;
os << std::showpoint << std::setprecision(18);
os << "Iteration " << it << "\nerrors:\n";
print_logs(os, left.errors_, right.errors_);
os << "warnings:\n";
print_logs(os, left.warnings_, right.warnings_);
os << "state:\n";
os << "\t" << left.status_.state_ << "\t" << right.status_.state_
<< "\n";
os << "\t" << left.status_.comment_ << "\t" << right.status_.comment_
<< "\n";
os << "\t" << left.status_.progress_ << "\t" << right.status_.progress_
<< "\n";
return os.str();
}
#define VERIFY_RESULT(left, right, it) \
BOOST_CHECK_MESSAGE(left == right, print(left, right, it));
static const char*
const REPORT = "report.json";
static progress
const indefinite(-1, -1, -1, -1, -1);
static progress
const steady(-1, -1, 0, 0, -1);
static struct logs
const LOGS_INIT = { std::deque<double>(), std::deque<std::string>() };
static struct result
const RES_INIT = { LOGS_INIT, LOGS_INIT,
{ "DISCONNECTED", "Disconnected", indefinite } };
template <typename Iteration>
static void
test_log(const char* const fname,
wsrep::reporter& rep,
result& check,
wsrep::reporter::log_level const lvl,
double const tstamp,
const std::string& msg,
Iteration const iteration)
{
// this is implementaiton detail, if it changes in the code, it needs
// to be changed here
size_t const MAX_ERROR(4);
logs& log(lvl == wsrep::reporter::error ? check.errors_ : check.warnings_);
log.tstamp_.push_back(tstamp);
if (log.tstamp_.size() > MAX_ERROR) log.tstamp_.pop_front();
log.msg_.push_back(msg);
if (log.msg_.size() > MAX_ERROR) log.msg_.pop_front();
rep.report_log_msg(lvl, msg, tstamp);
auto value = read_file(fname);
auto res = RES_INIT;
parse_result(value, res);
VERIFY_RESULT(res, check, iteration);
}
static size_t const MAX_MSG = 4;
struct reporter_fixture
{
wsrep::default_mutex mutex{};
wsrep::reporter rep{mutex, REPORT, MAX_MSG};
};
BOOST_FIXTURE_TEST_CASE(log_msg_test, reporter_fixture)
{
auto value = read_file(REPORT);
BOOST_REQUIRE(value != nullptr);
struct result res(RES_INIT), check(RES_INIT);
parse_result(value, res);
VERIFY_RESULT(res, check, -1);
struct entry
{
double tstamp_;
std::string msg_;
};
std::vector<entry> msgs =
{
{ 0.1, "a" },
{ 0.2, "bb" },
{ 0.3, "ccc" },
{ 0.4, "dddd" },
{ 0.5, "eeeee" },
{ 0.6, "ffffff" }
};
for (size_t i(0); i < msgs.size(); ++i)
{
test_log(REPORT, rep, check,
wsrep::reporter::error, msgs[i].tstamp_, msgs[i].msg_, i);
test_log(REPORT, rep, check,
wsrep::reporter::warning, msgs[i].tstamp_, msgs[i].msg_, i);
}
// test indefinite timestmap
std::string const msg("err");
rep.report_log_msg(wsrep::reporter::error, msg);
value = read_file(REPORT);
res = RES_INIT;
parse_result(value, res);
BOOST_REQUIRE(res.errors_.tstamp_.back() > 0);
BOOST_REQUIRE(res.errors_.msg_.back() == msg);
::unlink(REPORT);
}
BOOST_FIXTURE_TEST_CASE(state_test, reporter_fixture)
{
using wsrep::server_state;
double const err_tstamp(timestamp());
std::string const err_msg("Error!");
struct test
{
struct
{
enum wsrep::server_state::state state;
float progress;
} input;
struct result output;
};
logs const ERRS_INIT = { {err_tstamp}, {err_msg} };
std::vector<test> tests =
{
{{ server_state::s_disconnected, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTED", "Disconnected", indefinite }}},
{{ server_state::s_initializing, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTED", "Initializing", indefinite }}},
{{ server_state::s_initialized, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTED", "Connecting", indefinite }}},
{{ server_state::s_connected, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "CONNECTED", "Waiting", indefinite }}},
{{ server_state::s_joiner, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINING", "Receiving state", indefinite }}},
{{ server_state::s_disconnecting, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTING", "Disconnecting", indefinite }}},
{{ server_state::s_disconnected, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTED", "Disconnected", indefinite }}},
{{ server_state::s_connected, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "CONNECTED", "Waiting", indefinite }}},
{{ server_state::s_joiner, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINING", "Receiving SST", indefinite }}},
{{ server_state::s_initializing, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINING", "Initializing", indefinite }}},
{{ server_state::s_initialized, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINING", "Receiving IST", indefinite }}},
{{ server_state::s_joined, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINED", "Syncing", indefinite }}},
{{ server_state::s_synced, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "SYNCED", "Operational", steady }}},
{{ server_state::s_donor, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DONOR", "Donating SST", indefinite }}},
{{ server_state::s_joined, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "JOINED", "Syncing", indefinite }}},
{{ server_state::s_synced, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "SYNCED", "Operational", steady }}},
{{ server_state::s_disconnecting, 0 },
{ ERRS_INIT, LOGS_INIT,
{ "DISCONNECTING", "Disconnecting", indefinite }}},
};
rep.report_log_msg(wsrep::reporter::error, err_msg, err_tstamp);
for (auto i(tests.begin()); i != tests.end(); ++i)
{
rep.report_state(i->input.state);
auto value = read_file(REPORT);
result res(RES_INIT);
parse_result(value, res);
auto check(i->output);
VERIFY_RESULT(res, check, i - tests.begin());
}
::unlink(REPORT);
}
BOOST_FIXTURE_TEST_CASE(progress_test, reporter_fixture)
{
using wsrep::server_state;
wsrep::default_mutex m;
wsrep::reporter rep(m, REPORT, MAX_MSG);
double const warn_tstamp(timestamp());
std::string const warn_msg("Warn!");
static progress const progress5_0(-1, -1, 5, 0, -1);
static progress const progress5_2(-1, -1, 5, 2, -1);
static progress const progress5_5(-1, -1, 5, 5, -1);
struct test
{
struct
{
enum wsrep::server_state::state state;
int total;
int done;
} input;
struct result output;
};
logs const WARN_INIT = { {warn_tstamp}, {warn_msg} };
std::vector<test> tests =
{
{{ server_state::s_initialized, -1, -1 },
{ LOGS_INIT, WARN_INIT,
{ "DISCONNECTED", "Connecting", indefinite }}},
{{ server_state::s_connected, -1, -1 },
{ LOGS_INIT, WARN_INIT,
{ "CONNECTED", "Waiting", indefinite }}},
{{ server_state::s_joiner, 5, 0 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving state", progress5_0 }}},
{{ server_state::s_joiner, 5, 2 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving state", progress5_2 }}},
{{ server_state::s_disconnected, -1, -1 },
{ LOGS_INIT, WARN_INIT,
{ "DISCONNECTED", "Disconnected", indefinite }}},
{{ server_state::s_connected, -1, -1 },
{ LOGS_INIT, WARN_INIT,
{ "CONNECTED", "Waiting", indefinite }}},
{{ server_state::s_joiner, 5, 0 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving SST", progress5_0 }}},
{{ server_state::s_joiner, 5, 2 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving SST", progress5_2 }}},
{{ server_state::s_joiner, 5, 5 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving SST", progress5_5 }}},
{{ server_state::s_initializing, -1, -1 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Initializing", indefinite }}},
{{ server_state::s_initializing, 5, 2 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Initializing", progress5_2 }}},
{{ server_state::s_initialized, 5, 0 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving IST", progress5_0 }}},
{{ server_state::s_initialized, 5, 5 },
{ LOGS_INIT, WARN_INIT,
{ "JOINING", "Receiving IST", progress5_5 }}},
{{ server_state::s_joined, -1, -1 },
{ LOGS_INIT, WARN_INIT,
{ "JOINED", "Syncing", indefinite }}},
{{ server_state::s_joined, 5, 2 },
{ LOGS_INIT, WARN_INIT,
{ "JOINED", "Syncing", progress5_2 }}},
{{ server_state::s_synced, -1, -1 },
{ LOGS_INIT, WARN_INIT,
{ "SYNCED", "Operational", steady }}},
{{ server_state::s_donor, -1, -1 },
{ LOGS_INIT, WARN_INIT,
{ "DONOR", "Donating SST", indefinite }}},
{{ server_state::s_donor, 5, 0 },
{ LOGS_INIT, WARN_INIT,
{ "DONOR", "Donating SST", progress5_0 }}},
{{ server_state::s_joined, -1, -1 },
{ LOGS_INIT, WARN_INIT,
{ "JOINED", "Syncing", indefinite }}},
{{ server_state::s_synced, -1, -1 },
{ LOGS_INIT, WARN_INIT,
{ "SYNCED", "Operational", steady }}},
};
rep.report_log_msg(wsrep::reporter::warning, warn_msg, warn_tstamp);
for (auto i(tests.begin()); i != tests.end(); ++i)
{
rep.report_state(i->input.state);
if (i->input.total >= 0)
rep.report_progress(make_progress_string(-1, -1,
i->input.total, i->input.done,
-1));
auto value = read_file(REPORT);
result res(RES_INIT);
parse_result(value, res);
auto check(i->output);
VERIFY_RESULT(res, check, i - tests.begin());
}
::unlink(REPORT);
}
BOOST_FIXTURE_TEST_CASE(event_test, reporter_fixture)
{
rep.report_event("{\"msg\": \"message\"}");
auto value = read_file(REPORT);
BOOST_REQUIRE(value.at("events").is_array());
auto event_array = value.at("events").as_array();
BOOST_REQUIRE(event_array.size() == 1);
auto event = event_array[0];
BOOST_REQUIRE(event.is_object());
BOOST_REQUIRE(event.at("timestamp").is_double());
BOOST_REQUIRE(event.at("event").is_object());
BOOST_REQUIRE(event.at("event").at("msg").is_string());
BOOST_REQUIRE(event.at("event").at("msg").as_string() == "message");
::unlink(REPORT);
}