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
35extern void
36incPorts(int times);
37
38namespace detail {
39
41fmtdur(typename clock_type::duration const& d)
42{
43 using namespace std::chrono;
44 auto const ms = duration_cast<milliseconds>(d);
45 if (ms < seconds{1})
46 return boost::lexical_cast<std::string>(ms.count()) + "ms";
48 ss << std::fixed << std::setprecision(1) << (ms.count() / 1000.) << "s";
49 return ss.str();
50}
51
52//------------------------------------------------------------------------------
53
54void
56{
57 ++cases;
58 total += r.total;
59 failed += r.failed;
60}
61
62//------------------------------------------------------------------------------
63
64void
66{
67 ++suites;
68 total += r.total;
69 cases += r.cases;
70 failed += r.failed;
71 auto const elapsed = clock_type::now() - r.start;
72 if (elapsed >= std::chrono::seconds{1})
73 {
74 auto const iter = std::lower_bound(
75 top.begin(),
76 top.end(),
77 elapsed,
78 [](run_time const& t1, typename clock_type::duration const& t2) {
79 return t1.second > t2;
80 });
81
82 if (iter != top.end())
83 {
84 if (top.size() == max_top && iter == top.end() - 1)
85 {
86 // avoid invalidating the iterator
87 *iter = run_time{
88 static_string{static_string::string_view_type{r.name}},
89 elapsed};
90 }
91 else
92 {
93 if (top.size() == max_top)
94 top.resize(top.size() - 1);
95 top.emplace(
96 iter,
97 static_string{static_string::string_view_type{r.name}},
98 elapsed);
99 }
100 }
101 else if (top.size() < max_top)
102 {
103 top.emplace_back(
104 static_string{static_string::string_view_type{r.name}},
105 elapsed);
106 }
107 }
108}
109
110void
112{
113 suites += r.suites;
114 total += r.total;
115 cases += r.cases;
116 failed += r.failed;
117
118 // combine the two top collections
119 boost::container::static_vector<run_time, 2 * max_top> top_result;
120 top_result.resize(top.size() + r.top.size());
122 top.begin(),
123 top.end(),
124 r.top.begin(),
125 r.top.end(),
126 top_result.begin(),
127 [](run_time const& t1, run_time const& t2) {
128 return t1.second > t2.second;
129 });
131 if (top_result.size() > max_top)
132 top_result.resize(max_top);
134 top = top_result;
135}
137template <class S>
138void
140{
141 using namespace beast::unit_test;
143 if (top.size() > 0)
144 {
145 s << "Longest suite times:\n";
146 for (auto const& [name, dur] : top)
147 s << std::setw(8) << fmtdur(dur) << " " << name << '\n';
149
150 auto const elapsed = clock_type::now() - start;
151 s << fmtdur(elapsed) << ", " << amount{suites, "suite"} << ", "
152 << amount{cases, "case"} << ", " << amount{total, "test"} << " total, "
153 << amount{failed, "failure"} << std::endl;
154}
155
156//------------------------------------------------------------------------------
157
158template <bool IsParent>
161{
162 return job_index_++;
163}
164
165template <bool IsParent>
168{
169 return test_index_++;
170}
171
172template <bool IsParent>
173bool
176 return any_failed_;
177}
178
179template <bool IsParent>
180void
183 any_failed_ = any_failed_ || v;
184}
186template <bool IsParent>
189{
190 std::lock_guard l{m_};
191 return results_.total;
192}
193
194template <bool IsParent>
197{
199 return results_.suites;
200}
202template <bool IsParent>
203void
205{
206 ++keep_alive_;
208
209template <bool IsParent>
212{
213 return keep_alive_;
214}
215
216template <bool IsParent>
217void
219{
220 std::lock_guard l{m_};
221 results_.merge(r);
222}
223
224template <bool IsParent>
225template <class S>
226void
228{
229 std::lock_guard l{m_};
230 results_.print(s);
231}
232
233template <bool IsParent>
235{
236 try
237 {
238 if (IsParent)
239 {
240 // cleanup any leftover state for any previous failed runs
241 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
242 boost::interprocess::message_queue::remove(message_queue_name_);
243 }
244
245 shared_mem_ = boost::interprocess::shared_memory_object{
247 IsParent,
248 boost::interprocess::create_only_t,
249 boost::interprocess::open_only_t>{},
251 boost::interprocess::read_write};
252
253 if (IsParent)
254 {
255 shared_mem_.truncate(sizeof(inner));
257 std::make_unique<boost::interprocess::message_queue>(
258 boost::interprocess::create_only,
260 /*max messages*/ 16,
261 /*max message size*/ 1 << 20);
262 }
263 else
264 {
266 std::make_unique<boost::interprocess::message_queue>(
267 boost::interprocess::open_only, message_queue_name_);
268 }
269
270 region_ = boost::interprocess::mapped_region{
271 shared_mem_, boost::interprocess::read_write};
272 if (IsParent)
273 inner_ = new (region_.get_address()) inner{};
274 else
275 inner_ = reinterpret_cast<inner*>(region_.get_address());
276 }
277 catch (...)
278 {
279 if (IsParent)
280 {
281 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
282 boost::interprocess::message_queue::remove(message_queue_name_);
283 }
284 throw;
285 }
286}
287
288template <bool IsParent>
290{
291 if (IsParent)
292 {
293 inner_->~inner();
294 boost::interprocess::shared_memory_object::remove(shared_mem_name_);
295 boost::interprocess::message_queue::remove(message_queue_name_);
296 }
297}
298
299template <bool IsParent>
302{
303 return inner_->checkout_test_index();
304}
305
306template <bool IsParent>
309{
310 return inner_->checkout_job_index();
311}
312
313template <bool IsParent>
314bool
316{
317 return inner_->any_failed();
318}
319
320template <bool IsParent>
321void
323{
324 return inner_->any_failed(v);
325}
326
327template <bool IsParent>
328void
330{
331 inner_->add(r);
332}
333
334template <bool IsParent>
335void
337{
339}
340
341template <bool IsParent>
344{
346}
347
348template <bool IsParent>
349template <class S>
350void
352{
354}
355
356template <bool IsParent>
357void
359 MessageType mt,
360 std::string const& s)
361{
362 // must use a mutex since the two "sends" must happen in order
364 message_queue_->send(&mt, sizeof(mt), /*priority*/ 0);
365 message_queue_->send(s.c_str(), s.size(), /*priority*/ 0);
366}
367
368template <bool IsParent>
371{
372 return inner_->tests();
373}
374
375template <bool IsParent>
378{
379 return inner_->suites();
380}
381
382template <bool IsParent>
383void
385{
387 results.failed += failures;
388 add(results);
389 any_failed(failures != 0);
390}
391
392} // namespace detail
393
394//------------------------------------------------------------------------------
395
397{
399 std::vector<char> buf(1 << 20);
400 while (this->continue_message_queue_ ||
401 this->message_queue_->get_num_msg())
402 {
403 // let children know the parent is still alive
404 this->inc_keep_alive_count();
405 if (!this->message_queue_->get_num_msg())
406 {
407 // If a child does not see the keep alive count incremented,
408 // it will assume the parent has died. This sleep time needs
409 // to be small enough so the child will see increments from
410 // a live parent.
412 continue;
413 }
414 try
415 {
416 std::size_t recvd_size = 0;
417 unsigned int priority = 0;
418 this->message_queue_->receive(
419 buf.data(), buf.size(), recvd_size, priority);
420 if (!recvd_size)
421 continue;
422 assert(recvd_size == 1);
423 MessageType mt{*reinterpret_cast<MessageType*>(buf.data())};
424
425 this->message_queue_->receive(
426 buf.data(), buf.size(), recvd_size, priority);
427 if (recvd_size)
428 {
429 std::string s{buf.data(), recvd_size};
430 switch (mt)
431 {
432 case MessageType::log:
433 this->os_ << s;
434 this->os_.flush();
435 break;
436 case MessageType::test_start:
437 running_suites_.insert(std::move(s));
438 break;
439 case MessageType::test_end:
441 break;
442 default:
443 assert(0); // unknown message type
444 }
445 }
446 }
447 catch (std::exception const& e)
448 {
449 std::cerr << "Error: " << e.what()
450 << " reading unit test message queue.\n";
451 return;
452 }
453 catch (...)
454 {
455 std::cerr << "Unknown error reading unit test message queue.\n";
456 return;
457 }
458 }
459 });
460}
461
463{
464 using namespace beast::unit_test;
465
468
470
471 for (auto const& s : running_suites_)
472 {
473 os_ << "\nSuite: " << s
474 << " failed to complete. The child process may have crashed.\n";
475 }
476}
477
478bool
480{
481 return multi_runner_base<true>::any_failed();
482}
483
486{
487 return multi_runner_base<true>::tests();
488}
489
492{
493 return multi_runner_base<true>::suites();
494}
495
496void
498{
499 multi_runner_base<true>::add_failures(failures);
500}
501
502//------------------------------------------------------------------------------
503
505 std::size_t num_jobs,
506 bool quiet,
507 bool print_log)
508 : job_index_{checkout_job_index()}
509 , num_jobs_{num_jobs}
510 , quiet_{quiet}
511 , print_log_{!quiet || print_log}
512{
513 // incPort twice (2*jobIndex_) because some tests need two envs
515
516 if (num_jobs_ > 1)
517 {
519 std::size_t last_count = get_keep_alive_count();
520 while (this->continue_keep_alive_)
521 {
522 // Use a small sleep time so in the normal case the child
523 // process may shutdown quickly. However, to protect against
524 // false alarms, use a longer sleep time later on.
526 auto cur_count = this->get_keep_alive_count();
527 if (cur_count == last_count)
528 {
529 // longer sleep time to protect against false alarms
531 cur_count = this->get_keep_alive_count();
532 if (cur_count == last_count)
533 {
534 // assume parent process is no longer alive
535 std::cerr << "multi_runner_child " << job_index_
536 << ": Assuming parent died, exiting.\n";
537 std::exit(EXIT_FAILURE);
538 }
539 }
540 last_count = cur_count;
541 }
542 });
543 }
544}
545
547{
548 if (num_jobs_ > 1)
549 {
550 continue_keep_alive_ = false;
552 }
553
554 add(results_);
555}
556
559{
560 return results_.total;
561}
562
565{
566 return results_.suites;
567}
568
569void
571{
572 results_.failed += failures;
573 any_failed(failures != 0);
574}
575
576void
578{
580 message_queue_send(MessageType::test_start, suite_results_.name);
581}
582
583void
585{
587 message_queue_send(MessageType::test_end, suite_results_.name);
588}
589
590void
592{
594
595 if (quiet_)
596 return;
597
599 if (num_jobs_ > 1)
600 s << job_index_ << "> ";
602 << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << '\n';
603 message_queue_send(MessageType::log, s.str());
604}
605
606void
608{
610}
611
612void
614{
616}
617
618void
620{
624 if (num_jobs_ > 1)
625 s << job_index_ << "> ";
626 s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ")
627 << reason << '\n';
628 message_queue_send(MessageType::log, s.str());
629}
630
631void
633{
634 if (!print_log_)
635 return;
636
638 if (num_jobs_ > 1)
639 s << job_index_ << "> ";
640 s << msg;
641 message_queue_send(MessageType::log, s.str());
642}
643
644namespace detail {
645template class multi_runner_base<true>;
646template class multi_runner_base<false>;
647} // namespace detail
648
649} // namespace test
650} // 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)
void incPorts(int times)
Definition: envconfig.cpp:30
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)