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 {
33namespace test {
34
35namespace detail {
36
38fmtdur(typename clock_type::duration const& d)
39{
40 using namespace std::chrono;
41 auto const ms = duration_cast<milliseconds>(d);
42 if (ms < seconds{1})
43 return boost::lexical_cast<std::string>(ms.count()) + "ms";
45 ss << std::fixed << std::setprecision(1) << (ms.count() / 1000.) << "s";
46 return ss.str();
47}
48
49//------------------------------------------------------------------------------
50
51void
53{
54 ++cases;
55 total += r.total;
56 failed += r.failed;
57}
58
59//------------------------------------------------------------------------------
60
61void
63{
64 ++suites;
65 total += r.total;
66 cases += r.cases;
67 failed += r.failed;
68 auto const elapsed = clock_type::now() - r.start;
69 if (elapsed >= std::chrono::seconds{1})
70 {
71 auto const iter = std::lower_bound(
72 top.begin(),
73 top.end(),
74 elapsed,
75 [](run_time const& t1, typename clock_type::duration const& t2) {
76 return t1.second > t2;
77 });
78
79 if (iter != top.end())
80 {
81 if (top.size() == max_top && iter == top.end() - 1)
82 {
83 // avoid invalidating the iterator
84 *iter = run_time{
85 static_string{static_string::string_view_type{r.name}},
86 elapsed};
87 }
88 else
89 {
90 if (top.size() == max_top)
91 top.resize(top.size() - 1);
92 top.emplace(
93 iter,
94 static_string{static_string::string_view_type{r.name}},
95 elapsed);
96 }
97 }
98 else if (top.size() < max_top)
99 {
100 top.emplace_back(
101 static_string{static_string::string_view_type{r.name}},
102 elapsed);
103 }
104 }
105}
106
107void
109{
110 suites += r.suites;
111 total += r.total;
112 cases += r.cases;
113 failed += r.failed;
114
115 // combine the two top collections
116 boost::container::static_vector<run_time, 2 * max_top> top_result;
117 top_result.resize(top.size() + r.top.size());
119 top.begin(),
120 top.end(),
121 r.top.begin(),
122 r.top.end(),
123 top_result.begin(),
124 [](run_time const& t1, run_time const& t2) {
125 return t1.second > t2.second;
126 });
128 if (top_result.size() > max_top)
129 top_result.resize(max_top);
131 top = top_result;
132}
134template <class S>
135void
137{
138 using namespace beast::unit_test;
140 if (top.size() > 0)
141 {
142 s << "Longest suite times:\n";
143 for (auto const& [name, dur] : top)
144 s << std::setw(8) << fmtdur(dur) << " " << name << '\n';
146
147 auto const elapsed = clock_type::now() - start;
148 s << fmtdur(elapsed) << ", " << amount{suites, "suite"} << ", "
149 << amount{cases, "case"} << ", " << amount{total, "test"} << " total, "
150 << amount{failed, "failure"} << std::endl;
151}
153//------------------------------------------------------------------------------
154
155template <bool IsParent>
158{
159 return job_index_++;
160}
161
162template <bool IsParent>
165{
166 return test_index_++;
167}
168
169template <bool IsParent>
170bool
173 return any_failed_;
174}
176template <bool IsParent>
177void
180 any_failed_ = any_failed_ || v;
181}
183template <bool IsParent>
186{
187 std::lock_guard l{m_};
188 return results_.total;
189}
190
191template <bool IsParent>
195 std::lock_guard l{m_};
196 return results_.suites;
197}
199template <bool IsParent>
200void
202{
203 ++keep_alive_;
205
206template <bool IsParent>
209{
210 return keep_alive_;
211}
212
213template <bool IsParent>
214void
216{
217 std::lock_guard l{m_};
218 results_.merge(r);
219}
220
221template <bool IsParent>
222template <class S>
223void
225{
226 std::lock_guard l{m_};
227 results_.print(s);
228}
229
230template <bool IsParent>
232{
233 try
234 {
235 if (IsParent)
236 {
237 // cleanup any leftover state for any previous failed runs
238 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
239 boost::interprocess::message_queue::remove(message_queue_name_);
240 }
241
242 shared_mem_ = boost::interprocess::shared_memory_object{
244 IsParent,
245 boost::interprocess::create_only_t,
246 boost::interprocess::open_only_t>{},
248 boost::interprocess::read_write};
249
250 if (IsParent)
251 {
252 shared_mem_.truncate(sizeof(inner));
254 std::make_unique<boost::interprocess::message_queue>(
255 boost::interprocess::create_only,
257 /*max messages*/ 16,
258 /*max message size*/ 1 << 20);
259 }
260 else
261 {
263 std::make_unique<boost::interprocess::message_queue>(
264 boost::interprocess::open_only, message_queue_name_);
265 }
266
267 region_ = boost::interprocess::mapped_region{
268 shared_mem_, boost::interprocess::read_write};
269 if (IsParent)
270 inner_ = new (region_.get_address()) inner{};
271 else
272 inner_ = reinterpret_cast<inner*>(region_.get_address());
273 }
274 catch (...)
275 {
276 if (IsParent)
277 {
278 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
279 boost::interprocess::message_queue::remove(message_queue_name_);
280 }
281 throw;
282 }
283}
284
285template <bool IsParent>
287{
288 if (IsParent)
289 {
290 inner_->~inner();
291 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
292 boost::interprocess::message_queue::remove(message_queue_name_);
293 }
294}
295
296template <bool IsParent>
299{
300 return inner_->checkout_test_index();
301}
302
303template <bool IsParent>
306{
307 return inner_->checkout_job_index();
308}
309
310template <bool IsParent>
311bool
313{
314 return inner_->any_failed();
315}
316
317template <bool IsParent>
318void
320{
321 return inner_->any_failed(v);
322}
323
324template <bool IsParent>
325void
327{
328 inner_->add(r);
329}
330
331template <bool IsParent>
332void
334{
336}
337
338template <bool IsParent>
341{
343}
344
345template <bool IsParent>
346template <class S>
347void
349{
351}
352
353template <bool IsParent>
354void
356 MessageType mt,
357 std::string const& s)
358{
359 // must use a mutex since the two "sends" must happen in order
361 message_queue_->send(&mt, sizeof(mt), /*priority*/ 0);
362 message_queue_->send(s.c_str(), s.size(), /*priority*/ 0);
363}
364
365template <bool IsParent>
368{
369 return inner_->tests();
370}
371
372template <bool IsParent>
375{
376 return inner_->suites();
377}
378
379template <bool IsParent>
380void
382{
384 results.failed += failures;
385 add(results);
386 any_failed(failures != 0);
387}
388
389} // namespace detail
390
391//------------------------------------------------------------------------------
392
394{
396 std::vector<char> buf(1 << 20);
397 while (this->continue_message_queue_ ||
398 this->message_queue_->get_num_msg())
399 {
400 // let children know the parent is still alive
401 this->inc_keep_alive_count();
402 if (!this->message_queue_->get_num_msg())
403 {
404 // If a child does not see the keep alive count incremented,
405 // it will assume the parent has died. This sleep time needs
406 // to be small enough so the child will see increments from
407 // a live parent.
409 continue;
410 }
411 try
412 {
413 std::size_t recvd_size = 0;
414 unsigned int priority = 0;
415 this->message_queue_->receive(
416 buf.data(), buf.size(), recvd_size, priority);
417 if (!recvd_size)
418 continue;
419 assert(recvd_size == 1);
420 MessageType mt{*reinterpret_cast<MessageType*>(buf.data())};
421
422 this->message_queue_->receive(
423 buf.data(), buf.size(), recvd_size, priority);
424 if (recvd_size)
425 {
426 std::string s{buf.data(), recvd_size};
427 switch (mt)
428 {
429 case MessageType::log:
430 this->os_ << s;
431 this->os_.flush();
432 break;
433 case MessageType::test_start:
434 running_suites_.insert(std::move(s));
435 break;
436 case MessageType::test_end:
438 break;
439 default:
440 assert(0); // unknown message type
441 }
442 }
443 }
444 catch (std::exception const& e)
445 {
446 std::cerr << "Error: " << e.what()
447 << " reading unit test message queue.\n";
448 return;
449 }
450 catch (...)
451 {
452 std::cerr << "Unknown error reading unit test message queue.\n";
453 return;
454 }
455 }
456 });
457}
458
460{
461 using namespace beast::unit_test;
462
465
467
468 for (auto const& s : running_suites_)
469 {
470 os_ << "\nSuite: " << s
471 << " failed to complete. The child process may have crashed.\n";
472 }
473}
474
475bool
477{
478 return multi_runner_base<true>::any_failed();
479}
480
483{
484 return multi_runner_base<true>::tests();
485}
486
489{
490 return multi_runner_base<true>::suites();
491}
492
493void
495{
496 multi_runner_base<true>::add_failures(failures);
497}
498
499//------------------------------------------------------------------------------
500
502 std::size_t num_jobs,
503 bool quiet,
504 bool print_log)
505 : job_index_{checkout_job_index()}
506 , num_jobs_{num_jobs}
507 , quiet_{quiet}
508 , print_log_{!quiet || print_log}
509{
510 if (num_jobs_ > 1)
511 {
513 std::size_t last_count = get_keep_alive_count();
514 while (this->continue_keep_alive_)
515 {
516 // Use a small sleep time so in the normal case the child
517 // process may shutdown quickly. However, to protect against
518 // false alarms, use a longer sleep time later on.
520 auto cur_count = this->get_keep_alive_count();
521 if (cur_count == last_count)
522 {
523 // longer sleep time to protect against false alarms
525 cur_count = this->get_keep_alive_count();
526 if (cur_count == last_count)
527 {
528 // assume parent process is no longer alive
529 std::cerr << "multi_runner_child " << job_index_
530 << ": Assuming parent died, exiting.\n";
531 std::exit(EXIT_FAILURE);
532 }
533 }
534 last_count = cur_count;
535 }
536 });
537 }
538}
539
541{
542 if (num_jobs_ > 1)
543 {
544 continue_keep_alive_ = false;
546 }
547
548 add(results_);
549}
550
553{
554 return results_.total;
555}
556
559{
560 return results_.suites;
561}
562
563void
565{
566 results_.failed += failures;
567 any_failed(failures != 0);
568}
569
570void
572{
574 message_queue_send(MessageType::test_start, suite_results_.name);
575}
576
577void
579{
581 message_queue_send(MessageType::test_end, suite_results_.name);
582}
583
584void
586{
588
589 if (quiet_)
590 return;
591
593 if (num_jobs_ > 1)
594 s << job_index_ << "> ";
596 << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << '\n';
597 message_queue_send(MessageType::log, s.str());
598}
599
600void
602{
604}
605
606void
608{
610}
611
612void
614{
618 if (num_jobs_ > 1)
619 s << job_index_ << "> ";
620 s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ")
621 << reason << '\n';
622 message_queue_send(MessageType::log, s.str());
623}
624
625void
627{
628 if (!print_log_)
629 return;
630
632 if (num_jobs_ > 1)
633 s << job_index_ << "> ";
634 s << msg;
635 message_queue_send(MessageType::log, s.str());
636}
637
638namespace detail {
639template class multi_runner_base<true>;
640template class multi_runner_base<false>;
641} // namespace detail
642
643} // namespace test
644} // 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
static constexpr const char * shared_mem_name_
Definition: multi_runner.h:155
boost::interprocess::shared_memory_object shared_mem_
Definition: multi_runner.h:164
boost::interprocess::mapped_region region_
Definition: multi_runner.h:165
static constexpr const char * message_queue_name_
Definition: multi_runner.h:158
void add_failures(std::size_t failures)
void message_queue_send(MessageType mt, std::string const &s)
std::unique_ptr< boost::interprocess::message_queue > message_queue_
Definition: multi_runner.h:168
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_
Definition: multi_runner.h:261
virtual void on_case_end() override
Called when a new case ends.
detail::suite_results suite_results_
Definition: multi_runner.h:260
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_
Definition: multi_runner.h:266
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_
Definition: multi_runner.h:224
void add_failures(std::size_t failures)
std::set< std::string > running_suites_
Definition: multi_runner.h:227
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 join(T... args)
T lower_bound(T... args)
T merge(T... args)
std::string fmtdur(typename clock_type::duration const &d)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
STL namespace.
T setprecision(T... args)
T setw(T... args)
T size(T... args)
T sleep_for(T... args)
T str(T... args)
boost::interprocess::interprocess_mutex m_
Definition: multi_runner.h:120
void merge(results const &r)
boost::beast::static_string< 256 > static_string
Definition: multi_runner.h:78
void add(suite_results const &r)
std::pair< static_string, typename clock_type::duration > run_time
Definition: multi_runner.h:81
boost::container::static_vector< run_time, max_top > top
Definition: multi_runner.h:89
clock_type::time_point start
Definition: multi_runner.h:90
void add(case_results const &r)
clock_type::time_point start
Definition: multi_runner.h:66
T what(T... args)