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();
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>
186 void
188 {
189  ++keep_alive_;
190 }
191 
192 template <bool IsParent>
195 {
196  return keep_alive_;
197 }
198 
199 template <bool IsParent>
200 void
202 {
203  std::lock_guard l{m_};
204  results_.merge(r);
205 }
206 
207 template <bool IsParent>
208 template <class S>
209 void
211 {
212  std::lock_guard l{m_};
213  results_.print(s);
214 }
215 
216 template <bool IsParent>
218 {
219  try
220  {
221  if (IsParent)
222  {
223  // cleanup any leftover state for any previous failed runs
224  boost::interprocess::shared_memory_object::remove(shared_mem_name_);
225  boost::interprocess::message_queue::remove(message_queue_name_);
226  }
227 
228  shared_mem_ = boost::interprocess::shared_memory_object{
230  IsParent,
231  boost::interprocess::create_only_t,
232  boost::interprocess::open_only_t>{},
233  shared_mem_name_,
234  boost::interprocess::read_write};
235 
236  if (IsParent)
237  {
238  shared_mem_.truncate(sizeof(inner));
239  message_queue_ =
240  std::make_unique<boost::interprocess::message_queue>(
241  boost::interprocess::create_only,
242  message_queue_name_,
243  /*max messages*/ 16,
244  /*max message size*/ 1 << 20);
245  }
246  else
247  {
248  message_queue_ =
249  std::make_unique<boost::interprocess::message_queue>(
250  boost::interprocess::open_only, message_queue_name_);
251  }
252 
253  region_ = boost::interprocess::mapped_region{
254  shared_mem_, boost::interprocess::read_write};
255  if (IsParent)
256  inner_ = new (region_.get_address()) inner{};
257  else
258  inner_ = reinterpret_cast<inner*>(region_.get_address());
259  }
260  catch (...)
261  {
262  if (IsParent)
263  {
264  boost::interprocess::shared_memory_object::remove(shared_mem_name_);
265  boost::interprocess::message_queue::remove(message_queue_name_);
266  }
267  throw;
268  }
269 }
270 
271 template <bool IsParent>
273 {
274  if (IsParent)
275  {
276  inner_->~inner();
277  boost::interprocess::shared_memory_object::remove(shared_mem_name_);
278  boost::interprocess::message_queue::remove(message_queue_name_);
279  }
280 }
281 
282 template <bool IsParent>
285 {
286  return inner_->checkout_test_index();
287 }
288 
289 template <bool IsParent>
292 {
293  return inner_->checkout_job_index();
294 }
295 
296 template <bool IsParent>
297 bool
299 {
300  return inner_->any_failed();
301 }
302 
303 template <bool IsParent>
304 void
306 {
307  return inner_->any_failed(v);
308 }
309 
310 template <bool IsParent>
311 void
313 {
314  inner_->add(r);
315 }
316 
317 template <bool IsParent>
318 void
320 {
321  inner_->inc_keep_alive_count();
322 }
323 
324 template <bool IsParent>
327 {
328  return inner_->get_keep_alive_count();
329 }
330 
331 template <bool IsParent>
332 template <class S>
333 void
335 {
336  inner_->print_results(s);
337 }
338 
339 template <bool IsParent>
340 void
342  MessageType mt,
343  std::string const& s)
344 {
345  // must use a mutex since the two "sends" must happen in order
346  std::lock_guard l{inner_->m_};
347  message_queue_->send(&mt, sizeof(mt), /*priority*/ 0);
348  message_queue_->send(s.c_str(), s.size(), /*priority*/ 0);
349 }
350 
351 template <bool IsParent>
353 template <bool IsParent>
355 
356 } // namespace detail
357 
358 //------------------------------------------------------------------------------
359 
361 {
363  std::vector<char> buf(1 << 20);
364  while (this->continue_message_queue_ ||
365  this->message_queue_->get_num_msg())
366  {
367  // let children know the parent is still alive
368  this->inc_keep_alive_count();
369  if (!this->message_queue_->get_num_msg())
370  {
371  // If a child does not see the keep alive count incremented,
372  // it will assume the parent has died. This sleep time needs
373  // to be small enough so the child will see increments from
374  // a live parent.
376  continue;
377  }
378  try
379  {
380  std::size_t recvd_size = 0;
381  unsigned int priority = 0;
382  this->message_queue_->receive(
383  buf.data(), buf.size(), recvd_size, priority);
384  if (!recvd_size)
385  continue;
386  assert(recvd_size == 1);
387  MessageType mt{*reinterpret_cast<MessageType*>(buf.data())};
388 
389  this->message_queue_->receive(
390  buf.data(), buf.size(), recvd_size, priority);
391  if (recvd_size)
392  {
393  std::string s{buf.data(), recvd_size};
394  switch (mt)
395  {
396  case MessageType::log:
397  this->os_ << s;
398  this->os_.flush();
399  break;
400  case MessageType::test_start:
401  running_suites_.insert(std::move(s));
402  break;
403  case MessageType::test_end:
405  break;
406  default:
407  assert(0); // unknown message type
408  }
409  }
410  }
411  catch (std::exception const& e)
412  {
413  std::cerr << "Error: " << e.what()
414  << " reading unit test message queue.\n";
415  return;
416  }
417  catch (...)
418  {
419  std::cerr << "Unknown error reading unit test message queue.\n";
420  return;
421  }
422  }
423  });
424 }
425 
427 {
428  using namespace beast::unit_test;
429 
430  continue_message_queue_ = false;
432 
434 
435  for (auto const& s : running_suites_)
436  {
437  os_ << "\nSuite: " << s
438  << " failed to complete. The child process may have crashed.\n";
439  }
440 }
441 
442 bool
444 {
446 }
447 
448 //------------------------------------------------------------------------------
449 
451  std::size_t num_jobs,
452  bool quiet,
453  bool print_log)
454  : job_index_{checkout_job_index()}
455  , num_jobs_{num_jobs}
456  , quiet_{quiet}
457  , print_log_{!quiet || print_log}
458 {
459  // incPort twice (2*jobIndex_) because some tests need two envs
460  for (std::size_t i = 0; i < 2 * job_index_; ++i)
461  test::incPorts();
462 
463  if (num_jobs_ > 1)
464  {
465  keep_alive_thread_ = std::thread([this] {
466  std::size_t last_count = get_keep_alive_count();
467  while (this->continue_keep_alive_)
468  {
469  // Use a small sleep time so in the normal case the child
470  // process may shutdown quickly. However, to protect against
471  // false alarms, use a longer sleep time later on.
473  auto cur_count = this->get_keep_alive_count();
474  if (cur_count == last_count)
475  {
476  // longer sleep time to protect against false alarms
478  cur_count = this->get_keep_alive_count();
479  if (cur_count == last_count)
480  {
481  // assume parent process is no longer alive
482  std::cerr << "multi_runner_child " << job_index_
483  << ": Assuming parent died, exiting.\n";
484  std::exit(EXIT_FAILURE);
485  }
486  }
487  last_count = cur_count;
488  }
489  });
490  }
491 }
492 
494 {
495  if (num_jobs_ > 1)
496  {
497  continue_keep_alive_ = false;
499  }
500 
501  add(results_);
502 }
503 
504 void
505 multi_runner_child::on_suite_begin(beast::unit_test::suite_info const& info)
506 {
507  suite_results_ = detail::suite_results{info.full_name()};
508  message_queue_send(MessageType::test_start, suite_results_.name);
509 }
510 
511 void
513 {
515  message_queue_send(MessageType::test_end, suite_results_.name);
516 }
517 
518 void
520 {
522 
523  if (quiet_)
524  return;
525 
527  if (num_jobs_ > 1)
528  s << job_index_ << "> ";
529  s << suite_results_.name
530  << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << '\n';
531  message_queue_send(MessageType::log, s.str());
532 }
533 
534 void
536 {
538 }
539 
540 void
542 {
544 }
545 
546 void
548 {
552  if (num_jobs_ > 1)
553  s << job_index_ << "> ";
554  s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ")
555  << reason << '\n';
556  message_queue_send(MessageType::log, s.str());
557 }
558 
559 void
561 {
562  if (!print_log_)
563  return;
564 
566  if (num_jobs_ > 1)
567  s << job_index_ << "> ";
568  s << msg;
569  message_queue_send(MessageType::log, s.str());
570 }
571 
572 namespace detail {
573 template class multi_runner_base<true>;
574 template class multi_runner_base<false>;
575 } // namespace detail
576 
577 } // namespace test
578 } // namespace ripple
ripple::test::detail::results::suites
std::size_t suites
Definition: multi_runner.h:84
ripple::test::detail::multi_runner_base::checkout_test_index
std::size_t checkout_test_index()
Definition: multi_runner.cpp:284
sstream
ripple::test::detail::multi_runner_base::inner::print_results
void print_results(S &s)
Definition: multi_runner.cpp:210
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::detail::case_results
Definition: multi_runner.h:48
ripple::test::multi_runner_child::quiet_
bool quiet_
Definition: multi_runner.h:238
std::exception
STL class.
ripple::test::detail::multi_runner_base::inner::inc_keep_alive_count
void inc_keep_alive_count()
Definition: multi_runner.cpp:187
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:334
ripple::test::detail::multi_runner_base::get_keep_alive_count
std::size_t get_keep_alive_count()
Definition: multi_runner.cpp:326
ripple::test::detail::multi_runner_base::add
void add(results const &r)
Definition: multi_runner.cpp:312
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:239
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:211
std::chrono::seconds
ripple::test::multi_runner_child::continue_keep_alive_
std::atomic< bool > continue_keep_alive_
Definition: multi_runner.h:241
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:341
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:512
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_child::~multi_runner_child
~multi_runner_child()
Definition: multi_runner.cpp:493
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:505
ripple::test::detail::results::cases
std::size_t cases
Definition: multi_runner.h:85
std::thread
STL class.
ripple::test::multi_runner_parent::continue_message_queue_
std::atomic< bool > continue_message_queue_
Definition: multi_runner.h:208
ripple::test::detail::multi_runner_base::checkout_job_index
std::size_t checkout_job_index()
Definition: multi_runner.cpp:291
ripple::test::multi_runner_child::suite_results_
detail::suite_results suite_results_
Definition: multi_runner.h:235
ripple::test::detail::multi_runner_base::any_failed
bool any_failed() const
Definition: multi_runner.cpp:298
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:201
ripple::test::multi_runner_parent::~multi_runner_parent
~multi_runner_parent()
Definition: multi_runner.cpp:426
std::string::c_str
T c_str(T... args)
ripple::test::multi_runner_child::results_
detail::results results_
Definition: multi_runner.h:234
ripple::test::detail::multi_runner_base< true >::MessageType
MessageType
Definition: multi_runner.h:163
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:560
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::num_jobs_
std::size_t num_jobs_
Definition: multi_runner.h:237
ripple::test::multi_runner_child::case_results_
detail::case_results case_results_
Definition: multi_runner.h:236
std::merge
T merge(T... args)
ripple::test::multi_runner_parent::any_failed
bool any_failed() const
Definition: multi_runner.cpp:443
ripple::test::multi_runner_parent::multi_runner_parent
multi_runner_parent()
Definition: multi_runner.cpp:360
ripple::test::detail::suite_results::add
void add(case_results const &r)
Definition: multi_runner.cpp:54
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:535
ripple::test::multi_runner_parent::message_queue_thread_
std::thread message_queue_thread_
Definition: multi_runner.h:209
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:541
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:547
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::incPorts
void incPorts()
Definition: envconfig.cpp:29
std::fixed
T fixed(T... args)
std::string::empty
T empty(T... args)
ripple::test::detail::multi_runner_base::~multi_runner_base
~multi_runner_base()
Definition: multi_runner.cpp:272
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:319
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:519
ripple::test::multi_runner_child::job_index_
std::size_t job_index_
Definition: multi_runner.h:233
ripple::test::detail::multi_runner_base< true >::message_queue_
std::unique_ptr< boost::interprocess::message_queue > message_queue_
Definition: multi_runner.h:161
ripple::test::multi_runner_parent::os_
std::ostream & os_
Definition: multi_runner.h:207
std::vector::data
T data(T... args)
ripple::test::detail::multi_runner_base::multi_runner_base
multi_runner_base()
Definition: multi_runner.cpp:217
ripple::test::detail::results::max_top
@ max_top
Definition: multi_runner.h:82
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:194
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:242
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)