libcopp  2.2.0
test_manager.cpp
Go to the documentation of this file.
1 /*
2  * test_manager.cpp
3  *
4  * Created on: 2014-03-11
5  * Author: owent
6  *
7  * Released under the MIT license
8  */
9 
10 #include <cstring>
11 #include <iostream>
12 #include <list>
13 #include <sstream>
14 #include <string>
15 #include <vector>
16 
18 #include <libcopp/utils/features.h>
19 
20 #include "cli/cmd_option.h"
21 #include "cli/cmd_option_phoenix.h"
22 #include "cli/shell_font.h"
23 
24 #include "test_manager.h"
25 
26 namespace detail {
27 #if !(defined(THREAD_TLS_USE_PTHREAD) && THREAD_TLS_USE_PTHREAD)
28 
32 };
33 
34 # if (defined(__cplusplus) && __cplusplus >= 201402L) || ((defined(_MSVC_LANG) && _MSVC_LANG >= 201402L))
35 static_assert(std::is_trivially_copyable<test_manager_tls_block_t>::value, "test_manager_tls_block_t must be trially");
36 # elif (defined(__cplusplus) && __cplusplus >= 201103L) || ((defined(_MSVC_LANG) && _MSVC_LANG >= 201103L))
37 static_assert(std::is_trivial<test_manager_tls_block_t>::value, "test_manager_tls_block_t must be trially");
38 # else
39 static_assert(std::is_pod<test_manager_tls_block_t>::value, "test_manager_tls_block_t must be POD");
40 # endif
41 
44  static thread_local test_manager_tls_block_t ret = g_global_counter_cache;
45  return &ret;
46 }
47 #else
48 
49 # include <pthread.h>
50 struct test_manager_tls_block_t {
52  int *failed_counter_ptr;
53 
54  test_manager_tls_block_t() : success_counter_ptr(nullptr), failed_counter_ptr(nullptr) {}
55 };
56 static pthread_once_t gt_test_manager_tls_block_once = PTHREAD_ONCE_INIT;
57 static pthread_key_t gt_test_manager_tls_block_key;
58 
59 static void dtor_pthread_test_manager_tls_block(void *p) {
60  test_manager_tls_block_t *block = reinterpret_cast<test_manager_tls_block_t *>(p);
61  if (nullptr != block) {
62  delete block;
63  }
64 }
65 
66 static void init_pthread_test_manager_tls_block() {
67  (void)pthread_key_create(&gt_test_manager_tls_block_key, dtor_pthread_test_manager_tls_block);
68 }
69 
70 static test_manager_tls_block_t g_global_counter_cache;
71 test_manager_tls_block_t *get_test_manager_tls_block() {
72  (void)pthread_once(&gt_test_manager_tls_block_once, init_pthread_test_manager_tls_block);
73  test_manager_tls_block_t *block =
74  reinterpret_cast<test_manager_tls_block_t *>(pthread_getspecific(gt_test_manager_tls_block_key));
75  if (nullptr == block) {
76  block = new test_manager_tls_block_t(g_global_counter_cache);
77  pthread_setspecific(gt_test_manager_tls_block_key, block);
78  }
79  return block;
80 }
81 
82 struct gt_test_manager_tls_block_main_thread_dtor_t {
83  test_manager_tls_block_t *block_ptr;
84  gt_test_manager_tls_block_main_thread_dtor_t() {
85  block_ptr = get_test_manager_tls_block();
86  if (nullptr != block_ptr) {
87  block_ptr->success_counter_ptr = nullptr;
88  block_ptr->failed_counter_ptr = nullptr;
89  }
90  }
91 
92  ~gt_test_manager_tls_block_main_thread_dtor_t() {
93  pthread_setspecific(gt_test_manager_tls_block_key, nullptr);
94  dtor_pthread_test_manager_tls_block(reinterpret_cast<void *>(block_ptr));
95  }
96 };
97 static gt_test_manager_tls_block_main_thread_dtor_t gt_test_manager_tls_block_main_thread_dtor;
98 #endif
100  std::string name;
101  void *object;
102 
104  std::list<topological_sort_object_t *> depend_by;
105 };
106 } // namespace detail
107 
109 test_manager::pick_param_str_t::pick_param_str_t(const std::string &in) : str_(in.c_str()) {}
110 
112  return strcmp(str_, other.str_) == 0;
113 }
114 #ifdef __cpp_impl_three_way_comparison
115 std::strong_ordering test_manager::pick_param_str_t::operator<=>(const pick_param_str_t &other) const {
116  int res = strcmp(str_, other.str_);
117  if (res < 0) {
118  return std::strong_ordering::less;
119  } else if (res > 0) {
120  return std::strong_ordering::greater;
121  }
122 
123  return std::strong_ordering::equal;
124 }
125 #else
127  return strcmp(str_, other.str_) != 0;
128 }
130  return strcmp(str_, other.str_) >= 0;
131 }
133  return strcmp(str_, other.str_) > 0;
134 }
136  return strcmp(str_, other.str_) <= 0;
137 }
139  return strcmp(str_, other.str_) < 0;
140 }
141 #endif
142 
144  success_ = 0;
145  failed_ = 0;
146 }
147 
149 
150 void test_manager::append_test_case(const std::string &test_name, const std::string &case_name, case_ptr_type ptr) {
151  tests_[test_name].push_back(std::make_pair(case_name, ptr));
152 }
153 
154 void test_manager::append_event_on_start(const std::string &event_name, on_start_ptr_type ptr) {
155  evt_on_starts_.push_back(std::make_pair(event_name, ptr));
156 }
157 
158 void test_manager::append_event_on_exit(const std::string &event_name, on_exit_ptr_type ptr) {
159  evt_on_exits_.push_back(std::make_pair(event_name, ptr));
160 }
161 
162 #ifdef UTILS_TEST_MACRO_TEST_ENABLE_BOOST_TEST
163 
164 boost::unit_test::test_suite *&test_manager::test_suit() {
165  static boost::unit_test::test_suite *ret = nullptr;
166  return ret;
167 }
168 
169 int test_manager::run() {
170  using namespace boost::unit_test;
171 
172  for (test_data_type::iterator iter = tests_.begin(); iter != tests_.end(); ++iter) {
173  test_suit() = BOOST_TEST_SUITE(iter->first.c_str());
174 
175  for (test_type::iterator iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) {
176  test_suit()->add(make_test_case(callback0<>(iter2->second->func_), iter2->first.c_str()));
177  iter2->second->run();
178  }
179 
180  framework::master_test_suite().add(test_suit());
181  }
182 
183  return 0;
184 }
185 
186 #else
187 
189  std::vector<detail::topological_sort_object_t *> &out) {
190  typedef UTIL_UNIT_TEST_MACRO_AUTO_MAP(std::string, detail::topological_sort_object_t) index_by_name_t;
191  out.reserve(in.size());
192 
193  for (index_by_name_t::iterator iter = in.begin(); iter != in.end(); ++iter) {
194  if (0 == iter->second.dependency_count) {
195  out.push_back(&iter->second);
196  }
197  }
198 
199  for (size_t i = 0; i < out.size(); ++i) {
200  for (std::list<detail::topological_sort_object_t *>::iterator iter = out[i]->depend_by.begin();
201  iter != out[i]->depend_by.end(); ++iter) {
202  if ((*iter)->dependency_count > 0) {
203  --(*iter)->dependency_count;
204 
205  if (0 == (*iter)->dependency_count) {
206  out.push_back(*iter);
207  }
208  }
209  }
210  }
211 }
212 
214  // generate topological_sort_object_t
215  typedef UTIL_UNIT_TEST_MACRO_AUTO_MAP(std::string, detail::topological_sort_object_t) index_by_name_t;
216  index_by_name_t index_by_name;
217  for (size_t i = 0; i < evt_on_starts_.size(); ++i) {
218  detail::topological_sort_object_t &obj = index_by_name[evt_on_starts_[i].first];
219  obj.name = evt_on_starts_[i].first;
220  obj.object = reinterpret_cast<void *>(evt_on_starts_[i].second);
221  obj.dependency_count = 0;
222  }
223 
224  for (size_t i = 0; i < evt_on_starts_.size(); ++i) {
225  detail::topological_sort_object_t &obj = index_by_name[evt_on_starts_[i].first];
226 
227  for (test_on_start_base::after_set_t::iterator iter = evt_on_starts_[i].second->after.begin();
228  iter != evt_on_starts_[i].second->after.end(); ++iter) {
229  index_by_name_t::iterator dep_iter = index_by_name.find(*iter);
230  if (dep_iter == index_by_name.end()) {
231  util::cli::shell_stream ss(std::cerr);
233  << "[ WARNING ] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << "On Start Event "
234  << evt_on_starts_[i].first << " is configured run after " << (*iter) << ", but " << (*iter) << "not found."
235  << std::endl;
236  continue;
237  }
238 
239  ++obj.dependency_count;
240  dep_iter->second.depend_by.push_back(&obj);
241  }
242  }
243 
244  std::vector<detail::topological_sort_object_t *> run_order;
245  topological_sort(index_by_name, run_order);
246 
247  for (size_t i = 0; i < run_order.size(); ++i) {
248  util::cli::shell_stream ss(std::cout);
250  << "[ On Start ] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << "Running " << run_order[i]->name
251  << std::endl;
252  reinterpret_cast<on_start_ptr_type>(run_order[i]->object)->run();
253  }
254 
255  return 0;
256 }
257 
259  typedef UTIL_UNIT_TEST_MACRO_AUTO_MAP(std::string, detail::topological_sort_object_t) index_by_name_t;
260  // generate topological_sort_object_t
261  index_by_name_t index_by_name;
262  for (size_t i = 0; i < evt_on_exits_.size(); ++i) {
263  detail::topological_sort_object_t &obj = index_by_name[evt_on_exits_[i].first];
264  obj.name = evt_on_exits_[i].first;
265  obj.object = reinterpret_cast<void *>(evt_on_exits_[i].second);
266  obj.dependency_count = 0;
267  }
268 
269  for (size_t i = 0; i < evt_on_exits_.size(); ++i) {
270  detail::topological_sort_object_t &obj = index_by_name[evt_on_exits_[i].first];
271 
272  for (test_on_exit_base::before_set_t::iterator iter = evt_on_exits_[i].second->before.begin();
273  iter != evt_on_exits_[i].second->before.end(); ++iter) {
274  index_by_name_t::iterator dep_iter = index_by_name.find(*iter);
275  if (dep_iter == index_by_name.end()) {
276  util::cli::shell_stream ss(std::cerr);
278  << "[ WARNING ] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << "On Exit Event "
279  << evt_on_exits_[i].first << " is configured run before " << (*iter) << ", but " << (*iter) << "not found."
280  << std::endl;
281  continue;
282  }
283 
284  ++obj.dependency_count;
285  dep_iter->second.depend_by.push_back(&obj);
286  }
287  }
288 
289  std::vector<detail::topological_sort_object_t *> run_order;
290  topological_sort(index_by_name, run_order);
291 
292  for (size_t i = 0; i < run_order.size(); ++i) {
293  size_t idx = run_order.size() - 1 - i;
294  util::cli::shell_stream ss(std::cout);
296  << "[ On Exit ] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << "Running " << run_order[idx]->name
297  << std::endl;
298  reinterpret_cast<on_exit_ptr_type>(run_order[idx]->object)->run();
299  }
300 
301  return 0;
302 }
303 
305  success_ = 0;
306  failed_ = 0;
307 
308  clock_t all_begin_time = clock();
309  util::cli::shell_stream ss(std::cout);
311  << "[==========] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << "Running " << tests_.size()
312  << " test(s)" << std::endl;
313 
314  for (test_data_type::iterator iter = tests_.begin(); iter != tests_.end(); ++iter) {
315  bool check_test_group_passed = run_cases_.empty();
316  bool check_test_group_has_cases = false;
317 
318  if (!check_test_group_passed) {
319  check_test_group_passed = run_cases_.end() != run_cases_.find(iter->first);
320  }
321 
322  if (!check_test_group_passed) {
323  check_test_group_has_cases = run_groups_.end() != run_groups_.find(iter->first);
324  }
325 
326  // skip unknown groups
327  if (!check_test_group_passed && !check_test_group_has_cases) {
328  continue;
329  }
330 
331  size_t run_group_count = 0;
332 
333  ss() << std::endl
335  << "[----------] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << iter->second.size()
336  << " test case(s) from " << iter->first << std::endl;
337 
338  clock_t test_begin_time = clock();
339  for (test_type::iterator iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) {
340  bool check_test_case_passed = run_cases_.empty() || check_test_group_passed;
341  if (!check_test_case_passed) {
342  check_test_case_passed = run_cases_.end() != run_cases_.find(iter2->first);
343 
344  if (!check_test_case_passed) {
345  std::string full_name;
346  full_name.reserve(iter->first.size() + 1 + iter2->first.size());
347  full_name = iter->first + "." + iter2->first;
348  check_test_case_passed = run_cases_.end() != run_cases_.find(full_name);
349  }
350  }
351 
352  // skip unknown cases
353  if (!check_test_case_passed) {
354  continue;
355  }
356 
358  << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << iter->first << "." << iter2->first << std::endl;
359 
360  clock_t case_begin_time = clock();
361  iter2->second->run();
362  clock_t case_end_time = clock();
363 
364  if (0 == iter2->second->failed_) {
365  ++success_;
367  << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << iter->first << "." << iter2->first << " ("
368  << get_expire_time(case_begin_time, case_end_time) << ")" << std::endl;
369  } else {
370  ++failed_;
372  << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << iter->first << "." << iter2->first << " ("
373  << get_expire_time(case_begin_time, case_end_time) << ")" << std::endl;
374  }
375 
376  ++run_group_count;
377  }
378 
379  clock_t test_end_time = clock();
381  << "[----------] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << run_group_count
382  << " test case(s) from " << iter->first << " (" << get_expire_time(test_begin_time, test_end_time) << " total)"
383  << std::endl;
384  }
385 
386  clock_t all_end_time = clock();
389  << " test(s) ran."
390  << " (" << get_expire_time(all_begin_time, all_end_time) << " total)" << std::endl;
391 
393  << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << success_ << " test case(s)." << std::endl;
394 
395  if (failed_ > 0) {
397  << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << failed_ << " test case(s), listed below:" << std::endl;
398 
399  for (test_data_type::iterator iter = tests_.begin(); iter != tests_.end(); ++iter) {
400  for (test_type::iterator iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) {
401  if (iter2->second->failed_ > 0) {
403  << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << iter->first << "." << iter2->first << std::endl;
404  }
405  }
406  }
407  }
408 
409  return (0 == failed_) ? 0 : -failed_;
410 }
411 
412 #endif
413 
414 void test_manager::set_cases(const std::vector<std::string> &case_names) {
415  run_cases_.clear();
416  run_groups_.clear();
417 
418  for (size_t i = 0; i < case_names.size(); ++i) {
419  run_cases_.insert(case_names[i]);
420  std::string::size_type split_idx = case_names[i].find('.');
421  if (split_idx != std::string::npos) {
422  run_groups_.insert(case_names[i].substr(0, split_idx));
423  }
424  }
425 }
426 
428  static test_manager ret;
429  return ret;
430 }
431 
432 std::string test_manager::get_expire_time(clock_t begin, clock_t end) {
433  std::stringstream ss;
434  double ms = 1000.0 * (end - begin) / CLOCKS_PER_SEC;
435 
436  ss << ms << " ms";
437 
438  return ss.str();
439 }
440 
441 void test_manager::set_counter_ptr(int *success_counter_ptr, int *failed_counter_ptr) {
443  if (nullptr != block) {
444  block->success_counter_ptr = success_counter_ptr;
445  block->failed_counter_ptr = failed_counter_ptr;
446  }
449 }
450 
453  COPP_LIKELY_IF (nullptr != block && nullptr != block->success_counter_ptr) {
454  ++(*block->success_counter_ptr);
455  return;
456  }
457 
458  util::cli::shell_stream ss(std::cerr);
460  << "[==========] "
461  << "Expect expression can not be used when not running test case." << std::endl;
462 }
463 
466  COPP_LIKELY_IF (nullptr != block && nullptr != block->failed_counter_ptr) {
467  ++(*block->failed_counter_ptr);
468  return;
469  }
470 
471  util::cli::shell_stream ss(std::cerr);
473  << "[==========] "
474  << "Expect expression can not be used when not running test case." << std::endl;
475 }
476 
478 
480 
481 int run_tests(int argc, char *argv[]) {
482  std::vector<std::string> run_cases;
483  const char *version = "1.0.0";
484  bool is_help = false;
485  bool is_show_version = false;
486 
488  cmd_opts->bind_cmd("-h, --help, help", util::cli::phoenix::set_const(is_help, true))
489  ->set_help_msg(" show help message and exit.");
490  cmd_opts->bind_cmd("-v, --version, version", util::cli::phoenix::set_const(is_show_version, true))
491  ->set_help_msg(" show version and exit.");
492  cmd_opts->bind_cmd("-r, --run, run", util::cli::phoenix::push_back(run_cases))
493  ->set_help_msg("[case names...] only run specify cases.");
494 
495  cmd_opts->start(argc, argv);
496  if (is_help) {
497  std::cout << *cmd_opts << std::endl;
498  return 0;
499  }
500 
501  if (is_show_version) {
502  std::cout << version << std::endl;
503  return 0;
504  }
505 
506  test_manager::me().set_cases(run_cases);
508  int ret = test_manager::me().run();
510  return ret;
511 }
static std::string get_expire_time(clock_t begin, clock_t end)
void set_cases(const std::vector< std::string > &case_names)
static test_manager & me()
void append_event_on_start(const std::string &event_name, on_start_ptr_type)
event_on_exit_type evt_on_exits_
Definition: test_manager.h:303
static void set_counter_ptr(int *success_counter_ptr, int *failed_counter_ptr)
event_on_start_type evt_on_starts_
Definition: test_manager.h:302
test_data_type tests_
Definition: test_manager.h:301
void append_event_on_exit(const std::string &event_name, on_exit_ptr_type)
virtual ~test_manager()
int run_event_on_start()
static void inc_failed_counter()
static void inc_success_counter()
int run_event_on_exit()
void append_test_case(const std::string &test_name, const std::string &case_name, case_ptr_type)
std::shared_ptr< cmd_option_bind > ptr_type
Definition: cmd_option.h:325
static ptr_type create()
Definition: cmd_option.h:326
#define COPP_LIKELY_IF(...)
Definition: features.h:102
test_manager_tls_block_t * get_test_manager_tls_block()
static test_manager_tls_block_t g_global_counter_cache
set_const_t< T > set_const(T &t, const T &v)
push_back_t< T > push_back(T &t)
std::list< topological_sort_object_t * > depend_by
bool operator>=(const pick_param_str_t &other) const
bool operator!=(const pick_param_str_t &other) const
bool operator==(const pick_param_str_t &other) const
bool operator>(const pick_param_str_t &other) const
bool operator<=(const pick_param_str_t &other) const
bool operator<(const pick_param_str_t &other) const
#define UTIL_UNIT_TEST_MACRO_AUTO_MAP(...)
static void topological_sort(UTIL_UNIT_TEST_MACRO_AUTO_MAP(std::string, detail::topological_sort_object_t) &in, std::vector< detail::topological_sort_object_t * > &out)
int run_tests(int argc, char *argv[])