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 #include <vector>
31 
32 namespace ripple {
33 namespace test {
34 
35 extern void
36 incPorts(int times);
37 
38 namespace detail {
39 
41 fmtdur(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 
54 void
56 {
57  ++cases;
58  total += r.total;
59  failed += r.failed;
60 }
61 
62 //------------------------------------------------------------------------------
63 
64 void
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 
110 void
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());
121  std::merge(
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  });
130 
131  if (top_result.size() > max_top)
132  top_result.resize(max_top);
133 
134  top = top_result;
135 }
136 
137 template <class S>
138 void
140 {
141  using namespace beast::unit_test;
142 
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';
148  }
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 
158 template <bool IsParent>
161 {
162  return job_index_++;
163 }
164 
165 template <bool IsParent>
168 {
169  return test_index_++;
170 }
171 
172 template <bool IsParent>
173 bool
175 {
176  return any_failed_;
177 }
178 
179 template <bool IsParent>
180 void
182 {
183  any_failed_ = any_failed_ || v;
184 }
185 
186 template <bool IsParent>
189 {
190  std::lock_guard l{m_};
191  return results_.total;
192 }
193 
194 template <bool IsParent>
197 {
198  std::lock_guard l{m_};
199  return results_.suites;
200 }
201 
202 template <bool IsParent>
203 void
205 {
206  ++keep_alive_;
207 }
208 
209 template <bool IsParent>
212 {
213  return keep_alive_;
214 }
215 
216 template <bool IsParent>
217 void
219 {
220  std::lock_guard l{m_};
221  results_.merge(r);
222 }
223 
224 template <bool IsParent>
225 template <class S>
226 void
228 {
229  std::lock_guard l{m_};
230  results_.print(s);
231 }
232 
233 template <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>{},
250  shared_mem_name_,
251  boost::interprocess::read_write};
252 
253  if (IsParent)
254  {
255  shared_mem_.truncate(sizeof(inner));
256  message_queue_ =
257  std::make_unique<boost::interprocess::message_queue>(
258  boost::interprocess::create_only,
259  message_queue_name_,
260  /*max messages*/ 16,
261  /*max message size*/ 1 << 20);
262  }
263  else
264  {
265  message_queue_ =
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 
288 template <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 
299 template <bool IsParent>
302 {
303  return inner_->checkout_test_index();
304 }
305 
306 template <bool IsParent>
309 {
310  return inner_->checkout_job_index();
311 }
312 
313 template <bool IsParent>
314 bool
316 {
317  return inner_->any_failed();
318 }
319 
320 template <bool IsParent>
321 void
323 {
324  return inner_->any_failed(v);
325 }
326 
327 template <bool IsParent>
328 void
330 {
331  inner_->add(r);
332 }
333 
334 template <bool IsParent>
335 void
337 {
338  inner_->inc_keep_alive_count();
339 }
340 
341 template <bool IsParent>
344 {
345  return inner_->get_keep_alive_count();
346 }
347 
348 template <bool IsParent>
349 template <class S>
350 void
352 {
353  inner_->print_results(s);
354 }
355 
356 template <bool IsParent>
357 void
359  MessageType mt,
360  std::string const& s)
361 {
362  // must use a mutex since the two "sends" must happen in order
363  std::lock_guard l{inner_->m_};
364  message_queue_->send(&mt, sizeof(mt), /*priority*/ 0);
365  message_queue_->send(s.c_str(), s.size(), /*priority*/ 0);
366 }
367 
368 template <bool IsParent>
371 {
372  return inner_->tests();
373 }
374 
375 template <bool IsParent>
378 {
379  return inner_->suites();
380 }
381 
382 template <bool IsParent>
383 void
385 {
387  results.failed += failures;
388  add(results);
389  any_failed(failures != 0);
390 }
391 
392 template <bool IsParent>
394 template <bool IsParent>
396 
397 } // namespace detail
398 
399 //------------------------------------------------------------------------------
400 
402 {
404  std::vector<char> buf(1 << 20);
405  while (this->continue_message_queue_ ||
406  this->message_queue_->get_num_msg())
407  {
408  // let children know the parent is still alive
409  this->inc_keep_alive_count();
410  if (!this->message_queue_->get_num_msg())
411  {
412  // If a child does not see the keep alive count incremented,
413  // it will assume the parent has died. This sleep time needs
414  // to be small enough so the child will see increments from
415  // a live parent.
417  continue;
418  }
419  try
420  {
421  std::size_t recvd_size = 0;
422  unsigned int priority = 0;
423  this->message_queue_->receive(
424  buf.data(), buf.size(), recvd_size, priority);
425  if (!recvd_size)
426  continue;
427  assert(recvd_size == 1);
428  MessageType mt{*reinterpret_cast<MessageType*>(buf.data())};
429 
430  this->message_queue_->receive(
431  buf.data(), buf.size(), recvd_size, priority);
432  if (recvd_size)
433  {
434  std::string s{buf.data(), recvd_size};
435  switch (mt)
436  {
437  case MessageType::log:
438  this->os_ << s;
439  this->os_.flush();
440  break;
441  case MessageType::test_start:
442  running_suites_.insert(std::move(s));
443  break;
444  case MessageType::test_end:
446  break;
447  default:
448  assert(0); // unknown message type
449  }
450  }
451  }
452  catch (std::exception const& e)
453  {
454  std::cerr << "Error: " << e.what()
455  << " reading unit test message queue.\n";
456  return;
457  }
458  catch (...)
459  {
460  std::cerr << "Unknown error reading unit test message queue.\n";
461  return;
462  }
463  }
464  });
465 }
466 
468 {
469  using namespace beast::unit_test;
470 
471  continue_message_queue_ = false;
473 
475 
476  for (auto const& s : running_suites_)
477  {
478  os_ << "\nSuite: " << s
479  << " failed to complete. The child process may have crashed.\n";
480  }
481 }
482 
483 bool
485 {
487 }
488 
491 {
493 }
494 
497 {
499 }
500 
501 void
503 {
505 }
506 
507 //------------------------------------------------------------------------------
508 
510  std::size_t num_jobs,
511  bool quiet,
512  bool print_log)
513  : job_index_{checkout_job_index()}
514  , num_jobs_{num_jobs}
515  , quiet_{quiet}
516  , print_log_{!quiet || print_log}
517 {
518  // incPort twice (2*jobIndex_) because some tests need two envs
519  test::incPorts(2 * job_index_);
520 
521  if (num_jobs_ > 1)
522  {
523  keep_alive_thread_ = std::thread([this] {
524  std::size_t last_count = get_keep_alive_count();
525  while (this->continue_keep_alive_)
526  {
527  // Use a small sleep time so in the normal case the child
528  // process may shutdown quickly. However, to protect against
529  // false alarms, use a longer sleep time later on.
531  auto cur_count = this->get_keep_alive_count();
532  if (cur_count == last_count)
533  {
534  // longer sleep time to protect against false alarms
536  cur_count = this->get_keep_alive_count();
537  if (cur_count == last_count)
538  {
539  // assume parent process is no longer alive
540  std::cerr << "multi_runner_child " << job_index_
541  << ": Assuming parent died, exiting.\n";
542  std::exit(EXIT_FAILURE);
543  }
544  }
545  last_count = cur_count;
546  }
547  });
548  }
549 }
550 
552 {
553  if (num_jobs_ > 1)
554  {
555  continue_keep_alive_ = false;
557  }
558 
559  add(results_);
560 }
561 
564 {
565  return results_.total;
566 }
567 
570 {
571  return results_.suites;
572 }
573 
574 void
576 {
577  results_.failed += failures;
578  any_failed(failures != 0);
579 }
580 
581 void
582 multi_runner_child::on_suite_begin(beast::unit_test::suite_info const& info)
583 {
584  suite_results_ = detail::suite_results{info.full_name()};
585  message_queue_send(MessageType::test_start, suite_results_.name);
586 }
587 
588 void
590 {
592  message_queue_send(MessageType::test_end, suite_results_.name);
593 }
594 
595 void
597 {
599 
600  if (quiet_)
601  return;
602 
604  if (num_jobs_ > 1)
605  s << job_index_ << "> ";
606  s << suite_results_.name
607  << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << '\n';
608  message_queue_send(MessageType::log, s.str());
609 }
610 
611 void
613 {
615 }
616 
617 void
619 {
621 }
622 
623 void
625 {
629  if (num_jobs_ > 1)
630  s << job_index_ << "> ";
631  s << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ")
632  << reason << '\n';
633  message_queue_send(MessageType::log, s.str());
634 }
635 
636 void
638 {
639  if (!print_log_)
640  return;
641 
643  if (num_jobs_ > 1)
644  s << job_index_ << "> ";
645  s << msg;
646  message_queue_send(MessageType::log, s.str());
647 }
648 
649 namespace detail {
650 template class multi_runner_base<true>;
651 template class multi_runner_base<false>;
652 } // namespace detail
653 
654 } // namespace test
655 } // namespace ripple
ripple::test::detail::results::suites
std::size_t suites
Definition: multi_runner.h:85
ripple::test::detail::multi_runner_base::suites
std::size_t suites() const
Definition: multi_runner.cpp:377
ripple::test::detail::multi_runner_base::checkout_test_index
std::size_t checkout_test_index()
Definition: multi_runner.cpp:301
sstream
ripple::test::detail::multi_runner_base::inner::print_results
void print_results(S &s)
Definition: multi_runner.cpp:227
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:65
std::string
STL class.
ripple::test::detail::suite_results::total
std::size_t total
Definition: multi_runner.h:64
ripple::test::multi_runner_parent::add_failures
void add_failures(std::size_t failures)
Definition: multi_runner.cpp:502
ripple::test::detail::case_results
Definition: multi_runner.h:49
ripple::test::multi_runner_child::quiet_
bool quiet_
Definition: multi_runner.h:263
std::exception
STL class.
ripple::test::detail::multi_runner_base::inner::inc_keep_alive_count
void inc_keep_alive_count()
Definition: multi_runner.cpp:204
ripple::test::detail::suite_results::name
std::string name
Definition: multi_runner.h:62
ripple::test::detail::multi_runner_base::print_results
void print_results(S &s)
Definition: multi_runner.cpp:351
ripple::test::detail::multi_runner_base::get_keep_alive_count
std::size_t get_keep_alive_count()
Definition: multi_runner.cpp:343
ripple::test::detail::multi_runner_base::add
void add(results const &r)
Definition: multi_runner.cpp:329
ripple::test::detail::multi_runner_base::inner::checkout_test_index
std::size_t checkout_test_index()
Definition: multi_runner.cpp:167
std::pair
ripple::test::detail::case_results::total
std::size_t total
Definition: multi_runner.h:52
ripple::test::multi_runner_child::print_log_
bool print_log_
Definition: multi_runner.h:264
ripple::test::detail::case_results::name
std::string name
Definition: multi_runner.h:51
ripple::test::detail::suite_results::start
clock_type::time_point start
Definition: multi_runner.h:66
vector
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:227
std::chrono::seconds
ripple::test::multi_runner_child::continue_keep_alive_
std::atomic< bool > continue_keep_alive_
Definition: multi_runner.h:266
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:358
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:589
ripple::test::multi_runner_child::tests
std::size_t tests() const
Definition: multi_runner.cpp:563
iostream
ripple::test::detail::suite_results
Definition: multi_runner.h:60
ripple::test::detail::results::add
void add(suite_results const &r)
Definition: multi_runner.cpp:65
algorithm
ripple::test::detail::results
Definition: multi_runner.h:76
ripple::test::multi_runner_parent::suites
std::size_t suites() const
Definition: multi_runner.cpp:496
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:551
ripple::test::detail::fmtdur
std::string fmtdur(typename clock_type::duration const &d)
Definition: multi_runner.cpp:41
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:582
ripple::test::detail::results::max_top
@ max_top
Definition: multi_runner.h:83
ripple::test::detail::multi_runner_base::add_failures
void add_failures(std::size_t failures)
Definition: multi_runner.cpp:384
ripple::test::detail::results::cases
std::size_t cases
Definition: multi_runner.h:86
ripple::test::multi_runner_child::suites
std::size_t suites() const
Definition: multi_runner.cpp:569
std::thread
STL class.
ripple::test::multi_runner_parent::continue_message_queue_
std::atomic< bool > continue_message_queue_
Definition: multi_runner.h:224
ripple::test::detail::multi_runner_base::checkout_job_index
std::size_t checkout_job_index()
Definition: multi_runner.cpp:308
ripple::test::multi_runner_child::suite_results_
detail::suite_results suite_results_
Definition: multi_runner.h:260
ripple::test::detail::multi_runner_base::any_failed
bool any_failed() const
Definition: multi_runner.cpp:315
ripple::test::detail::multi_runner_base::inner::checkout_job_index
std::size_t checkout_job_index()
Definition: multi_runner.cpp:160
ripple::test::detail::multi_runner_base::inner::add
void add(results const &r)
Definition: multi_runner.cpp:218
ripple::test::multi_runner_parent::~multi_runner_parent
~multi_runner_parent()
Definition: multi_runner.cpp:467
std::string::c_str
T c_str(T... args)
ripple::test::multi_runner_child::results_
detail::results results_
Definition: multi_runner.h:259
ripple::test::detail::multi_runner_base< true >::MessageType
MessageType
Definition: multi_runner.h:170
ripple::test::detail::results::total
std::size_t total
Definition: multi_runner.h:87
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:637
std::set::erase
T erase(T... args)
ripple::test::detail::suite_results::cases
std::size_t cases
Definition: multi_runner.h:63
ripple::test::multi_runner_child::add_failures
void add_failures(std::size_t failures)
Definition: multi_runner.cpp:575
ripple::test::multi_runner_child::num_jobs_
std::size_t num_jobs_
Definition: multi_runner.h:262
ripple::test::multi_runner_child::case_results_
detail::case_results case_results_
Definition: multi_runner.h:261
std::merge
T merge(T... args)
ripple::test::multi_runner_parent::any_failed
bool any_failed() const
Definition: multi_runner.cpp:484
ripple::test::multi_runner_parent::multi_runner_parent
multi_runner_parent()
Definition: multi_runner.cpp:401
ripple::test::detail::suite_results::add
void add(case_results const &r)
Definition: multi_runner.cpp:55
ripple::test::detail::multi_runner_base::inner::suites
std::size_t suites() const
Definition: multi_runner.cpp:196
ripple::test::detail::results::start
clock_type::time_point start
Definition: multi_runner.h:90
ripple::test::detail::results::print
void print(S &s)
Definition: multi_runner.cpp:139
ripple::test::multi_runner_child::on_case_end
virtual void on_case_end() override
Definition: multi_runner.cpp:612
ripple::test::multi_runner_parent::message_queue_thread_
std::thread message_queue_thread_
Definition: multi_runner.h:225
ripple::test::detail::multi_runner_base
Definition: multi_runner.h:104
ripple::test::detail::results::failed
std::size_t failed
Definition: multi_runner.h:88
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:618
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:624
iomanip
ripple::test::detail::results::merge
void merge(results const &r)
Definition: multi_runner.cpp:111
ripple::test::detail::results::top
boost::container::static_vector< run_time, max_top > top
Definition: multi_runner.h:89
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:490
ripple::test::detail::multi_runner_base::tests
std::size_t tests() const
Definition: multi_runner.cpp:370
std::fixed
T fixed(T... args)
ripple::test::detail::multi_runner_base::inner::tests
std::size_t tests() const
Definition: multi_runner.cpp:188
std::string::empty
T empty(T... args)
ripple::test::detail::multi_runner_base::~multi_runner_base
~multi_runner_base()
Definition: multi_runner.cpp:289
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:336
ripple::test::detail::results::static_string
boost::beast::static_string< 256 > static_string
Definition: multi_runner.h:78
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:174
ripple::test::multi_runner_child::on_case_begin
virtual void on_case_begin(std::string const &name) override
Definition: multi_runner.cpp:596
ripple::test::multi_runner_child::job_index_
std::size_t job_index_
Definition: multi_runner.h:258
ripple::test::detail::multi_runner_base< true >::message_queue_
std::unique_ptr< boost::interprocess::message_queue > message_queue_
Definition: multi_runner.h:168
ripple::test::multi_runner_parent::os_
std::ostream & os_
Definition: multi_runner.h:223
std::vector::data
T data(T... args)
ripple::test::detail::multi_runner_base::multi_runner_base
multi_runner_base()
Definition: multi_runner.cpp:234
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:211
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:267
ripple::test::detail::case_results::failed
std::size_t failed
Definition: multi_runner.h:53
std::chrono
std::chrono::steady_clock::now
T now(T... args)