rippled
Loading...
Searching...
No Matches
multi_runner.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2017 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/unit_test/multi_runner.h>
21
22#include <xrpl/beast/unit_test/amount.h>
23
24#include <boost/lexical_cast.hpp>
25
26#include <algorithm>
27#include <iomanip>
28#include <iostream>
29#include <sstream>
30#include <vector>
31
32namespace ripple {
33
34namespace detail {
35
37fmtdur(typename clock_type::duration const& d)
38{
39 using namespace std::chrono;
40 auto const ms = duration_cast<milliseconds>(d);
41 if (ms < seconds{1})
42 return boost::lexical_cast<std::string>(ms.count()) + "ms";
44 ss << std::fixed << std::setprecision(1) << (ms.count() / 1000.) << "s";
45 return ss.str();
46}
47
48//------------------------------------------------------------------------------
49
50void
52{
53 ++cases;
54 total += r.total;
55 failed += r.failed;
56}
57
58//------------------------------------------------------------------------------
59
60void
62{
63 ++suites;
64 total += r.total;
65 cases += r.cases;
66 failed += r.failed;
67 auto const elapsed = clock_type::now() - r.start;
68 if (elapsed >= std::chrono::seconds{1})
69 {
70 auto const iter = std::lower_bound(
71 top.begin(),
72 top.end(),
73 elapsed,
74 [](run_time const& t1, typename clock_type::duration const& t2) {
75 return t1.second > t2;
76 });
77
78 if (iter != top.end())
79 {
80 if (top.size() == max_top && iter == top.end() - 1)
81 {
82 // avoid invalidating the iterator
83 *iter = run_time{
84 static_string{static_string::string_view_type{r.name}},
85 elapsed};
86 }
87 else
88 {
89 if (top.size() == max_top)
90 top.resize(top.size() - 1);
91 top.emplace(
92 iter,
93 static_string{static_string::string_view_type{r.name}},
94 elapsed);
95 }
96 }
97 else if (top.size() < max_top)
98 {
99 top.emplace_back(
100 static_string{static_string::string_view_type{r.name}},
101 elapsed);
102 }
103 }
104}
105
106void
108{
109 suites += r.suites;
110 total += r.total;
111 cases += r.cases;
112 failed += r.failed;
113
114 // combine the two top collections
115 boost::container::static_vector<run_time, 2 * max_top> top_result;
116 top_result.resize(top.size() + r.top.size());
118 top.begin(),
119 top.end(),
120 r.top.begin(),
121 r.top.end(),
122 top_result.begin(),
123 [](run_time const& t1, run_time const& t2) {
124 return t1.second > t2.second;
125 });
127 if (top_result.size() > max_top)
128 top_result.resize(max_top);
130 top = top_result;
131}
133template <class S>
134void
136{
137 using namespace beast::unit_test;
139 if (top.size() > 0)
140 {
141 s << "Longest suite times:\n";
142 for (auto const& [name, dur] : top)
143 s << std::setw(8) << fmtdur(dur) << " " << name << '\n';
145
146 auto const elapsed = clock_type::now() - start;
147 s << fmtdur(elapsed) << ", " << amount{suites, "suite"} << ", "
148 << amount{cases, "case"} << ", " << amount{total, "test"} << " total, "
149 << amount{failed, "failure"} << std::endl;
150}
152//------------------------------------------------------------------------------
153
154template <bool IsParent>
160
161template <bool IsParent>
167
168template <bool IsParent>
169bool
172 return any_failed_;
173}
175template <bool IsParent>
176void
179 any_failed_ = any_failed_ || v;
180}
182template <bool IsParent>
185{
186 std::lock_guard l{m_};
187 return results_.total;
188}
189
190template <bool IsParent>
194 std::lock_guard l{m_};
195 return results_.suites;
196}
198template <bool IsParent>
199void
201{
202 ++keep_alive_;
204
205template <bool IsParent>
208{
209 return keep_alive_;
210}
211
212template <bool IsParent>
213void
215{
216 std::lock_guard l{m_};
217 results_.merge(r);
218}
219
220template <bool IsParent>
221template <class S>
222void
224{
225 std::lock_guard l{m_};
226 results_.print(s);
227}
228
229template <bool IsParent>
231{
232 try
233 {
234 if (IsParent)
235 {
236 // cleanup any leftover state for any previous failed runs
237 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
238 boost::interprocess::message_queue::remove(message_queue_name_);
239 }
240
241 shared_mem_ = boost::interprocess::shared_memory_object{
243 IsParent,
244 boost::interprocess::create_only_t,
245 boost::interprocess::open_only_t>{},
247 boost::interprocess::read_write};
248
249 if (IsParent)
250 {
251 shared_mem_.truncate(sizeof(inner));
254 boost::interprocess::create_only,
256 /*max messages*/ 16,
257 /*max message size*/ 1 << 20);
258 }
259 else
260 {
263 boost::interprocess::open_only, message_queue_name_);
264 }
265
266 region_ = boost::interprocess::mapped_region{
267 shared_mem_, boost::interprocess::read_write};
268 if (IsParent)
269 inner_ = new (region_.get_address()) inner{};
270 else
271 inner_ = reinterpret_cast<inner*>(region_.get_address());
272 }
273 catch (...)
274 {
275 if (IsParent)
276 {
277 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
278 boost::interprocess::message_queue::remove(message_queue_name_);
279 }
280 throw;
281 }
282}
283
284template <bool IsParent>
286{
287 if (IsParent)
288 {
289 inner_->~inner();
290 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
291 boost::interprocess::message_queue::remove(message_queue_name_);
292 }
293}
294
295template <bool IsParent>
301
302template <bool IsParent>
308
309template <bool IsParent>
310bool
315
316template <bool IsParent>
317void
322
323template <bool IsParent>
324void
329
330template <bool IsParent>
331void
336
337template <bool IsParent>
343
344template <bool IsParent>
345template <class S>
346void
351
352template <bool IsParent>
353void
355 MessageType mt,
356 std::string const& s)
357{
358 // must use a mutex since the two "sends" must happen in order
360 message_queue_->send(&mt, sizeof(mt), /*priority*/ 0);
361 message_queue_->send(s.c_str(), s.size(), /*priority*/ 0);
362}
363
364template <bool IsParent>
367{
368 return inner_->tests();
369}
370
371template <bool IsParent>
374{
375 return inner_->suites();
376}
377
378template <bool IsParent>
379void
381{
383 results.failed += failures;
384 add(results);
385 any_failed(failures != 0);
386}
387
388} // namespace detail
389
390namespace test {
391
392//------------------------------------------------------------------------------
393
394multi_runner_parent::multi_runner_parent() : os_(std::cout)
395{
397 std::vector<char> buf(1 << 20);
398 while (this->continue_message_queue_ ||
399 this->message_queue_->get_num_msg())
400 {
401 // let children know the parent is still alive
402 this->inc_keep_alive_count();
403 if (!this->message_queue_->get_num_msg())
404 {
405 // If a child does not see the keep alive count incremented,
406 // it will assume the parent has died. This sleep time needs
407 // to be small enough so the child will see increments from
408 // a live parent.
410 continue;
411 }
412 try
413 {
414 std::size_t recvd_size = 0;
415 unsigned int priority = 0;
416 this->message_queue_->receive(
417 buf.data(), buf.size(), recvd_size, priority);
418 if (!recvd_size)
419 continue;
420 assert(recvd_size == 1);
421 MessageType mt{*reinterpret_cast<MessageType*>(buf.data())};
422
423 this->message_queue_->receive(
424 buf.data(), buf.size(), recvd_size, priority);
425 if (recvd_size)
426 {
427 std::string s{buf.data(), recvd_size};
428 switch (mt)
429 {
430 case MessageType::log:
431 this->os_ << s;
432 this->os_.flush();
433 break;
434 case MessageType::test_start:
435 running_suites_.insert(std::move(s));
436 break;
437 case MessageType::test_end:
439 break;
440 default:
441 assert(0); // unknown message type
442 }
443 }
444 }
445 catch (std::exception const& e)
446 {
447 std::cerr << "Error: " << e.what()
448 << " reading unit test message queue.\n";
449 return;
450 }
451 catch (...)
452 {
453 std::cerr << "Unknown error reading unit test message queue.\n";
454 return;
455 }
456 }
457 });
458}
459
461{
462 using namespace beast::unit_test;
463
466
468
469 for (auto const& s : running_suites_)
470 {
471 os_ << "\nSuite: " << s
472 << " failed to complete. The child process may have crashed.\n";
473 }
474}
475
476bool
478{
479 return multi_runner_base<true>::any_failed();
480}
481
484{
485 return multi_runner_base<true>::tests();
486}
487
490{
491 return multi_runner_base<true>::suites();
492}
493
494void
496{
497 multi_runner_base<true>::add_failures(failures);
498}
499
500//------------------------------------------------------------------------------
501
503 std::size_t num_jobs,
504 bool quiet,
505 bool print_log)
506 : job_index_{checkout_job_index()}
507 , num_jobs_{num_jobs}
508 , quiet_{quiet}
509 , print_log_{!quiet || print_log}
510{
511 if (num_jobs_ > 1)
512 {
514 std::size_t last_count = get_keep_alive_count();
515 while (this->continue_keep_alive_)
516 {
517 // Use a small sleep time so in the normal case the child
518 // process may shutdown quickly. However, to protect against
519 // false alarms, use a longer sleep time later on.
521 auto cur_count = this->get_keep_alive_count();
522 if (cur_count == last_count)
523 {
524 // longer sleep time to protect against false alarms
526 cur_count = this->get_keep_alive_count();
527 if (cur_count == last_count)
528 {
529 // assume parent process is no longer alive
530 std::cerr << "multi_runner_child " << job_index_
531 << ": Assuming parent died, exiting.\n";
532 std::exit(EXIT_FAILURE);
533 }
534 }
535 last_count = cur_count;
536 }
537 });
538 }
539}
540
542{
543 if (num_jobs_ > 1)
544 {
545 continue_keep_alive_ = false;
547 }
548
549 add(results_);
550}
551
554{
555 return results_.total;
556}
557
560{
561 return results_.suites;
562}
563
564void
566{
567 results_.failed += failures;
568 any_failed(failures != 0);
569}
570
571void
577
578void
580{
582 {
584 if (num_jobs_ > 1)
585 s << job_index_ << "> ";
586 s << (suite_results_.failed > 0 ? "failed: " : "")
588 << " failures." << std::endl;
589 message_queue_send(MessageType::log, s.str());
590 }
592 message_queue_send(MessageType::test_end, suite_results_.name);
593}
594
595void
597{
599
600 if (quiet_)
601 return;
602
604 if (num_jobs_ > 1)
605 s << job_index_ << "> ";
607 << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << '\n';
608 message_queue_send(MessageType::log, s.str());
609}
610
611void
616
617void
622
623void
625{
629 if (num_jobs_ > 1)
630 s << job_index_ << "> ";
631 s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ")
632 << reason << '\n';
633 message_queue_send(MessageType::log, s.str());
634}
635
636void
638{
639 if (!print_log_)
640 return;
641
643 if (num_jobs_ > 1)
644 s << job_index_ << "> ";
645 s << msg;
646 message_queue_send(MessageType::log, s.str());
647}
648
649} // namespace test
650
651namespace detail {
652template class multi_runner_base<true>;
653template class multi_runner_base<false>;
654} // namespace detail
655
656} // namespace ripple
T c_str(T... args)
Utility for producing nicely composed output of amounts with units.
Associates a unit test type with metadata.
Definition suite_info.h:23
std::string full_name() const
Return the canonical suite name as a string.
Definition suite_info.h:77
std::unique_ptr< boost::interprocess::message_queue > message_queue_
static constexpr char const * message_queue_name_
void message_queue_send(MessageType mt, std::string const &s)
boost::interprocess::mapped_region region_
void add_failures(std::size_t failures)
static constexpr char const * shared_mem_name_
boost::interprocess::shared_memory_object shared_mem_
multi_runner_child(multi_runner_child const &)=delete
virtual void on_log(std::string const &s) override
Called when a test logs output.
detail::case_results case_results_
virtual void on_case_end() override
Called when a new case ends.
detail::suite_results suite_results_
virtual void on_case_begin(std::string const &name) override
Called when a new case starts.
void add_failures(std::size_t failures)
virtual void on_suite_end() override
Called when a suite ends.
std::atomic< bool > continue_keep_alive_
virtual void on_suite_begin(beast::unit_test::suite_info const &info) override
Called when a new suite starts.
virtual void on_pass() override
Called for each passing condition.
virtual void on_fail(std::string const &reason) override
Called for each failing condition.
std::atomic< bool > continue_message_queue_
void add_failures(std::size_t failures)
std::set< std::string > running_suites_
T data(T... args)
T empty(T... args)
T endl(T... args)
T erase(T... args)
T exit(T... args)
T fixed(T... args)
T flush(T... args)
T insert(T... args)
T is_same_v
T join(T... args)
T lower_bound(T... args)
T merge(T... args)
std::string fmtdur(std::chrono::duration< Period, Rep > const &d)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
STL namespace.
T resize(T... args)
T setprecision(T... args)
T size(T... args)
T sleep_for(T... args)
T str(T... args)
boost::interprocess::interprocess_mutex m_
std::atomic< std::size_t > job_index_
void add(suite_results const &r)
boost::beast::static_string< 256 > static_string
std::pair< static_string, typename clock_type::duration > run_time
boost::container::static_vector< run_time, max_top > top
clock_type::time_point start
void merge(results const &r)
void add(case_results const &r)
clock_type::time_point start
T what(T... args)