//------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled Copyright (c) 2017 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== #include #include #include #include #include #include #include namespace ripple { namespace test { extern void incPorts(); 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.size() > 0) { 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 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 l{m_}; results_.merge(r); } template template void multi_runner_base::inner::print_results(S& s) { std::lock_guard 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 l{inner_->m_}; message_queue_->send(&mt, sizeof(mt), /*priority*/ 0); message_queue_->send(s.c_str(), s.size(), /*priority*/ 0); } template constexpr const char* multi_runner_base::shared_mem_name_; template constexpr const char* multi_runner_base::message_queue_name_; } // detail //------------------------------------------------------------------------------ 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 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(); 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(); } //------------------------------------------------------------------------------ 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} { // incPort twice (2*jobIndex_) because some tests need two envs for (std::size_t i = 0; i < 2 * job_index_; ++i) test::incPorts(); 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_); } 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() { 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 detail { template class multi_runner_base; template class multi_runner_base; } } // unit_test } // beast