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 {
343  // must use a mutex since the two "sends" must happen in order
344  std::lock_guard l{inner_->m_};
345  message_queue_->send(&mt, sizeof(mt), /*priority*/ 0);
346  message_queue_->send(s.c_str(), s.size(), /*priority*/ 0);
347 }
348 
349 template <bool IsParent>
351 template <bool IsParent>
353 
354 } // detail
355 
356 //------------------------------------------------------------------------------
357 
359  : os_(std::cout)
360 {
362  std::vector<char> buf(1 << 20);
363  while (this->continue_message_queue_ ||
364  this->message_queue_->get_num_msg())
365  {
366  // let children know the parent is still alive
367  this->inc_keep_alive_count();
368  if (!this->message_queue_->get_num_msg())
369  {
370  // If a child does not see the keep alive count incremented,
371  // it will assume the parent has died. This sleep time needs
372  // to be small enough so the child will see increments from
373  // a live parent.
375  continue;
376  }
377  try
378  {
379  std::size_t recvd_size = 0;
380  unsigned int priority = 0;
381  this->message_queue_->receive(
382  buf.data(), buf.size(), recvd_size, priority);
383  if (!recvd_size)
384  continue;
385  assert (recvd_size == 1);
386  MessageType mt{*reinterpret_cast<MessageType*>(buf.data())};
387 
388  this->message_queue_->receive(
389  buf.data(), buf.size(), recvd_size, priority);
390  if (recvd_size)
391  {
392  std::string s{buf.data(), recvd_size};
393  switch (mt)
394  {
395  case MessageType::log:
396  this->os_ << s;
397  this->os_.flush();
398  break;
399  case MessageType::test_start:
400  running_suites_.insert(std::move(s));
401  break;
402  case MessageType::test_end:
404  break;
405  default:
406  assert(0); // unknown message type
407  }
408  }
409  }
410  catch (std::exception const& e)
411  {
412  std::cerr << "Error: " << e.what()
413  << " reading unit test message queue.\n";
414  return;
415  }
416  catch (...)
417  {
418  std::cerr << "Unknown error reading unit test message queue.\n";
419  return;
420  }
421  }
422  });
423 }
424 
426 {
427  using namespace beast::unit_test;
428 
429  continue_message_queue_ = false;
431 
433 
434  for (auto const& s : running_suites_)
435  {
436  os_ << "\nSuite: " << s
437  << " failed to complete. The child process may have crashed.\n";
438  }
439 }
440 
441 bool
443 {
445 }
446 
447 //------------------------------------------------------------------------------
448 
450  std::size_t num_jobs,
451  bool quiet,
452  bool print_log)
453  : job_index_{checkout_job_index()}
454  , num_jobs_{num_jobs}
455  , quiet_{quiet}
456  , print_log_{!quiet || print_log}
457 {
458  // incPort twice (2*jobIndex_) because some tests need two envs
459  for (std::size_t i = 0; i < 2 * job_index_; ++i)
460  test::incPorts();
461 
462  if (num_jobs_ > 1)
463  {
464  keep_alive_thread_ = std::thread([this] {
465  std::size_t last_count = get_keep_alive_count();
466  while (this->continue_keep_alive_)
467  {
468  // Use a small sleep time so in the normal case the child
469  // process may shutdown quickly. However, to protect against
470  // false alarms, use a longer sleep time later on.
472  auto cur_count = this->get_keep_alive_count();
473  if (cur_count == last_count)
474  {
475  // longer sleep time to protect against false alarms
477  cur_count = this->get_keep_alive_count();
478  if (cur_count == last_count)
479  {
480  // assume parent process is no longer alive
481  std::cerr << "multi_runner_child " << job_index_
482  << ": Assuming parent died, exiting.\n";
483  std::exit(EXIT_FAILURE);
484  }
485  }
486  last_count = cur_count;
487  }
488  });
489  }
490 }
491 
493 {
494  if (num_jobs_ > 1)
495  {
496  continue_keep_alive_ = false;
498  }
499 
500  add(results_);
501 }
502 
503 void
504 multi_runner_child::on_suite_begin(beast::unit_test::suite_info const& info)
505 {
506  suite_results_ = detail::suite_results{info.full_name()};
507  message_queue_send(MessageType::test_start, suite_results_.name);
508 }
509 
510 void
512 {
514  message_queue_send(MessageType::test_end, suite_results_.name);
515 }
516 
517 void
519 {
521 
522  if (quiet_)
523  return;
524 
526  if (num_jobs_ > 1)
527  s << job_index_ << "> ";
528  s << suite_results_.name
529  << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << '\n';
530  message_queue_send(MessageType::log, s.str());
531 }
532 
533 void
535 {
537 }
538 
539 void
541 {
543 }
544 
545 void
547 {
551  if (num_jobs_ > 1)
552  s << job_index_ << "> ";
553  s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ")
554  << reason << '\n';
555  message_queue_send(MessageType::log, s.str());
556 }
557 
558 void
560 {
561  if (!print_log_)
562  return;
563 
565  if (num_jobs_ > 1)
566  s << job_index_ << "> ";
567  s << msg;
568  message_queue_send(MessageType::log, s.str());
569 }
570 
571 namespace detail {
572 template class multi_runner_base<true>;
573 template class multi_runner_base<false>;
574 }
575 
576 } // unit_test
577 } // beast
ripple::test::detail::results::suites
std::size_t suites
Definition: multi_runner.h:88
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:66
std::string
STL class.
ripple::test::detail::suite_results::total
std::size_t total
Definition: multi_runner.h:65
ripple::test::detail::case_results
Definition: multi_runner.h:48
ripple::test::multi_runner_child::quiet_
bool quiet_
Definition: multi_runner.h:239
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:63
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:240
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:67
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:213
std::chrono::seconds
ripple::test::multi_runner_child::continue_keep_alive_
std::atomic< bool > continue_keep_alive_
Definition: multi_runner.h:242
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:511
iostream
ripple::test::detail::suite_results
Definition: multi_runner.h:61
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:79
ripple::test::multi_runner_child::~multi_runner_child
~multi_runner_child()
Definition: multi_runner.cpp:492
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:504
ripple::test::detail::results::cases
std::size_t cases
Definition: multi_runner.h:89
std::thread
STL class.
ripple::test::multi_runner_parent::continue_message_queue_
std::atomic< bool > continue_message_queue_
Definition: multi_runner.h:210
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:236
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:425
std::string::c_str
T c_str(T... args)
ripple::test::multi_runner_child::results_
detail::results results_
Definition: multi_runner.h:235
ripple::test::detail::multi_runner_base< true >::MessageType
MessageType
Definition: multi_runner.h:166
ripple::test::detail::results::total
std::size_t total
Definition: multi_runner.h:90
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:559
std::set::erase
T erase(T... args)
ripple::test::detail::suite_results::cases
std::size_t cases
Definition: multi_runner.h:64
ripple::test::multi_runner_child::num_jobs_
std::size_t num_jobs_
Definition: multi_runner.h:238
ripple::test::multi_runner_child::case_results_
detail::case_results case_results_
Definition: multi_runner.h:237
std::merge
T merge(T... args)
ripple::test::multi_runner_parent::any_failed
bool any_failed() const
Definition: multi_runner.cpp:442
ripple::test::multi_runner_parent::multi_runner_parent
multi_runner_parent()
Definition: multi_runner.cpp:358
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:93
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:534
ripple::test::multi_runner_parent::message_queue_thread_
std::thread message_queue_thread_
Definition: multi_runner.h:211
ripple::test::detail::multi_runner_base
Definition: multi_runner.h:107
ripple::test::detail::results::failed
std::size_t failed
Definition: multi_runner.h:91
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:540
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:546
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:92
std
STL namespace.
std::set::insert
T insert(T... args)
ripple::test::detail::results::max_top
@ max_top
Definition: multi_runner.h:86
ripple::test::incPorts
void incPorts()
Definition: envconfig.cpp:28
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:81
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:518
ripple::test::multi_runner_child::job_index_
std::size_t job_index_
Definition: multi_runner.h:234
ripple::test::detail::multi_runner_base< true >::message_queue_
std::unique_ptr< boost::interprocess::message_queue > message_queue_
Definition: multi_runner.h:164
ripple::test::multi_runner_parent::os_
std::ostream & os_
Definition: multi_runner.h:209
std::vector::data
T data(T... args)
ripple::test::detail::multi_runner_base::multi_runner_base
multi_runner_base()
Definition: multi_runner.cpp:217
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:243
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)