rippled
Loading...
Searching...
No Matches
multi_runner.cpp
1#include <test/unit_test/multi_runner.h>
2
3#include <xrpl/beast/unit_test/amount.h>
4
5#include <boost/lexical_cast.hpp>
6
7#include <algorithm>
8#include <iomanip>
9#include <iostream>
10#include <sstream>
11#include <vector>
12
13namespace xrpl {
14
15namespace detail {
16
18fmtdur(typename clock_type::duration const& d)
19{
20 using namespace std::chrono;
21 auto const ms = duration_cast<milliseconds>(d);
22 if (ms < seconds{1})
23 return boost::lexical_cast<std::string>(ms.count()) + "ms";
25 ss << std::fixed << std::setprecision(1) << (ms.count() / 1000.) << "s";
26 return ss.str();
27}
28
29//------------------------------------------------------------------------------
30
31void
33{
34 ++cases;
35 total += r.total;
36 failed += r.failed;
37}
38
39//------------------------------------------------------------------------------
40
41void
43{
44 ++suites;
45 total += r.total;
46 cases += r.cases;
47 failed += r.failed;
48 auto const elapsed = clock_type::now() - r.start;
49 if (elapsed >= std::chrono::seconds{1})
50 {
51 auto const iter = std::lower_bound(
52 top.begin(),
53 top.end(),
54 elapsed,
55 [](run_time const& t1, typename clock_type::duration const& t2) {
56 return t1.second > t2;
57 });
58
59 if (iter != top.end())
60 {
61 if (top.size() == max_top && iter == top.end() - 1)
62 {
63 // avoid invalidating the iterator
64 *iter = run_time{static_string{static_string::string_view_type{r.name}}, elapsed};
65 }
66 else
67 {
68 if (top.size() == max_top)
69 top.resize(top.size() - 1);
70 top.emplace(iter, static_string{static_string::string_view_type{r.name}}, elapsed);
71 }
72 }
73 else if (top.size() < max_top)
74 {
75 top.emplace_back(static_string{static_string::string_view_type{r.name}}, elapsed);
76 }
77 }
78}
79
80void
82{
83 suites += r.suites;
84 total += r.total;
85 cases += r.cases;
86 failed += r.failed;
87
88 // combine the two top collections
89 boost::container::static_vector<run_time, 2 * max_top> top_result;
90 top_result.resize(top.size() + r.top.size());
92 top.begin(),
93 top.end(),
94 r.top.begin(),
95 r.top.end(),
96 top_result.begin(),
97 [](run_time const& t1, run_time const& t2) { return t1.second > t2.second; });
98
99 if (top_result.size() > max_top)
100 top_result.resize(max_top);
101
102 top = top_result;
104
105template <class S>
106void
108{
109 using namespace beast::unit_test;
110
111 if (!top.empty())
113 s << "Longest suite times:\n";
114 for (auto const& [name, dur] : top)
115 s << std::setw(8) << fmtdur(dur) << " " << name << '\n';
116 }
117
118 auto const elapsed = clock_type::now() - start;
119 s << fmtdur(elapsed) << ", " << amount{suites, "suite"} << ", " << amount{cases, "case"} << ", "
120 << amount{total, "test"} << " total, " << amount{failed, "failure"} << std::endl;
122
123//------------------------------------------------------------------------------
125template <bool IsParent>
128{
129 return job_index_++;
130}
132template <bool IsParent>
138
139template <bool IsParent>
140bool
142{
143 return any_failed_;
144}
145
146template <bool IsParent>
147void
149{
150 any_failed_ = any_failed_ || v;
151}
152
153template <bool IsParent>
156{
157 std::lock_guard const l{m_};
158 return results_.total;
159}
161template <bool IsParent>
164{
165 std::lock_guard const l{m_};
166 return results_.suites;
167}
168
169template <bool IsParent>
170void
173 ++keep_alive_;
174}
175
176template <bool IsParent>
180 return keep_alive_;
181}
183template <bool IsParent>
184void
186{
187 std::lock_guard const l{m_};
188 results_.merge(r);
189}
190
191template <bool IsParent>
192template <class S>
193void
195{
196 std::lock_guard const l{m_};
197 results_.print(s);
198}
199
200template <bool IsParent>
202{
203 try
204 {
205 if (IsParent)
206 {
207 // cleanup any leftover state for any previous failed runs
208 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
209 boost::interprocess::message_queue::remove(message_queue_name_);
210 }
211
212 shared_mem_ = boost::interprocess::shared_memory_object{
214 IsParent,
215 boost::interprocess::create_only_t,
216 boost::interprocess::open_only_t>{},
218 boost::interprocess::read_write};
219
220 if (IsParent)
221 {
222 shared_mem_.truncate(sizeof(inner));
224 boost::interprocess::create_only,
226 /*max messages*/ 16,
227 /*max message size*/ 1 << 20);
228 }
229 else
230 {
232 boost::interprocess::open_only, message_queue_name_);
233 }
234
235 region_ = boost::interprocess::mapped_region{shared_mem_, boost::interprocess::read_write};
236 if (IsParent)
237 {
238 inner_ = new (region_.get_address()) inner{};
239 }
240 else
241 {
242 inner_ = reinterpret_cast<inner*>(region_.get_address());
243 }
244 }
245 catch (...)
246 {
247 if (IsParent)
248 {
249 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
250 boost::interprocess::message_queue::remove(message_queue_name_);
251 }
252 throw;
253 }
254}
255
256template <bool IsParent>
258{
259 if (IsParent)
260 {
261 inner_->~inner();
262 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
263 boost::interprocess::message_queue::remove(message_queue_name_);
264 }
265}
266
267template <bool IsParent>
273
274template <bool IsParent>
280
281template <bool IsParent>
282bool
287
288template <bool IsParent>
289void
294
295template <bool IsParent>
296void
301
302template <bool IsParent>
303void
308
309template <bool IsParent>
315
316template <bool IsParent>
317template <class S>
318void
323
324template <bool IsParent>
325void
327{
328 // must use a mutex since the two "sends" must happen in order
329 std::lock_guard const l{inner_->m_};
330 message_queue_->send(&mt, sizeof(mt), /*priority*/ 0);
331 message_queue_->send(s.c_str(), s.size(), /*priority*/ 0);
332}
333
334template <bool IsParent>
337{
338 return inner_->tests();
339}
340
341template <bool IsParent>
344{
345 return inner_->suites();
346}
347
348template <bool IsParent>
349void
351{
353 results.failed += failures;
354 add(results);
355 any_failed(failures != 0);
356}
357
358} // namespace detail
359
360namespace test {
361
362//------------------------------------------------------------------------------
363
364multi_runner_parent::multi_runner_parent() : os_(std::cout)
365{
367 std::vector<char> buf(1 << 20);
368 while (this->continue_message_queue_ || this->message_queue_->get_num_msg())
369 {
370 // let children know the parent is still alive
371 this->inc_keep_alive_count();
372 if (!this->message_queue_->get_num_msg())
373 {
374 // If a child does not see the keep alive count incremented,
375 // it will assume the parent has died. This sleep time needs
376 // to be small enough so the child will see increments from
377 // a live parent.
379 continue;
380 }
381 try
382 {
383 std::size_t recvd_size = 0;
384 unsigned int priority = 0;
385 this->message_queue_->receive(buf.data(), buf.size(), recvd_size, priority);
386 if (!recvd_size)
387 continue;
388 assert(recvd_size == 1);
389 MessageType const mt{*reinterpret_cast<MessageType*>(buf.data())};
390
391 this->message_queue_->receive(buf.data(), buf.size(), recvd_size, priority);
392 if (recvd_size)
393 {
394 std::string s{buf.data(), recvd_size};
395 switch (mt)
396 {
397 case MessageType::log:
398 this->os_ << s;
399 this->os_.flush();
400 break;
401 case MessageType::test_start:
402 running_suites_.insert(std::move(s));
403 break;
404 case MessageType::test_end:
406 break;
407 default:
408 assert(0); // unknown message type
409 }
410 }
411 }
412 catch (std::exception const& e)
413 {
414 std::cerr << "Error: " << e.what() << " reading unit test message queue.\n";
415 return;
416 }
417 catch (...)
418 {
419 std::cerr << "Unknown error reading unit test message queue.\n";
420 return;
421 }
422 }
423 });
424}
425
427{
428 using namespace beast::unit_test;
429
432
434
436
437 for (auto const& s : running_suites_)
438 {
439 os_ << "\nSuite: " << s << " failed to complete. The child process may have crashed.\n";
440 }
441}
442
443bool
445{
446 return multi_runner_base<true>::any_failed();
447}
448
451{
452 return multi_runner_base<true>::tests();
453}
454
457{
458 return multi_runner_base<true>::suites();
459}
460
461void
463{
464 multi_runner_base<true>::add_failures(failures);
465}
466
467//------------------------------------------------------------------------------
468
469multi_runner_child::multi_runner_child(std::size_t num_jobs, bool quiet, bool print_log)
470 : job_index_{checkout_job_index()}
471 , num_jobs_{num_jobs}
472 , quiet_{quiet}
473 , print_log_{!quiet || print_log}
474{
475 if (num_jobs_ > 1)
476 {
478 std::size_t last_count = get_keep_alive_count();
479 while (this->continue_keep_alive_)
480 {
481 // Use a small sleep time so in the normal case the child
482 // process may shutdown quickly. However, to protect against
483 // false alarms, use a longer sleep time later on.
485 auto cur_count = this->get_keep_alive_count();
486 if (cur_count == last_count)
487 {
488 // longer sleep time to protect against false alarms
490 cur_count = this->get_keep_alive_count();
491 if (cur_count == last_count)
492 {
493 // assume parent process is no longer alive
494 std::cerr << "multi_runner_child " << job_index_
495 << ": Assuming parent died, exiting.\n";
496 std::exit(EXIT_FAILURE);
497 }
498 }
499 last_count = cur_count;
500 }
501 });
502 }
503}
504
506{
507 if (num_jobs_ > 1)
508 {
509 continue_keep_alive_ = false;
511 }
512
513 add(results_);
514}
515
518{
519 return results_.total;
520}
521
524{
525 return results_.suites;
526}
527
528void
530{
531 results_.failed += failures;
532 any_failed(failures != 0);
533}
534
535void
541
542void
544{
546 {
548 if (num_jobs_ > 1)
549 s << job_index_ << "> ";
550 s << (suite_results_.failed > 0 ? "failed: " : "") << suite_results_.name << " had "
551 << suite_results_.failed << " failures." << std::endl;
552 message_queue_send(MessageType::log, s.str());
553 }
555 message_queue_send(MessageType::test_end, suite_results_.name);
556}
557
558void
560{
562
563 if (quiet_)
564 return;
565
567 if (num_jobs_ > 1)
568 s << job_index_ << "> ";
569 s << suite_results_.name << (case_results_.name.empty() ? "" : (" " + case_results_.name))
570 << '\n';
571 message_queue_send(MessageType::log, s.str());
572}
573
574void
579
580void
585
586void
588{
592 if (num_jobs_ > 1)
593 s << job_index_ << "> ";
594 s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ") << reason << '\n';
595 message_queue_send(MessageType::log, s.str());
596}
597
598void
600{
601 if (!print_log_)
602 return;
603
605 if (num_jobs_ > 1)
606 s << job_index_ << "> ";
607 s << msg;
608 message_queue_send(MessageType::log, s.str());
609}
610
611} // namespace test
612
613namespace detail {
614template class multi_runner_base<true>;
615template class multi_runner_base<false>;
616} // namespace detail
617
618} // namespace xrpl
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:19
std::string full_name() const
Return the canonical suite name as a string.
Definition suite_info.h:73
boost::interprocess::shared_memory_object shared_mem_
void add_failures(std::size_t failures)
static constexpr char const * message_queue_name_
void message_queue_send(MessageType mt, std::string const &s)
std::unique_ptr< boost::interprocess::message_queue > message_queue_
boost::interprocess::mapped_region region_
static constexpr char const * shared_mem_name_
std::atomic< bool > continue_keep_alive_
detail::suite_results suite_results_
virtual void on_case_begin(std::string const &name) override
Called when a new case starts.
virtual void on_log(std::string const &s) override
Called when a test logs output.
virtual void on_suite_end() override
Called when a suite ends.
virtual void on_fail(std::string const &reason) override
Called for each failing condition.
detail::case_results case_results_
virtual void on_pass() override
Called for each passing condition.
multi_runner_child(multi_runner_child const &)=delete
void add_failures(std::size_t failures)
virtual void on_suite_begin(beast::unit_test::suite_info const &info) override
Called when a new suite starts.
virtual void on_case_end() override
Called when a new case ends.
std::set< std::string > running_suites_
std::atomic< bool > continue_message_queue_
void add_failures(std::size_t failures)
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)
STL namespace.
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:5
T resize(T... args)
T setprecision(T... args)
T setw(T... args)
T size(T... args)
T sleep_for(T... args)
T str(T... args)
std::atomic< std::size_t > job_index_
boost::interprocess::interprocess_mutex m_
boost::beast::static_string< 256 > static_string
void add(suite_results const &r)
void merge(results const &r)
clock_type::time_point start
boost::container::static_vector< run_time, max_top > top
void add(case_results const &r)
clock_type::time_point start
T what(T... args)