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 ripple {
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{
65 static_string{static_string::string_view_type{r.name}},
66 elapsed};
67 }
68 else
69 {
70 if (top.size() == max_top)
71 top.resize(top.size() - 1);
72 top.emplace(
73 iter,
74 static_string{static_string::string_view_type{r.name}},
75 elapsed);
76 }
77 }
78 else if (top.size() < max_top)
79 {
80 top.emplace_back(
81 static_string{static_string::string_view_type{r.name}},
82 elapsed);
83 }
84 }
85}
86
87void
89{
90 suites += r.suites;
91 total += r.total;
92 cases += r.cases;
93 failed += r.failed;
94
95 // combine the two top collections
96 boost::container::static_vector<run_time, 2 * max_top> top_result;
97 top_result.resize(top.size() + r.top.size());
99 top.begin(),
100 top.end(),
101 r.top.begin(),
102 r.top.end(),
103 top_result.begin(),
104 [](run_time const& t1, run_time const& t2) {
105 return t1.second > t2.second;
106 });
108 if (top_result.size() > max_top)
109 top_result.resize(max_top);
111 top = top_result;
112}
114template <class S>
115void
117{
118 using namespace beast::unit_test;
120 if (top.size() > 0)
121 {
122 s << "Longest suite times:\n";
123 for (auto const& [name, dur] : top)
124 s << std::setw(8) << fmtdur(dur) << " " << name << '\n';
126
127 auto const elapsed = clock_type::now() - start;
128 s << fmtdur(elapsed) << ", " << amount{suites, "suite"} << ", "
129 << amount{cases, "case"} << ", " << amount{total, "test"} << " total, "
130 << amount{failed, "failure"} << std::endl;
131}
133//------------------------------------------------------------------------------
134
135template <bool IsParent>
141
142template <bool IsParent>
148
149template <bool IsParent>
150bool
153 return any_failed_;
154}
156template <bool IsParent>
157void
160 any_failed_ = any_failed_ || v;
161}
163template <bool IsParent>
166{
167 std::lock_guard l{m_};
168 return results_.total;
169}
170
171template <bool IsParent>
175 std::lock_guard l{m_};
176 return results_.suites;
177}
179template <bool IsParent>
180void
182{
183 ++keep_alive_;
185
186template <bool IsParent>
189{
190 return keep_alive_;
191}
192
193template <bool IsParent>
194void
196{
197 std::lock_guard l{m_};
198 results_.merge(r);
199}
200
201template <bool IsParent>
202template <class S>
203void
205{
206 std::lock_guard l{m_};
207 results_.print(s);
208}
209
210template <bool IsParent>
212{
213 try
214 {
215 if (IsParent)
216 {
217 // cleanup any leftover state for any previous failed runs
218 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
219 boost::interprocess::message_queue::remove(message_queue_name_);
220 }
221
222 shared_mem_ = boost::interprocess::shared_memory_object{
224 IsParent,
225 boost::interprocess::create_only_t,
226 boost::interprocess::open_only_t>{},
228 boost::interprocess::read_write};
229
230 if (IsParent)
231 {
232 shared_mem_.truncate(sizeof(inner));
235 boost::interprocess::create_only,
237 /*max messages*/ 16,
238 /*max message size*/ 1 << 20);
239 }
240 else
241 {
244 boost::interprocess::open_only, message_queue_name_);
245 }
246
247 region_ = boost::interprocess::mapped_region{
248 shared_mem_, boost::interprocess::read_write};
249 if (IsParent)
250 inner_ = new (region_.get_address()) inner{};
251 else
252 inner_ = reinterpret_cast<inner*>(region_.get_address());
253 }
254 catch (...)
255 {
256 if (IsParent)
257 {
258 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
259 boost::interprocess::message_queue::remove(message_queue_name_);
260 }
261 throw;
262 }
263}
264
265template <bool IsParent>
267{
268 if (IsParent)
269 {
270 inner_->~inner();
271 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
272 boost::interprocess::message_queue::remove(message_queue_name_);
273 }
274}
275
276template <bool IsParent>
282
283template <bool IsParent>
289
290template <bool IsParent>
291bool
296
297template <bool IsParent>
298void
303
304template <bool IsParent>
305void
310
311template <bool IsParent>
312void
317
318template <bool IsParent>
324
325template <bool IsParent>
326template <class S>
327void
332
333template <bool IsParent>
334void
336 MessageType mt,
337 std::string const& s)
338{
339 // must use a mutex since the two "sends" must happen in order
341 message_queue_->send(&mt, sizeof(mt), /*priority*/ 0);
342 message_queue_->send(s.c_str(), s.size(), /*priority*/ 0);
343}
344
345template <bool IsParent>
348{
349 return inner_->tests();
350}
351
352template <bool IsParent>
355{
356 return inner_->suites();
357}
358
359template <bool IsParent>
360void
362{
364 results.failed += failures;
365 add(results);
366 any_failed(failures != 0);
367}
368
369} // namespace detail
370
371namespace test {
372
373//------------------------------------------------------------------------------
374
375multi_runner_parent::multi_runner_parent() : os_(std::cout)
376{
378 std::vector<char> buf(1 << 20);
379 while (this->continue_message_queue_ ||
380 this->message_queue_->get_num_msg())
381 {
382 // let children know the parent is still alive
383 this->inc_keep_alive_count();
384 if (!this->message_queue_->get_num_msg())
385 {
386 // If a child does not see the keep alive count incremented,
387 // it will assume the parent has died. This sleep time needs
388 // to be small enough so the child will see increments from
389 // a live parent.
391 continue;
392 }
393 try
394 {
395 std::size_t recvd_size = 0;
396 unsigned int priority = 0;
397 this->message_queue_->receive(
398 buf.data(), buf.size(), recvd_size, priority);
399 if (!recvd_size)
400 continue;
401 assert(recvd_size == 1);
402 MessageType mt{*reinterpret_cast<MessageType*>(buf.data())};
403
404 this->message_queue_->receive(
405 buf.data(), buf.size(), recvd_size, priority);
406 if (recvd_size)
407 {
408 std::string s{buf.data(), recvd_size};
409 switch (mt)
410 {
411 case MessageType::log:
412 this->os_ << s;
413 this->os_.flush();
414 break;
415 case MessageType::test_start:
416 running_suites_.insert(std::move(s));
417 break;
418 case MessageType::test_end:
420 break;
421 default:
422 assert(0); // unknown message type
423 }
424 }
425 }
426 catch (std::exception const& e)
427 {
428 std::cerr << "Error: " << e.what()
429 << " reading unit test message queue.\n";
430 return;
431 }
432 catch (...)
433 {
434 std::cerr << "Unknown error reading unit test message queue.\n";
435 return;
436 }
437 }
438 });
439}
440
442{
443 using namespace beast::unit_test;
444
447
449
451
452 for (auto const& s : running_suites_)
453 {
454 os_ << "\nSuite: " << s
455 << " failed to complete. The child process may have crashed.\n";
456 }
457}
458
459bool
461{
462 return multi_runner_base<true>::any_failed();
463}
464
467{
468 return multi_runner_base<true>::tests();
469}
470
473{
474 return multi_runner_base<true>::suites();
475}
476
477void
479{
480 multi_runner_base<true>::add_failures(failures);
481}
482
483//------------------------------------------------------------------------------
484
486 std::size_t num_jobs,
487 bool quiet,
488 bool print_log)
489 : job_index_{checkout_job_index()}
490 , num_jobs_{num_jobs}
491 , quiet_{quiet}
492 , print_log_{!quiet || print_log}
493{
494 if (num_jobs_ > 1)
495 {
497 std::size_t last_count = get_keep_alive_count();
498 while (this->continue_keep_alive_)
499 {
500 // Use a small sleep time so in the normal case the child
501 // process may shutdown quickly. However, to protect against
502 // false alarms, use a longer sleep time later on.
504 auto cur_count = this->get_keep_alive_count();
505 if (cur_count == last_count)
506 {
507 // longer sleep time to protect against false alarms
509 cur_count = this->get_keep_alive_count();
510 if (cur_count == last_count)
511 {
512 // assume parent process is no longer alive
513 std::cerr << "multi_runner_child " << job_index_
514 << ": Assuming parent died, exiting.\n";
515 std::exit(EXIT_FAILURE);
516 }
517 }
518 last_count = cur_count;
519 }
520 });
521 }
522}
523
525{
526 if (num_jobs_ > 1)
527 {
528 continue_keep_alive_ = false;
530 }
531
532 add(results_);
533}
534
537{
538 return results_.total;
539}
540
543{
544 return results_.suites;
545}
546
547void
549{
550 results_.failed += failures;
551 any_failed(failures != 0);
552}
553
554void
560
561void
563{
565 {
567 if (num_jobs_ > 1)
568 s << job_index_ << "> ";
569 s << (suite_results_.failed > 0 ? "failed: " : "")
571 << " failures." << std::endl;
572 message_queue_send(MessageType::log, s.str());
573 }
575 message_queue_send(MessageType::test_end, suite_results_.name);
576}
577
578void
580{
582
583 if (quiet_)
584 return;
585
587 if (num_jobs_ > 1)
588 s << job_index_ << "> ";
590 << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << '\n';
591 message_queue_send(MessageType::log, s.str());
592}
593
594void
599
600void
605
606void
608{
612 if (num_jobs_ > 1)
613 s << job_index_ << "> ";
614 s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ")
615 << reason << '\n';
616 message_queue_send(MessageType::log, s.str());
617}
618
619void
621{
622 if (!print_log_)
623 return;
624
626 if (num_jobs_ > 1)
627 s << job_index_ << "> ";
628 s << msg;
629 message_queue_send(MessageType::log, s.str());
630}
631
632} // namespace test
633
634namespace detail {
635template class multi_runner_base<true>;
636template class multi_runner_base<false>;
637} // namespace detail
638
639} // 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:20
std::string full_name() const
Return the canonical suite name as a string.
Definition suite_info.h:74
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:6
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)