#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { namespace detail { std::string fmtdur(typename clock_type::duration const& d) { using namespace std::chrono; auto const ms = duration_cast(d); if (ms < seconds{1}) return boost::lexical_cast(ms.count()) + "ms"; std::stringstream ss; ss << std::fixed << std::setprecision(1) << (ms.count() / 1000.) << "s"; return ss.str(); } //------------------------------------------------------------------------------ void suite_results::add(case_results const& r) { ++cases; total += r.total; failed += r.failed; } //------------------------------------------------------------------------------ void results::add(suite_results const& r) { ++suites; total += r.total; cases += r.cases; failed += r.failed; auto const elapsed = clock_type::now() - r.start; if (elapsed >= std::chrono::seconds{1}) { auto const iter = std::lower_bound( top.begin(), top.end(), elapsed, [](run_time const& t1, typename clock_type::duration const& t2) { return t1.second > t2; }); if (iter != top.end()) { if (top.size() == max_top && iter == top.end() - 1) { // avoid invalidating the iterator *iter = run_time{static_string{static_string::string_view_type{r.name}}, elapsed}; } else { if (top.size() == max_top) top.resize(top.size() - 1); top.emplace(iter, static_string{static_string::string_view_type{r.name}}, elapsed); } } else if (top.size() < max_top) { top.emplace_back(static_string{static_string::string_view_type{r.name}}, elapsed); } } } void results::merge(results const& r) { suites += r.suites; total += r.total; cases += r.cases; failed += r.failed; // combine the two top collections boost::container::static_vector top_result; top_result.resize(top.size() + r.top.size()); std::merge( top.begin(), top.end(), r.top.begin(), r.top.end(), top_result.begin(), [](run_time const& t1, run_time const& t2) { return t1.second > t2.second; }); if (top_result.size() > max_top) top_result.resize(max_top); top = top_result; } template void results::print(S& s) { using namespace beast::unit_test; if (!top.empty()) { s << "Longest suite times:\n"; for (auto const& [name, dur] : top) s << std::setw(8) << fmtdur(dur) << " " << name << '\n'; } auto const elapsed = clock_type::now() - start; s << fmtdur(elapsed) << ", " << amount{suites, "suite"} << ", " << amount{cases, "case"} << ", " << amount{total, "test"} << " total, " << amount{failed, "failure"} << std::endl; } //------------------------------------------------------------------------------ template std::size_t multi_runner_base::inner::checkout_job_index() { return job_index_++; } template std::size_t multi_runner_base::inner::checkout_test_index() { return test_index_++; } template bool multi_runner_base::inner::any_failed() const { return any_failed_; } template void multi_runner_base::inner::any_failed(bool v) { any_failed_ = any_failed_ || v; } template std::size_t multi_runner_base::inner::tests() const { std::lock_guard const l{m_}; return results_.total; } template std::size_t multi_runner_base::inner::suites() const { std::lock_guard const l{m_}; return results_.suites; } template void multi_runner_base::inner::inc_keep_alive_count() { ++keep_alive_; } template std::size_t multi_runner_base::inner::get_keep_alive_count() { return keep_alive_; } template void multi_runner_base::inner::add(results const& r) { std::lock_guard const l{m_}; results_.merge(r); } template template void multi_runner_base::inner::print_results(S& s) { std::lock_guard const l{m_}; results_.print(s); } template multi_runner_base::multi_runner_base() { try { if (IsParent) { // cleanup any leftover state for any previous failed runs boost::interprocess::shared_memory_object::remove(shared_mem_name_); boost::interprocess::message_queue::remove(message_queue_name_); } shared_mem_ = boost::interprocess::shared_memory_object{ std::conditional_t< IsParent, boost::interprocess::create_only_t, boost::interprocess::open_only_t>{}, shared_mem_name_, boost::interprocess::read_write}; if (IsParent) { shared_mem_.truncate(sizeof(inner)); message_queue_ = std::make_unique( boost::interprocess::create_only, message_queue_name_, /*max messages*/ 16, /*max message size*/ 1 << 20); } else { message_queue_ = std::make_unique( boost::interprocess::open_only, message_queue_name_); } region_ = boost::interprocess::mapped_region{shared_mem_, boost::interprocess::read_write}; if (IsParent) { inner_ = new (region_.get_address()) inner{}; } else { inner_ = reinterpret_cast(region_.get_address()); } } catch (...) { if (IsParent) { boost::interprocess::shared_memory_object::remove(shared_mem_name_); boost::interprocess::message_queue::remove(message_queue_name_); } throw; } } template multi_runner_base::~multi_runner_base() { if (IsParent) { inner_->~inner(); boost::interprocess::shared_memory_object::remove(shared_mem_name_); boost::interprocess::message_queue::remove(message_queue_name_); } } template std::size_t multi_runner_base::checkout_test_index() { return inner_->checkout_test_index(); } template std::size_t multi_runner_base::checkout_job_index() { return inner_->checkout_job_index(); } template bool multi_runner_base::any_failed() const { return inner_->any_failed(); } template void multi_runner_base::any_failed(bool v) { return inner_->any_failed(v); } template void multi_runner_base::add(results const& r) { inner_->add(r); } template void multi_runner_base::inc_keep_alive_count() { inner_->inc_keep_alive_count(); } template std::size_t multi_runner_base::get_keep_alive_count() { return inner_->get_keep_alive_count(); } template template void multi_runner_base::print_results(S& s) { inner_->print_results(s); } template void multi_runner_base::message_queue_send(MessageType mt, std::string const& s) { // must use a mutex since the two "sends" must happen in order std::lock_guard const l{inner_->m_}; message_queue_->send(&mt, sizeof(mt), /*priority*/ 0); message_queue_->send(s.c_str(), s.size(), /*priority*/ 0); } template std::size_t multi_runner_base::tests() const { return inner_->tests(); } template std::size_t multi_runner_base::suites() const { return inner_->suites(); } template void multi_runner_base::add_failures(std::size_t failures) { results results; results.failed += failures; add(results); any_failed(failures != 0); } } // namespace detail namespace test { //------------------------------------------------------------------------------ multi_runner_parent::multi_runner_parent() : os_(std::cout) { message_queue_thread_ = std::thread([this] { std::vector buf(1 << 20); while (this->continue_message_queue_ || this->message_queue_->get_num_msg()) { // let children know the parent is still alive this->inc_keep_alive_count(); if (!this->message_queue_->get_num_msg()) { // If a child does not see the keep alive count incremented, // it will assume the parent has died. This sleep time needs // to be small enough so the child will see increments from // a live parent. std::this_thread::sleep_for(std::chrono::milliseconds(100)); continue; } try { std::size_t recvd_size = 0; unsigned int priority = 0; this->message_queue_->receive(buf.data(), buf.size(), recvd_size, priority); if (!recvd_size) continue; assert(recvd_size == 1); MessageType const mt{*reinterpret_cast(buf.data())}; this->message_queue_->receive(buf.data(), buf.size(), recvd_size, priority); if (recvd_size) { std::string s{buf.data(), recvd_size}; switch (mt) { case MessageType::log: this->os_ << s; this->os_.flush(); break; case MessageType::test_start: running_suites_.insert(std::move(s)); break; case MessageType::test_end: running_suites_.erase(s); break; default: assert(0); // unknown message type } } } catch (std::exception const& e) { std::cerr << "Error: " << e.what() << " reading unit test message queue.\n"; return; } catch (...) { std::cerr << "Unknown error reading unit test message queue.\n"; return; } } }); } multi_runner_parent::~multi_runner_parent() { using namespace beast::unit_test; continue_message_queue_ = false; message_queue_thread_.join(); add_failures(running_suites_.size()); print_results(os_); for (auto const& s : running_suites_) { os_ << "\nSuite: " << s << " failed to complete. The child process may have crashed.\n"; } } bool multi_runner_parent::any_failed() const { return multi_runner_base::any_failed(); } std::size_t multi_runner_parent::tests() const { return multi_runner_base::tests(); } std::size_t multi_runner_parent::suites() const { return multi_runner_base::suites(); } void multi_runner_parent::add_failures(std::size_t failures) { multi_runner_base::add_failures(failures); } //------------------------------------------------------------------------------ multi_runner_child::multi_runner_child(std::size_t num_jobs, bool quiet, bool print_log) : job_index_{checkout_job_index()} , num_jobs_{num_jobs} , quiet_{quiet} , print_log_{!quiet || print_log} { if (num_jobs_ > 1) { keep_alive_thread_ = std::thread([this] { std::size_t last_count = get_keep_alive_count(); while (this->continue_keep_alive_) { // Use a small sleep time so in the normal case the child // process may shutdown quickly. However, to protect against // false alarms, use a longer sleep time later on. std::this_thread::sleep_for(std::chrono::milliseconds(500)); auto cur_count = this->get_keep_alive_count(); if (cur_count == last_count) { // longer sleep time to protect against false alarms std::this_thread::sleep_for(std::chrono::seconds(2)); cur_count = this->get_keep_alive_count(); if (cur_count == last_count) { // assume parent process is no longer alive std::cerr << "multi_runner_child " << job_index_ << ": Assuming parent died, exiting.\n"; std::exit(EXIT_FAILURE); } } last_count = cur_count; } }); } } multi_runner_child::~multi_runner_child() { if (num_jobs_ > 1) { continue_keep_alive_ = false; keep_alive_thread_.join(); } add(results_); } std::size_t multi_runner_child::tests() const { return results_.total; } std::size_t multi_runner_child::suites() const { return results_.suites; } void multi_runner_child::add_failures(std::size_t failures) { results_.failed += failures; any_failed(failures != 0); } void multi_runner_child::on_suite_begin(beast::unit_test::suite_info const& info) { suite_results_ = detail::suite_results{info.full_name()}; message_queue_send(MessageType::test_start, suite_results_.name); } void multi_runner_child::on_suite_end() { if (print_log_ || suite_results_.failed > 0) { std::stringstream s; if (num_jobs_ > 1) s << job_index_ << "> "; s << (suite_results_.failed > 0 ? "failed: " : "") << suite_results_.name << " had " << suite_results_.failed << " failures." << std::endl; message_queue_send(MessageType::log, s.str()); } results_.add(suite_results_); message_queue_send(MessageType::test_end, suite_results_.name); } void multi_runner_child::on_case_begin(std::string const& name) { case_results_ = detail::case_results(name); if (quiet_) return; std::stringstream s; if (num_jobs_ > 1) s << job_index_ << "> "; s << suite_results_.name << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << '\n'; message_queue_send(MessageType::log, s.str()); } void multi_runner_child::on_case_end() { suite_results_.add(case_results_); } void multi_runner_child::on_pass() { ++case_results_.total; } void multi_runner_child::on_fail(std::string const& reason) { ++case_results_.failed; ++case_results_.total; std::stringstream s; if (num_jobs_ > 1) s << job_index_ << "> "; s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ") << reason << '\n'; message_queue_send(MessageType::log, s.str()); } void multi_runner_child::on_log(std::string const& msg) { if (!print_log_) return; std::stringstream s; if (num_jobs_ > 1) s << job_index_ << "> "; s << msg; message_queue_send(MessageType::log, s.str()); } } // namespace test namespace detail { template class multi_runner_base; template class multi_runner_base; } // namespace detail } // namespace xrpl