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#include <xrpl/beast/unit_test/amount.h>
22
23#include <boost/lexical_cast.hpp>
24
25#include <algorithm>
26#include <iomanip>
27#include <iostream>
28#include <sstream>
29#include <vector>
30
31namespace ripple {
32namespace test {
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 });
126
127 if (top_result.size() > max_top)
128 top_result.resize(max_top);
129
130 top = top_result;
131}
132
133template <class S>
134void
137 using namespace beast::unit_test;
138
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';
144 }
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}
151
152//------------------------------------------------------------------------------
153
154template <bool IsParent>
157{
158 return job_index_++;
159}
160
161template <bool IsParent>
164{
165 return test_index_++;
166}
167
168template <bool IsParent>
169bool
171{
172 return any_failed_;
173}
174
175template <bool IsParent>
176void
178{
179 any_failed_ = any_failed_ || v;
180}
181
182template <bool IsParent>
186 std::lock_guard l{m_};
187 return results_.total;
189
190template <bool IsParent>
193{
195 return results_.suites;
196}
197
198template <bool IsParent>
199void
202 ++keep_alive_;
203}
205template <bool IsParent>
208{
209 return keep_alive_;
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));
253 std::make_unique<boost::interprocess::message_queue>(
254 boost::interprocess::create_only,
256 /*max messages*/ 16,
257 /*max message size*/ 1 << 20);
258 }
259 else
260 {
262 std::make_unique<boost::interprocess::message_queue>(
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>
298{
299 return inner_->checkout_test_index();
300}
301
302template <bool IsParent>
305{
306 return inner_->checkout_job_index();
307}
308
309template <bool IsParent>
310bool
312{
313 return inner_->any_failed();
314}
315
316template <bool IsParent>
317void
319{
320 return inner_->any_failed(v);
321}
322
323template <bool IsParent>
324void
326{
327 inner_->add(r);
328}
329
330template <bool IsParent>
331void
333{
335}
336
337template <bool IsParent>
340{
342}
343
344template <bool IsParent>
345template <class S>
346void
348{
350}
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
390//------------------------------------------------------------------------------
391
393{
395 std::vector<char> buf(1 << 20);
396 while (this->continue_message_queue_ ||
397 this->message_queue_->get_num_msg())
398 {
399 // let children know the parent is still alive
400 this->inc_keep_alive_count();
401 if (!this->message_queue_->get_num_msg())
402 {
403 // If a child does not see the keep alive count incremented,
404 // it will assume the parent has died. This sleep time needs
405 // to be small enough so the child will see increments from
406 // a live parent.
408 continue;
409 }
410 try
411 {
412 std::size_t recvd_size = 0;
413 unsigned int priority = 0;
414 this->message_queue_->receive(
415 buf.data(), buf.size(), recvd_size, priority);
416 if (!recvd_size)
417 continue;
418 assert(recvd_size == 1);
419 MessageType mt{*reinterpret_cast<MessageType*>(buf.data())};
420
421 this->message_queue_->receive(
422 buf.data(), buf.size(), recvd_size, priority);
423 if (recvd_size)
424 {
425 std::string s{buf.data(), recvd_size};
426 switch (mt)
427 {
428 case MessageType::log:
429 this->os_ << s;
430 this->os_.flush();
431 break;
432 case MessageType::test_start:
433 running_suites_.insert(std::move(s));
434 break;
435 case MessageType::test_end:
437 break;
438 default:
439 assert(0); // unknown message type
440 }
441 }
442 }
443 catch (std::exception const& e)
444 {
445 std::cerr << "Error: " << e.what()
446 << " reading unit test message queue.\n";
447 return;
448 }
449 catch (...)
450 {
451 std::cerr << "Unknown error reading unit test message queue.\n";
452 return;
453 }
454 }
455 });
456}
457
459{
460 using namespace beast::unit_test;
461
464
466
467 for (auto const& s : running_suites_)
468 {
469 os_ << "\nSuite: " << s
470 << " failed to complete. The child process may have crashed.\n";
471 }
472}
473
474bool
476{
477 return multi_runner_base<true>::any_failed();
478}
479
482{
483 return multi_runner_base<true>::tests();
484}
485
488{
489 return multi_runner_base<true>::suites();
490}
491
492void
494{
495 multi_runner_base<true>::add_failures(failures);
496}
497
498//------------------------------------------------------------------------------
499
501 std::size_t num_jobs,
502 bool quiet,
503 bool print_log)
504 : job_index_{checkout_job_index()}
505 , num_jobs_{num_jobs}
506 , quiet_{quiet}
507 , print_log_{!quiet || print_log}
508{
509 if (num_jobs_ > 1)
510 {
512 std::size_t last_count = get_keep_alive_count();
513 while (this->continue_keep_alive_)
514 {
515 // Use a small sleep time so in the normal case the child
516 // process may shutdown quickly. However, to protect against
517 // false alarms, use a longer sleep time later on.
519 auto cur_count = this->get_keep_alive_count();
520 if (cur_count == last_count)
521 {
522 // longer sleep time to protect against false alarms
524 cur_count = this->get_keep_alive_count();
525 if (cur_count == last_count)
526 {
527 // assume parent process is no longer alive
528 std::cerr << "multi_runner_child " << job_index_
529 << ": Assuming parent died, exiting.\n";
530 std::exit(EXIT_FAILURE);
531 }
532 }
533 last_count = cur_count;
534 }
535 });
536 }
537}
538
540{
541 if (num_jobs_ > 1)
542 {
543 continue_keep_alive_ = false;
545 }
546
547 add(results_);
548}
549
552{
553 return results_.total;
554}
555
558{
559 return results_.suites;
560}
561
562void
564{
565 results_.failed += failures;
566 any_failed(failures != 0);
567}
568
569void
571{
573 message_queue_send(MessageType::test_start, suite_results_.name);
574}
575
576void
578{
580 message_queue_send(MessageType::test_end, suite_results_.name);
581}
582
583void
585{
587
588 if (quiet_)
589 return;
590
592 if (num_jobs_ > 1)
593 s << job_index_ << "> ";
595 << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << '\n';
596 message_queue_send(MessageType::log, s.str());
597}
598
599void
601{
603}
604
605void
607{
609}
610
611void
613{
617 if (num_jobs_ > 1)
618 s << job_index_ << "> ";
619 s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ")
620 << reason << '\n';
621 message_queue_send(MessageType::log, s.str());
622}
623
624void
626{
627 if (!print_log_)
628 return;
629
631 if (num_jobs_ > 1)
632 s << job_index_ << "> ";
633 s << msg;
634 message_queue_send(MessageType::log, s.str());
635}
636
637namespace detail {
638template class multi_runner_base<true>;
639template class multi_runner_base<false>;
640} // namespace detail
641
642} // namespace test
643} // 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)
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)