rippled
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 <beast/unit_test/amount.hpp>
23 
24 #include <boost/lexical_cast.hpp>
25 
26 #include <algorithm>
27 #include <iomanip>
28 #include <iostream>
29 #include <sstream>
30 
31 namespace ripple {
32 namespace test {
33 
34 extern void
35 incPorts(int times);
36 
37 namespace detail {
38 
40 fmtdur(typename clock_type::duration const& d)
41 {
42  using namespace std::chrono;
43  auto const ms = duration_cast<milliseconds>(d);
44  if (ms < seconds{1})
45  return boost::lexical_cast<std::string>(ms.count()) + "ms";
47  ss << std::fixed << std::setprecision(1) << (ms.count() / 1000.) << "s";
48  return ss.str();
49 }
50 
51 //------------------------------------------------------------------------------
52 
53 void
55 {
56  ++cases;
57  total += r.total;
58  failed += r.failed;
59 }
60 
61 //------------------------------------------------------------------------------
62 
63 void
65 {
66  ++suites;
67  total += r.total;
68  cases += r.cases;
69  failed += r.failed;
70  auto const elapsed = clock_type::now() - r.start;
71  if (elapsed >= std::chrono::seconds{1})
72  {
73  auto const iter = std::lower_bound(
74  top.begin(),
75  top.end(),
76  elapsed,
77  [](run_time const& t1, typename clock_type::duration const& t2) {
78  return t1.second > t2;
79  });
80 
81  if (iter != top.end())
82  {
83  if (top.size() == max_top && iter == top.end() - 1)
84  {
85  // avoid invalidating the iterator
86  *iter = run_time{
87  static_string{static_string::string_view_type{r.name}},
88  elapsed};
89  }
90  else
91  {
92  if (top.size() == max_top)
93  top.resize(top.size() - 1);
94  top.emplace(
95  iter,
96  static_string{static_string::string_view_type{r.name}},
97  elapsed);
98  }
99  }
100  else if (top.size() < max_top)
101  {
102  top.emplace_back(
103  static_string{static_string::string_view_type{r.name}},
104  elapsed);
105  }
106  }
107 }
108 
109 void
111 {
112  suites += r.suites;
113  total += r.total;
114  cases += r.cases;
115  failed += r.failed;
116 
117  // combine the two top collections
118  boost::container::static_vector<run_time, 2 * max_top> top_result;
119  top_result.resize(top.size() + r.top.size());
120  std::merge(
121  top.begin(),
122  top.end(),
123  r.top.begin(),
124  r.top.end(),
125  top_result.begin(),
126  [](run_time const& t1, run_time const& t2) {
127  return t1.second > t2.second;
128  });
129 
130  if (top_result.size() > max_top)
131  top_result.resize(max_top);
132 
133  top = top_result;
134 }
135 
136 template <class S>
137 void
139 {
140  using namespace beast::unit_test;
141 
142  if (top.size() > 0)
143  {
144  s << "Longest suite times:\n";
145  for (auto const& [name, dur] : top)
146  s << std::setw(8) << fmtdur(dur) << " " << name << '\n';
147  }
148 
149  auto const elapsed = clock_type::now() - start;
150  s << fmtdur(elapsed) << ", " << amount{suites, "suite"} << ", "
151  << amount{cases, "case"} << ", " << amount{total, "test"} << " total, "
152  << amount{failed, "failure"} << std::endl;
153 }
154 
155 //------------------------------------------------------------------------------
156 
157 template <bool IsParent>
160 {
161  return job_index_++;
162 }
163 
164 template <bool IsParent>
167 {
168  return test_index_++;
169 }
170 
171 template <bool IsParent>
172 bool
174 {
175  return any_failed_;
176 }
177 
178 template <bool IsParent>
179 void
181 {
182  any_failed_ = any_failed_ || v;
183 }
184 
185 template <bool IsParent>
188 {
189  std::lock_guard l{m_};
190  return results_.total;
191 }
192 
193 template <bool IsParent>
196 {
197  std::lock_guard l{m_};
198  return results_.suites;
199 }
200 
201 template <bool IsParent>
202 void
204 {
205  ++keep_alive_;
206 }
207 
208 template <bool IsParent>
211 {
212  return keep_alive_;
213 }
214 
215 template <bool IsParent>
216 void
218 {
219  std::lock_guard l{m_};
220  results_.merge(r);
221 }
222 
223 template <bool IsParent>
224 template <class S>
225 void
227 {
228  std::lock_guard l{m_};
229  results_.print(s);
230 }
231 
232 template <bool IsParent>
234 {
235  try
236  {
237  if (IsParent)
238  {
239  // cleanup any leftover state for any previous failed runs
240  boost::interprocess::shared_memory_object::remove(shared_mem_name_);
241  boost::interprocess::message_queue::remove(message_queue_name_);
242  }
243 
244  shared_mem_ = boost::interprocess::shared_memory_object{
246  IsParent,
247  boost::interprocess::create_only_t,
248  boost::interprocess::open_only_t>{},
249  shared_mem_name_,
250  boost::interprocess::read_write};
251 
252  if (IsParent)
253  {
254  shared_mem_.truncate(sizeof(inner));
255  message_queue_ =
256  std::make_unique<boost::interprocess::message_queue>(
257  boost::interprocess::create_only,
258  message_queue_name_,
259  /*max messages*/ 16,
260  /*max message size*/ 1 << 20);
261  }
262  else
263  {
264  message_queue_ =
265  std::make_unique<boost::interprocess::message_queue>(
266  boost::interprocess::open_only, message_queue_name_);
267  }
268 
269  region_ = boost::interprocess::mapped_region{
270  shared_mem_, boost::interprocess::read_write};
271  if (IsParent)
272  inner_ = new (region_.get_address()) inner{};
273  else
274  inner_ = reinterpret_cast<inner*>(region_.get_address());
275  }
276  catch (...)
277  {
278  if (IsParent)
279  {
280  boost::interprocess::shared_memory_object::remove(shared_mem_name_);
281  boost::interprocess::message_queue::remove(message_queue_name_);
282  }
283  throw;
284  }
285 }
286 
287 template <bool IsParent>
289 {
290  if (IsParent)
291  {
292  inner_->~inner();
293  boost::interprocess::shared_memory_object::remove(shared_mem_name_);
294  boost::interprocess::message_queue::remove(message_queue_name_);
295  }
296 }
297 
298 template <bool IsParent>
301 {
302  return inner_->checkout_test_index();
303 }
304 
305 template <bool IsParent>
308 {
309  return inner_->checkout_job_index();
310 }
311 
312 template <bool IsParent>
313 bool
315 {
316  return inner_->any_failed();
317 }
318 
319 template <bool IsParent>
320 void
322 {
323  return inner_->any_failed(v);
324 }
325 
326 template <bool IsParent>
327 void
329 {
330  inner_->add(r);
331 }
332 
333 template <bool IsParent>
334 void
336 {
337  inner_->inc_keep_alive_count();
338 }
339 
340 template <bool IsParent>
343 {
344  return inner_->get_keep_alive_count();
345 }
346 
347 template <bool IsParent>
348 template <class S>
349 void
351 {
352  inner_->print_results(s);
353 }
354 
355 template <bool IsParent>
356 void
358  MessageType mt,
359  std::string const& s)
360 {
361  // must use a mutex since the two "sends" must happen in order
362  std::lock_guard l{inner_->m_};
363  message_queue_->send(&mt, sizeof(mt), /*priority*/ 0);
364  message_queue_->send(s.c_str(), s.size(), /*priority*/ 0);
365 }
366 
367 template <bool IsParent>
370 {
371  return inner_->tests();
372 }
373 
374 template <bool IsParent>
377 {
378  return inner_->suites();
379 }
380 
381 template <bool IsParent>
382 void
384 {
386  results.failed += failures;
387  add(results);
388  any_failed(failures != 0);
389 }
390 
391 template <bool IsParent>
393 template <bool IsParent>
395 
396 } // namespace detail
397 
398 //------------------------------------------------------------------------------
399 
401 {
403  std::vector<char> buf(1 << 20);
404  while (this->continue_message_queue_ ||
405  this->message_queue_->get_num_msg())
406  {
407  // let children know the parent is still alive
408  this->inc_keep_alive_count();
409  if (!this->message_queue_->get_num_msg())
410  {
411  // If a child does not see the keep alive count incremented,
412  // it will assume the parent has died. This sleep time needs
413  // to be small enough so the child will see increments from
414  // a live parent.
416  continue;
417  }
418  try
419  {
420  std::size_t recvd_size = 0;
421  unsigned int priority = 0;
422  this->message_queue_->receive(
423  buf.data(), buf.size(), recvd_size, priority);
424  if (!recvd_size)
425  continue;
426  assert(recvd_size == 1);
427  MessageType mt{*reinterpret_cast<MessageType*>(buf.data())};
428 
429  this->message_queue_->receive(
430  buf.data(), buf.size(), recvd_size, priority);
431  if (recvd_size)
432  {
433  std::string s{buf.data(), recvd_size};
434  switch (mt)
435  {
436  case MessageType::log:
437  this->os_ << s;
438  this->os_.flush();
439  break;
440  case MessageType::test_start:
441  running_suites_.insert(std::move(s));
442  break;
443  case MessageType::test_end:
445  break;
446  default:
447  assert(0); // unknown message type
448  }
449  }
450  }
451  catch (std::exception const& e)
452  {
453  std::cerr << "Error: " << e.what()
454  << " reading unit test message queue.\n";
455  return;
456  }
457  catch (...)
458  {
459  std::cerr << "Unknown error reading unit test message queue.\n";
460  return;
461  }
462  }
463  });
464 }
465 
467 {
468  using namespace beast::unit_test;
469 
470  continue_message_queue_ = false;
472 
474 
475  for (auto const& s : running_suites_)
476  {
477  os_ << "\nSuite: " << s
478  << " failed to complete. The child process may have crashed.\n";
479  }
480 }
481 
482 bool
484 {
486 }
487 
490 {
492 }
493 
496 {
498 }
499 
500 void
502 {
504 }
505 
506 //------------------------------------------------------------------------------
507 
509  std::size_t num_jobs,
510  bool quiet,
511  bool print_log)
512  : job_index_{checkout_job_index()}
513  , num_jobs_{num_jobs}
514  , quiet_{quiet}
515  , print_log_{!quiet || print_log}
516 {
517  // incPort twice (2*jobIndex_) because some tests need two envs
518  test::incPorts(2 * job_index_);
519 
520  if (num_jobs_ > 1)
521  {
522  keep_alive_thread_ = std::thread([this] {
523  std::size_t last_count = get_keep_alive_count();
524  while (this->continue_keep_alive_)
525  {
526  // Use a small sleep time so in the normal case the child
527  // process may shutdown quickly. However, to protect against
528  // false alarms, use a longer sleep time later on.
530  auto cur_count = this->get_keep_alive_count();
531  if (cur_count == last_count)
532  {
533  // longer sleep time to protect against false alarms
535  cur_count = this->get_keep_alive_count();
536  if (cur_count == last_count)
537  {
538  // assume parent process is no longer alive
539  std::cerr << "multi_runner_child " << job_index_
540  << ": Assuming parent died, exiting.\n";
541  std::exit(EXIT_FAILURE);
542  }
543  }
544  last_count = cur_count;
545  }
546  });
547  }
548 }
549 
551 {
552  if (num_jobs_ > 1)
553  {
554  continue_keep_alive_ = false;
556  }
557 
558  add(results_);
559 }
560 
563 {
564  return results_.total;
565 }
566 
569 {
570  return results_.suites;
571 }
572 
573 void
575 {
576  results_.failed += failures;
577  any_failed(failures != 0);
578 }
579 
580 void
581 multi_runner_child::on_suite_begin(beast::unit_test::suite_info const& info)
582 {
583  suite_results_ = detail::suite_results{info.full_name()};
584  message_queue_send(MessageType::test_start, suite_results_.name);
585 }
586 
587 void
589 {
591  message_queue_send(MessageType::test_end, suite_results_.name);
592 }
593 
594 void
596 {
598 
599  if (quiet_)
600  return;
601 
603  if (num_jobs_ > 1)
604  s << job_index_ << "> ";
605  s << suite_results_.name
606  << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << '\n';
607  message_queue_send(MessageType::log, s.str());
608 }
609 
610 void
612 {
614 }
615 
616 void
618 {
620 }
621 
622 void
624 {
628  if (num_jobs_ > 1)
629  s << job_index_ << "> ";
630  s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ")
631  << reason << '\n';
632  message_queue_send(MessageType::log, s.str());
633 }
634 
635 void
637 {
638  if (!print_log_)
639  return;
640 
642  if (num_jobs_ > 1)
643  s << job_index_ << "> ";
644  s << msg;
645  message_queue_send(MessageType::log, s.str());
646 }
647 
648 namespace detail {
649 template class multi_runner_base<true>;
650 template class multi_runner_base<false>;
651 } // namespace detail
652 
653 } // namespace test
654 } // namespace ripple
ripple::test::detail::results::suites
std::size_t suites
Definition: multi_runner.h:84
ripple::test::detail::multi_runner_base::suites
std::size_t suites() const
Definition: multi_runner.cpp:376
ripple::test::detail::multi_runner_base::checkout_test_index
std::size_t checkout_test_index()
Definition: multi_runner.cpp:300
sstream
ripple::test::detail::multi_runner_base::inner::print_results
void print_results(S &s)
Definition: multi_runner.cpp:226
std::setprecision
T setprecision(T... args)
std::this_thread::sleep_for
T sleep_for(T... args)
ripple::test::detail::suite_results::failed
std::size_t failed
Definition: multi_runner.h:64
std::string
STL class.
ripple::test::detail::suite_results::total
std::size_t total
Definition: multi_runner.h:63
ripple::test::multi_runner_parent::add_failures
void add_failures(std::size_t failures)
Definition: multi_runner.cpp:501
ripple::test::detail::case_results
Definition: multi_runner.h:48
ripple::test::multi_runner_child::quiet_
bool quiet_
Definition: multi_runner.h:262
std::exception
STL class.
ripple::test::detail::multi_runner_base::inner::inc_keep_alive_count
void inc_keep_alive_count()
Definition: multi_runner.cpp:203
ripple::test::detail::suite_results::name
std::string name
Definition: multi_runner.h:61
ripple::test::detail::multi_runner_base::print_results
void print_results(S &s)
Definition: multi_runner.cpp:350
ripple::test::detail::multi_runner_base::get_keep_alive_count
std::size_t get_keep_alive_count()
Definition: multi_runner.cpp:342
ripple::test::detail::multi_runner_base::add
void add(results const &r)
Definition: multi_runner.cpp:328
ripple::test::detail::multi_runner_base::inner::checkout_test_index
std::size_t checkout_test_index()
Definition: multi_runner.cpp:166
std::pair
ripple::test::detail::case_results::total
std::size_t total
Definition: multi_runner.h:51
ripple::test::multi_runner_child::print_log_
bool print_log_
Definition: multi_runner.h:263
ripple::test::detail::case_results::name
std::string name
Definition: multi_runner.h:50
ripple::test::detail::suite_results::start
clock_type::time_point start
Definition: multi_runner.h:65
std::vector< char >
beast::unit_test
Definition: define_print.cpp:16
std::string::size
T size(T... args)
ripple::test::multi_runner_parent::running_suites_
std::set< std::string > running_suites_
Definition: multi_runner.h:226
std::chrono::seconds
ripple::test::multi_runner_child::continue_keep_alive_
std::atomic< bool > continue_keep_alive_
Definition: multi_runner.h:265
std::stringstream
STL class.
ripple::test::detail::multi_runner_base::message_queue_send
void message_queue_send(MessageType mt, std::string const &s)
Definition: multi_runner.cpp:357
std::lock_guard
STL class.
std::cerr
ripple::test::multi_runner_child::on_suite_end
virtual void on_suite_end() override
Definition: multi_runner.cpp:588
ripple::test::multi_runner_child::tests
std::size_t tests() const
Definition: multi_runner.cpp:562
iostream
ripple::test::detail::suite_results
Definition: multi_runner.h:59
ripple::test::detail::results::add
void add(suite_results const &r)
Definition: multi_runner.cpp:64
algorithm
ripple::test::detail::results
Definition: multi_runner.h:75
ripple::test::multi_runner_parent::suites
std::size_t suites() const
Definition: multi_runner.cpp:495
ripple::test::incPorts
void incPorts(int times)
Definition: envconfig.cpp:29
ripple::test::multi_runner_child::~multi_runner_child
~multi_runner_child()
Definition: multi_runner.cpp:550
ripple::test::detail::fmtdur
std::string fmtdur(typename clock_type::duration const &d)
Definition: multi_runner.cpp:40
ripple::test::multi_runner_child::on_suite_begin
virtual void on_suite_begin(beast::unit_test::suite_info const &info) override
Definition: multi_runner.cpp:581
ripple::test::detail::multi_runner_base::add_failures
void add_failures(std::size_t failures)
Definition: multi_runner.cpp:383
ripple::test::detail::results::cases
std::size_t cases
Definition: multi_runner.h:85
ripple::test::multi_runner_child::suites
std::size_t suites() const
Definition: multi_runner.cpp:568
std::thread
STL class.
ripple::test::multi_runner_parent::continue_message_queue_
std::atomic< bool > continue_message_queue_
Definition: multi_runner.h:223
ripple::test::detail::multi_runner_base::checkout_job_index
std::size_t checkout_job_index()
Definition: multi_runner.cpp:307
ripple::test::multi_runner_child::suite_results_
detail::suite_results suite_results_
Definition: multi_runner.h:259
ripple::test::detail::multi_runner_base::any_failed
bool any_failed() const
Definition: multi_runner.cpp:314
ripple::test::detail::multi_runner_base::inner::checkout_job_index
std::size_t checkout_job_index()
Definition: multi_runner.cpp:159
ripple::test::detail::multi_runner_base::inner::add
void add(results const &r)
Definition: multi_runner.cpp:217
ripple::test::multi_runner_parent::~multi_runner_parent
~multi_runner_parent()
Definition: multi_runner.cpp:466
std::string::c_str
T c_str(T... args)
ripple::test::multi_runner_child::results_
detail::results results_
Definition: multi_runner.h:258
ripple::test::detail::multi_runner_base< true >::MessageType
MessageType
Definition: multi_runner.h:169
ripple::test::detail::results::total
std::size_t total
Definition: multi_runner.h:86
std::ostream::flush
T flush(T... args)
ripple::test::multi_runner_child::on_log
virtual void on_log(std::string const &s) override
Definition: multi_runner.cpp:636
std::set::erase
T erase(T... args)
ripple::test::detail::suite_results::cases
std::size_t cases
Definition: multi_runner.h:62
ripple::test::multi_runner_child::add_failures
void add_failures(std::size_t failures)
Definition: multi_runner.cpp:574
ripple::test::multi_runner_child::num_jobs_
std::size_t num_jobs_
Definition: multi_runner.h:261
ripple::test::multi_runner_child::case_results_
detail::case_results case_results_
Definition: multi_runner.h:260
std::merge
T merge(T... args)
ripple::test::multi_runner_parent::any_failed
bool any_failed() const
Definition: multi_runner.cpp:483
ripple::test::multi_runner_parent::multi_runner_parent
multi_runner_parent()
Definition: multi_runner.cpp:400
ripple::test::detail::suite_results::add
void add(case_results const &r)
Definition: multi_runner.cpp:54
ripple::test::detail::multi_runner_base::inner::suites
std::size_t suites() const
Definition: multi_runner.cpp:195
ripple::test::detail::results::max_top
@ max_top
Definition: multi_runner.h:82
ripple::test::detail::results::start
clock_type::time_point start
Definition: multi_runner.h:89
ripple::test::detail::results::print
void print(S &s)
Definition: multi_runner.cpp:138
ripple::test::multi_runner_child::on_case_end
virtual void on_case_end() override
Definition: multi_runner.cpp:611
ripple::test::multi_runner_parent::message_queue_thread_
std::thread message_queue_thread_
Definition: multi_runner.h:224
ripple::test::detail::multi_runner_base
Definition: multi_runner.h:103
ripple::test::detail::results::failed
std::size_t failed
Definition: multi_runner.h:87
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::test::multi_runner_child::on_pass
virtual void on_pass() override
Definition: multi_runner.cpp:617
std::lower_bound
T lower_bound(T... args)
std::endl
T endl(T... args)
ripple::test::multi_runner_child::on_fail
virtual void on_fail(std::string const &reason) override
Definition: multi_runner.cpp:623
iomanip
ripple::test::detail::results::merge
void merge(results const &r)
Definition: multi_runner.cpp:110
ripple::test::detail::results::top
boost::container::static_vector< run_time, max_top > top
Definition: multi_runner.h:88
std
STL namespace.
std::set::insert
T insert(T... args)
ripple::test::multi_runner_parent::tests
std::size_t tests() const
Definition: multi_runner.cpp:489
ripple::test::detail::multi_runner_base::tests
std::size_t tests() const
Definition: multi_runner.cpp:369
std::fixed
T fixed(T... args)
ripple::test::detail::multi_runner_base::inner::tests
std::size_t tests() const
Definition: multi_runner.cpp:187
std::string::empty
T empty(T... args)
ripple::test::detail::multi_runner_base::~multi_runner_base
~multi_runner_base()
Definition: multi_runner.cpp:288
std::stringstream::str
T str(T... args)
std::size_t
ripple::test::multi_runner_child::multi_runner_child
multi_runner_child(multi_runner_child const &)=delete
ripple::test::detail::multi_runner_base::inc_keep_alive_count
void inc_keep_alive_count()
Definition: multi_runner.cpp:335
ripple::test::detail::results::static_string
boost::beast::static_string< 256 > static_string
Definition: multi_runner.h:77
std::setw
T setw(T... args)
std::conditional_t
ripple::test::detail::multi_runner_base::inner::any_failed
bool any_failed() const
Definition: multi_runner.cpp:173
ripple::test::multi_runner_child::on_case_begin
virtual void on_case_begin(std::string const &name) override
Definition: multi_runner.cpp:595
ripple::test::multi_runner_child::job_index_
std::size_t job_index_
Definition: multi_runner.h:257
ripple::test::detail::multi_runner_base< true >::message_queue_
std::unique_ptr< boost::interprocess::message_queue > message_queue_
Definition: multi_runner.h:167
ripple::test::multi_runner_parent::os_
std::ostream & os_
Definition: multi_runner.h:222
std::vector::data
T data(T... args)
ripple::test::detail::multi_runner_base::multi_runner_base
multi_runner_base()
Definition: multi_runner.cpp:233
std::exit
T exit(T... args)
ripple::test::detail::multi_runner_base::inner::get_keep_alive_count
std::size_t get_keep_alive_count()
Definition: multi_runner.cpp:210
std::thread::join
T join(T... args)
std::exception::what
T what(T... args)
ripple::test::multi_runner_child::keep_alive_thread_
std::thread keep_alive_thread_
Definition: multi_runner.h:266
ripple::test::detail::case_results::failed
std::size_t failed
Definition: multi_runner.h:52
std::chrono
std::chrono::steady_clock::now
T now(T... args)