diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7f317a7..f142389 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -159,19 +159,27 @@ jobs: else export CXX="ccache clang++-${{ matrix.config.version }}" fi - if [ ${{ matrix.config.version }} == "4.8" ] + if [ ${{ matrix.config.CC }} == "gcc" ] && [ ${{ matrix.config.version }} == "4.8" ] then STRICT=OFF DBSIM=OFF + TESTS_EXTRA=OFF + elif [ ${{ matrix.config.CC }} == "gcc" ] && [ ${{ matrix.config.version }} == "5" ] + then + STRICT=ON + DBSIM=ON + TESTS_EXTRA=OFF else STRICT=ON DBSIM=ON + TESTS_EXTRA=ON fi cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.config.type }} \ -DWSREP_LIB_MAINTAINER_MODE:BOOL=ON \ -DWSREP_LIB_STRICT_BUILD_FLAGS:BOOL=$STRICT \ -DWSREP_LIB_WITH_DBSIM:BOOL=$DBSIM \ - -DWSREP_LIB_WITH_ASAN:BOOL=ON . + -DWSREP_LIB_WITH_ASAN:BOOL=ON \ + -DWSREP_LIB_WITH_UNIT_TESTS_EXTRA:BOOL=$TESTS_EXTRA - name: Build working-directory: ${{runner.workspace}}/build diff --git a/CMakeLists.txt b/CMakeLists.txt index 292c413..49d1a12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,7 @@ endif() set(MIN_BOOST_VERSION "1.54.0") if (WSREP_LIB_WITH_UNIT_TESTS) if (WSREP_LIB_WITH_UNIT_TESTS_EXTRA) + message(STATUS "Compiling extra unit tests") set(json_HEADER "boost/json/src.hpp") # Extra tests may require packages from very recent boost which may be # unavailable on the system. In such case download private boost distro. @@ -140,17 +141,14 @@ if (WSREP_LIB_WITH_UNIT_TESTS) PATHS ${WITH_BOOST}/lib/cmake NO_DEFAULT_PATH ) - # Boost 1.76.0 comes very sloppy (and not only in JSON department) - # - need to disable some checks. - set(ADDITIONAL_CXX_FLAGS "-Wno-effc++ -Wno-conversion -Wno-suggest-override -Wno-overloaded-virtual") - set(ADDITIONAL_CXX_INCLUDES ${BOOST_INCLUDE_DIR}) - check_include_file_cxx(${json_HEADER} json_FOUND - "-I${ADDITIONAL_CXX_INCLUDES} ${ADDITIONAL_CXX_FLAGS}" - ) + # Include as system header to be more permissive about generated + # warnings. + set(CMAKE_REQUIRED_FLAGS "-isystem ${BOOST_INCLUDE_DIR}") + check_include_file_cxx(${json_HEADER} json_FOUND) if (NOT json_FOUND) message(FATAL_ERROR "Required header 'boost/json.hpp' not found: ${json_FOUND}") else() - include_directories(SYSTEM ${ADDITIONAL_CXX_INCLUDES}) + include_directories(SYSTEM ${BOOST_INCLUDE_DIR}) endif() endif() else() diff --git a/include/wsrep/reporter.hpp b/include/wsrep/reporter.hpp index 75ed038..3e8c700 100644 --- a/include/wsrep/reporter.hpp +++ b/include/wsrep/reporter.hpp @@ -57,6 +57,15 @@ namespace wsrep */ void report_progress(const std::string& json); + /** + * Report provider event. + * { + * "status": "Status string", + * "message": "Message from the provider" + * } + */ + void report_event(const std::string& json); + enum log_level { error, @@ -100,14 +109,17 @@ namespace wsrep std::deque err_msg_; std::deque warn_msg_; + std::deque events_; size_t const max_msg_; - static void write_log_msgs(std::ostream& os, - const std::string& label, - const std::deque& msgs); static void write_log_msg(std::ostream& os, - const log_msg& msg); - + const log_msg& msg); + static void write_event(std::ostream& os, + const log_msg& msg); + static void write_array(std::ostream& os, const std::string& label, + const std::deque& events, + void (*element_writer)(std::ostream& os, + const log_msg& msg)); substates substate_map(enum server_state::state state); float progress_map(float progress) const; void write_file(double timestamp); diff --git a/src/reporter.cpp b/src/reporter.cpp index 2a5c6f5..57382ea 100644 --- a/src/reporter.cpp +++ b/src/reporter.cpp @@ -72,6 +72,7 @@ wsrep::reporter::reporter(wsrep::mutex& mutex, , initialized_(false) , err_msg_() , warn_msg_() + , events_() , max_msg_(max_msg) { template_[file_name_.length() + TEMP_EXTENSION.length()] = '\0'; @@ -137,6 +138,37 @@ wsrep::reporter::substate_map(enum wsrep::server_state::state const state) } } +// See https://www.ietf.org/rfc/rfc4627.txt +static std::string escape_json(const std::string& str) +{ + std::ostringstream os; + for (auto c = str.cbegin(); c != str.cend(); ++c) + { + switch (*c) + { + case '"': os << "\\\""; break; + case '\\': os << "\\\\"; break; + case '/': os << "\\/"; break; + case '\b': os << "\\b"; break; + case '\f': os << "\\f"; break; + case '\n': os << "\\n"; break; + case '\r': os << "\\r"; break; + case '\t': os << "\\t"; break; + default: + if (0x0 <= *c && *c <= 0x1f) + { + os << "\\u" << std::hex << std::setw(4) << + std::setfill('0') << static_cast(*c); + } + else + { + os << *c; + } + } + } + return os.str(); +} + void wsrep::reporter::write_log_msg(std::ostream& os, const log_msg& msg) @@ -149,14 +181,27 @@ wsrep::reporter::write_log_msg(std::ostream& os, } void -wsrep::reporter::write_log_msgs(std::ostream& os, - const std::string& label, - const std::deque& msgs) +wsrep::reporter::write_event(std::ostream& os, + const log_msg& msg) +{ + os << "\t\t{\n"; + os << "\t\t\t\"timestamp\": " << std::showpoint << std::setprecision(18) + << msg.tstamp << ",\n"; + os << "\t\t\t\"event\": " << msg.msg << "\n"; + os << "\t\t}"; +} + +void +wsrep::reporter::write_array(std::ostream& os, + const std::string& label, + const std::deque& msgs, + void (*element_writer)(std::ostream& os, + const log_msg& msg)) { os << "\t\"" << label << "\": [\n"; for (size_t i(0); i < msgs.size(); ++i) { - write_log_msg(os, msgs[i]); + element_writer(os, msgs[i]); os << (i+1 < msgs.size() ? ",\n" : "\n"); } os << "\t],\n"; @@ -223,8 +268,9 @@ wsrep::reporter::write_file(double const tstamp) os << "\t\"date\": \"" << date_str << "\",\n"; os << "\t\"timestamp\": " << std::showpoint << std::setprecision(18) << tstamp << ",\n"; - write_log_msgs(os, "errors", err_msg_); - write_log_msgs(os, "warnings", warn_msg_); + write_array(os, "errors", err_msg_, write_log_msg); + write_array(os, "warnings", warn_msg_, write_log_msg); + write_array(os, "events", events_, write_event); os << "\t\"status\": {\n"; os << "\t\t\"state\": \"" << strings[state_].state << "\",\n"; os << "\t\t\"comment\": \"" << strings[state_].comment << "\",\n"; @@ -282,6 +328,18 @@ wsrep::reporter::report_progress(const std::string& json) } } +void +wsrep::reporter::report_event(const std::string& json) +{ + wsrep::unique_lock lock(mutex_); + if (events_.size() == max_msg_) + { + events_.pop_front(); + } + events_.push_back({timestamp(), json}); + write_file(timestamp()); +} + void wsrep::reporter::report_log_msg(log_level const lvl, const std::string& msg, @@ -297,7 +355,9 @@ wsrep::reporter::report_log_msg(log_level const lvl, if (tstamp <= undefined) tstamp = timestamp(); - log_msg entry({tstamp, msg}); + /* Log messages are not expected to be json formatted, so we escape + the message strings here to keep the report file well formatted. */ + log_msg entry({tstamp, escape_json(msg)}); deque.push_back(entry); write_file(tstamp); } diff --git a/test/reporter_test.cpp b/test/reporter_test.cpp index bed7a2a..9ee381e 100644 --- a/test/reporter_test.cpp +++ b/test/reporter_test.cpp @@ -320,8 +320,9 @@ print_logs(std::ostream& os, const logs& left, const logs& right) } // print two results against each other +template static std::string -print(const result& left, const result& right, size_t it) +print(const result& left, const result& right, Iteration it) { std::ostringstream os; @@ -360,6 +361,7 @@ static struct result const RES_INIT = { LOGS_INIT, LOGS_INIT, { "DISCONNECTED", "Disconnected", indefinite } }; +template static void test_log(const char* const fname, wsrep::reporter& rep, @@ -367,7 +369,7 @@ test_log(const char* const fname, wsrep::reporter::log_level const lvl, double const tstamp, const std::string& msg, - size_t const iteration) + Iteration const iteration) { // this is implementaiton detail, if it changes in the code, it needs // to be changed here @@ -389,11 +391,14 @@ test_log(const char* const fname, static size_t const MAX_MSG = 4; -BOOST_AUTO_TEST_CASE(log_msg_test) +struct reporter_fixture { - wsrep::default_mutex m; - wsrep::reporter rep(m, REPORT, MAX_MSG); + 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); @@ -435,12 +440,10 @@ BOOST_AUTO_TEST_CASE(log_msg_test) ::unlink(REPORT); } -BOOST_AUTO_TEST_CASE(state_test) +BOOST_FIXTURE_TEST_CASE(state_test, reporter_fixture) { using wsrep::server_state; - wsrep::default_mutex m; - wsrep::reporter rep(m, REPORT, MAX_MSG); double const err_tstamp(timestamp()); std::string const err_msg("Error!"); @@ -526,7 +529,7 @@ BOOST_AUTO_TEST_CASE(state_test) ::unlink(REPORT); } -BOOST_AUTO_TEST_CASE(progress_test) +BOOST_FIXTURE_TEST_CASE(progress_test, reporter_fixture) { using wsrep::server_state; @@ -634,3 +637,19 @@ BOOST_AUTO_TEST_CASE(progress_test) ::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); +}