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(), top.end(), elapsed, [](run_time const& t1, typename clock_type::duration const& t2) {
53 return t1.second > t2;
54 });
55
56 if (iter != top.end())
57 {
58 if (top.size() == max_top && iter == top.end() - 1)
59 {
60 // avoid invalidating the iterator
61 *iter = run_time{static_string{static_string::string_view_type{r.name}}, elapsed};
62 }
63 else
64 {
65 if (top.size() == max_top)
66 top.resize(top.size() - 1);
67 top.emplace(iter, static_string{static_string::string_view_type{r.name}}, elapsed);
68 }
69 }
70 else if (top.size() < max_top)
71 {
72 top.emplace_back(static_string{static_string::string_view_type{r.name}}, elapsed);
73 }
74 }
75}
76
77void
79{
80 suites += r.suites;
81 total += r.total;
82 cases += r.cases;
83 failed += r.failed;
84
85 // combine the two top collections
86 boost::container::static_vector<run_time, 2 * max_top> top_result;
87 top_result.resize(top.size() + r.top.size());
89 top.begin(),
90 top.end(),
91 r.top.begin(),
92 r.top.end(),
93 top_result.begin(),
94 [](run_time const& t1, run_time const& t2) { return t1.second > t2.second; });
95
96 if (top_result.size() > max_top)
97 top_result.resize(max_top);
98
99 top = top_result;
100}
101
102template <class S>
103void
105{
106 using namespace beast::unit_test;
108 if (top.size() > 0)
109 {
110 s << "Longest suite times:\n";
111 for (auto const& [name, dur] : top)
112 s << std::setw(8) << fmtdur(dur) << " " << name << '\n';
114
115 auto const elapsed = clock_type::now() - start;
116 s << fmtdur(elapsed) << ", " << amount{suites, "suite"} << ", " << amount{cases, "case"} << ", "
117 << amount{total, "test"} << " total, " << amount{failed, "failure"} << std::endl;
118}
120//------------------------------------------------------------------------------
121
122template <bool IsParent>
126 return job_index_++;
127}
129template <bool IsParent>
133 return test_index_++;
134}
135
136template <bool IsParent>
137bool
139{
140 return any_failed_;
141}
142
143template <bool IsParent>
144void
146{
147 any_failed_ = any_failed_ || v;
148}
149
150template <bool IsParent>
153{
155 return results_.total;
156}
157
158template <bool IsParent>
162 std::lock_guard l{m_};
163 return results_.suites;
165
166template <bool IsParent>
167void
169{
170 ++keep_alive_;
171}
172
173template <bool IsParent>
176{
177 return keep_alive_;
178}
179
180template <bool IsParent>
181void
184 std::lock_guard l{m_};
185 results_.merge(r);
187
188template <bool IsParent>
189template <class S>
190void
192{
193 std::lock_guard l{m_};
194 results_.print(s);
195}
196
197template <bool IsParent>
199{
200 try
201 {
202 if (IsParent)
203 {
204 // cleanup any leftover state for any previous failed runs
205 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
206 boost::interprocess::message_queue::remove(message_queue_name_);
207 }
208
209 shared_mem_ = boost::interprocess::shared_memory_object{
212 boost::interprocess::read_write};
213
214 if (IsParent)
215 {
216 shared_mem_.truncate(sizeof(inner));
218 boost::interprocess::create_only,
220 /*max messages*/ 16,
221 /*max message size*/ 1 << 20);
222 }
223 else
224 {
226 boost::interprocess::open_only, message_queue_name_);
227 }
228
229 region_ = boost::interprocess::mapped_region{shared_mem_, boost::interprocess::read_write};
230 if (IsParent)
231 inner_ = new (region_.get_address()) inner{};
232 else
233 inner_ = reinterpret_cast<inner*>(region_.get_address());
234 }
235 catch (...)
236 {
237 if (IsParent)
238 {
239 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
240 boost::interprocess::message_queue::remove(message_queue_name_);
241 }
242 throw;
243 }
244}
245
246template <bool IsParent>
248{
249 if (IsParent)
250 {
251 inner_->~inner();
252 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
253 boost::interprocess::message_queue::remove(message_queue_name_);
254 }
255}
256
257template <bool IsParent>
263
264template <bool IsParent>
270
271template <bool IsParent>
272bool
277
278template <bool IsParent>
279void
284
285template <bool IsParent>
286void
291
292template <bool IsParent>
293void
298
299template <bool IsParent>
305
306template <bool IsParent>
307template <class S>
308void
313
314template <bool IsParent>
315void
317{
318 // must use a mutex since the two "sends" must happen in order
320 message_queue_->send(&mt, sizeof(mt), /*priority*/ 0);
321 message_queue_->send(s.c_str(), s.size(), /*priority*/ 0);
322}
323
324template <bool IsParent>
327{
328 return inner_->tests();
329}
330
331template <bool IsParent>
334{
335 return inner_->suites();
336}
337
338template <bool IsParent>
339void
341{
343 results.failed += failures;
344 add(results);
345 any_failed(failures != 0);
346}
347
348} // namespace detail
349
350namespace test {
351
352//------------------------------------------------------------------------------
353
354multi_runner_parent::multi_runner_parent() : os_(std::cout)
355{
357 std::vector<char> buf(1 << 20);
358 while (this->continue_message_queue_ || this->message_queue_->get_num_msg())
359 {
360 // let children know the parent is still alive
361 this->inc_keep_alive_count();
362 if (!this->message_queue_->get_num_msg())
363 {
364 // If a child does not see the keep alive count incremented,
365 // it will assume the parent has died. This sleep time needs
366 // to be small enough so the child will see increments from
367 // a live parent.
369 continue;
370 }
371 try
372 {
373 std::size_t recvd_size = 0;
374 unsigned int priority = 0;
375 this->message_queue_->receive(buf.data(), buf.size(), recvd_size, priority);
376 if (!recvd_size)
377 continue;
378 assert(recvd_size == 1);
379 MessageType mt{*reinterpret_cast<MessageType*>(buf.data())};
380
381 this->message_queue_->receive(buf.data(), buf.size(), recvd_size, priority);
382 if (recvd_size)
383 {
384 std::string s{buf.data(), recvd_size};
385 switch (mt)
386 {
387 case MessageType::log:
388 this->os_ << s;
389 this->os_.flush();
390 break;
391 case MessageType::test_start:
392 running_suites_.insert(std::move(s));
393 break;
394 case MessageType::test_end:
396 break;
397 default:
398 assert(0); // unknown message type
399 }
400 }
401 }
402 catch (std::exception const& e)
403 {
404 std::cerr << "Error: " << e.what() << " reading unit test message queue.\n";
405 return;
406 }
407 catch (...)
408 {
409 std::cerr << "Unknown error reading unit test message queue.\n";
410 return;
411 }
412 }
413 });
414}
415
417{
418 using namespace beast::unit_test;
419
422
424
426
427 for (auto const& s : running_suites_)
428 {
429 os_ << "\nSuite: " << s << " failed to complete. The child process may have crashed.\n";
430 }
431}
432
433bool
435{
436 return multi_runner_base<true>::any_failed();
437}
438
441{
442 return multi_runner_base<true>::tests();
443}
444
447{
448 return multi_runner_base<true>::suites();
449}
450
451void
453{
454 multi_runner_base<true>::add_failures(failures);
455}
456
457//------------------------------------------------------------------------------
458
459multi_runner_child::multi_runner_child(std::size_t num_jobs, bool quiet, bool print_log)
460 : job_index_{checkout_job_index()}, num_jobs_{num_jobs}, quiet_{quiet}, print_log_{!quiet || print_log}
461{
462 if (num_jobs_ > 1)
463 {
465 std::size_t last_count = get_keep_alive_count();
466 while (this->continue_keep_alive_)
467 {
468 // Use a small sleep time so in the normal case the child
469 // process may shutdown quickly. However, to protect against
470 // false alarms, use a longer sleep time later on.
472 auto cur_count = this->get_keep_alive_count();
473 if (cur_count == last_count)
474 {
475 // longer sleep time to protect against false alarms
477 cur_count = this->get_keep_alive_count();
478 if (cur_count == last_count)
479 {
480 // assume parent process is no longer alive
481 std::cerr << "multi_runner_child " << job_index_ << ": Assuming parent died, exiting.\n";
482 std::exit(EXIT_FAILURE);
483 }
484 }
485 last_count = cur_count;
486 }
487 });
488 }
489}
490
492{
493 if (num_jobs_ > 1)
494 {
495 continue_keep_alive_ = false;
497 }
498
499 add(results_);
500}
501
504{
505 return results_.total;
506}
507
510{
511 return results_.suites;
512}
513
514void
516{
517 results_.failed += failures;
518 any_failed(failures != 0);
519}
520
521void
527
528void
530{
532 {
534 if (num_jobs_ > 1)
535 s << job_index_ << "> ";
536 s << (suite_results_.failed > 0 ? "failed: " : "") << suite_results_.name << " had " << suite_results_.failed
537 << " failures." << std::endl;
538 message_queue_send(MessageType::log, s.str());
539 }
541 message_queue_send(MessageType::test_end, suite_results_.name);
542}
543
544void
546{
548
549 if (quiet_)
550 return;
551
553 if (num_jobs_ > 1)
554 s << job_index_ << "> ";
555 s << suite_results_.name << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << '\n';
556 message_queue_send(MessageType::log, s.str());
557}
558
559void
564
565void
570
571void
573{
577 if (num_jobs_ > 1)
578 s << job_index_ << "> ";
579 s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ") << reason << '\n';
580 message_queue_send(MessageType::log, s.str());
581}
582
583void
585{
586 if (!print_log_)
587 return;
588
590 if (num_jobs_ > 1)
591 s << job_index_ << "> ";
592 s << msg;
593 message_queue_send(MessageType::log, s.str());
594}
595
596} // namespace test
597
598namespace detail {
599template class multi_runner_base<true>;
600template class multi_runner_base<false>;
601} // namespace detail
602
603} // 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:20
std::string full_name() const
Return the canonical suite name as a string.
Definition suite_info.h:68
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:6
T resize(T... args)
T setprecision(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)