libcopp 2.3.1
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
19
20#include "cli/cmd_option.h"
22#include "cli/shell_font.h"
23
24#include "test_manager.h"
25
26namespace detail {
27#if !(defined(THREAD_TLS_USE_PTHREAD) && THREAD_TLS_USE_PTHREAD)
28
33
34# if (defined(__cplusplus) && __cplusplus >= 201402L) || ((defined(_MSVC_LANG) && _MSVC_LANG >= 201402L))
35static_assert(std::is_trivially_copyable<test_manager_tls_block_t>::value,
36 "test_manager_tls_block_t must be trivially copyable");
37# elif (defined(__cplusplus) && __cplusplus >= 201103L) || ((defined(_MSVC_LANG) && _MSVC_LANG >= 201103L))
38static_assert(std::is_trivial<test_manager_tls_block_t>::value, "test_manager_tls_block_t must be trially");
39# else
40static_assert(std::is_pod<test_manager_tls_block_t>::value, "test_manager_tls_block_t must be POD");
41# endif
42
48#else
49
50# include <pthread.h>
51struct test_manager_tls_block_t {
54
55 test_manager_tls_block_t() : success_counter_ptr(nullptr), failed_counter_ptr(nullptr) {}
56};
57static pthread_once_t gt_test_manager_tls_block_once = PTHREAD_ONCE_INIT;
58static pthread_key_t gt_test_manager_tls_block_key;
59
60static void dtor_pthread_test_manager_tls_block(void *p) {
61 test_manager_tls_block_t *block = reinterpret_cast<test_manager_tls_block_t *>(p);
62 if (nullptr != block) {
63 delete block;
64 }
65}
66
67static void init_pthread_test_manager_tls_block() {
68 (void)pthread_key_create(&gt_test_manager_tls_block_key, dtor_pthread_test_manager_tls_block);
69}
70
71static test_manager_tls_block_t g_global_counter_cache;
72test_manager_tls_block_t *get_test_manager_tls_block() {
73 (void)pthread_once(&gt_test_manager_tls_block_once, init_pthread_test_manager_tls_block);
74 test_manager_tls_block_t *block =
75 reinterpret_cast<test_manager_tls_block_t *>(pthread_getspecific(gt_test_manager_tls_block_key));
76 if (nullptr == block) {
77 block = new test_manager_tls_block_t(g_global_counter_cache);
78 pthread_setspecific(gt_test_manager_tls_block_key, block);
79 }
80 return block;
81}
82
83struct gt_test_manager_tls_block_main_thread_dtor_t {
84 test_manager_tls_block_t *block_ptr;
85 gt_test_manager_tls_block_main_thread_dtor_t() {
86 block_ptr = get_test_manager_tls_block();
87 if (nullptr != block_ptr) {
88 block_ptr->success_counter_ptr = nullptr;
89 block_ptr->failed_counter_ptr = nullptr;
90 }
91 }
92
93 ~gt_test_manager_tls_block_main_thread_dtor_t() {
94 pthread_setspecific(gt_test_manager_tls_block_key, nullptr);
95 dtor_pthread_test_manager_tls_block(reinterpret_cast<void *>(block_ptr));
96 }
97};
98static gt_test_manager_tls_block_main_thread_dtor_t gt_test_manager_tls_block_main_thread_dtor;
99#endif
101 std::string name;
102 void *object;
103
105 std::list<topological_sort_object_t *> depend_by;
106};
107} // namespace detail
108
110 success_ = 0;
111 failed_ = 0;
112}
113
115
116void test_manager::append_test_case(const std::string &test_name, const std::string &case_name, case_ptr_type ptr) {
117 tests_[test_name].push_back(std::make_pair(case_name, ptr));
118}
119
120void test_manager::append_event_on_start(const std::string &event_name, on_start_ptr_type ptr) {
121 evt_on_starts_.push_back(std::make_pair(event_name, ptr));
122}
123
124void test_manager::append_event_on_exit(const std::string &event_name, on_exit_ptr_type ptr) {
125 evt_on_exits_.push_back(std::make_pair(event_name, ptr));
126}
127
128#ifdef UTILS_TEST_MACRO_TEST_ENABLE_BOOST_TEST
129
130boost::unit_test::test_suite *&test_manager::test_suit() {
131 static boost::unit_test::test_suite *ret = nullptr;
132 return ret;
133}
134
135int test_manager::run() {
136 using namespace boost::unit_test;
137
138 for (test_data_type::iterator iter = tests_.begin(); iter != tests_.end(); ++iter) {
139 test_suit() = BOOST_TEST_SUITE(iter->first.c_str());
140
141 for (test_type::iterator iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) {
142 test_suit()->add(make_test_case(callback0<>(iter2->second->func_), iter2->first.c_str()));
143 iter2->second->run();
144 }
145
146 framework::master_test_suite().add(test_suit());
147 }
148
149 return 0;
150}
151
152#else
153
154static void topological_sort(std::unordered_map<std::string, detail::topological_sort_object_t> &in,
155 std::vector<detail::topological_sort_object_t *> &out) {
156 using index_by_name_t = std::unordered_map<std::string, detail::topological_sort_object_t>;
157 out.reserve(in.size());
158
159 for (index_by_name_t::iterator iter = in.begin(); iter != in.end(); ++iter) {
160 if (0 == iter->second.dependency_count) {
161 out.push_back(&iter->second);
162 }
163 }
164
165 for (size_t i = 0; i < out.size(); ++i) {
166 for (std::list<detail::topological_sort_object_t *>::iterator iter = out[i]->depend_by.begin();
167 iter != out[i]->depend_by.end(); ++iter) {
168 if ((*iter)->dependency_count > 0) {
169 --(*iter)->dependency_count;
170
171 if (0 == (*iter)->dependency_count) {
172 out.push_back(*iter);
173 }
174 }
175 }
176 }
177}
178
180 // generate topological_sort_object_t
181 using index_by_name_t = std::unordered_map<std::string, detail::topological_sort_object_t>;
182 index_by_name_t index_by_name;
183 for (size_t i = 0; i < evt_on_starts_.size(); ++i) {
184 detail::topological_sort_object_t &obj = index_by_name[evt_on_starts_[i].first];
185 obj.name = evt_on_starts_[i].first;
186 obj.object = reinterpret_cast<void *>(evt_on_starts_[i].second);
187 obj.dependency_count = 0;
188 }
189
190 for (size_t i = 0; i < evt_on_starts_.size(); ++i) {
191 detail::topological_sort_object_t &obj = index_by_name[evt_on_starts_[i].first];
192
193 for (test_on_start_base::after_set_t::iterator iter = evt_on_starts_[i].second->after.begin();
194 iter != evt_on_starts_[i].second->after.end(); ++iter) {
195 index_by_name_t::iterator dep_iter = index_by_name.find(*iter);
196 if (dep_iter == index_by_name.end()) {
197 util::cli::shell_stream ss(std::cerr);
199 << "[ WARNING ] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << "On Start Event "
200 << evt_on_starts_[i].first << " is configured run after " << (*iter) << ", but " << (*iter) << "not found."
201 << std::endl;
202 continue;
203 }
204
205 ++obj.dependency_count;
206 dep_iter->second.depend_by.push_back(&obj);
207 }
208 }
209
210 std::vector<detail::topological_sort_object_t *> run_order;
211 topological_sort(index_by_name, run_order);
212
213 for (size_t i = 0; i < run_order.size(); ++i) {
214 util::cli::shell_stream ss(std::cout);
216 << "[ On Start ] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << "Running " << run_order[i]->name
217 << std::endl;
218 reinterpret_cast<on_start_ptr_type>(run_order[i]->object)->run();
219 }
220
221 return 0;
222}
223
225 using index_by_name_t = std::unordered_map<std::string, detail::topological_sort_object_t>;
226 // generate topological_sort_object_t
227 index_by_name_t index_by_name;
228 for (size_t i = 0; i < evt_on_exits_.size(); ++i) {
229 detail::topological_sort_object_t &obj = index_by_name[evt_on_exits_[i].first];
230 obj.name = evt_on_exits_[i].first;
231 obj.object = reinterpret_cast<void *>(evt_on_exits_[i].second);
232 obj.dependency_count = 0;
233 }
234
235 for (size_t i = 0; i < evt_on_exits_.size(); ++i) {
236 detail::topological_sort_object_t &obj = index_by_name[evt_on_exits_[i].first];
237
238 for (test_on_exit_base::before_set_t::iterator iter = evt_on_exits_[i].second->before.begin();
239 iter != evt_on_exits_[i].second->before.end(); ++iter) {
240 index_by_name_t::iterator dep_iter = index_by_name.find(*iter);
241 if (dep_iter == index_by_name.end()) {
242 util::cli::shell_stream ss(std::cerr);
244 << "[ WARNING ] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << "On Exit Event "
245 << evt_on_exits_[i].first << " is configured run before " << (*iter) << ", but " << (*iter) << "not found."
246 << std::endl;
247 continue;
248 }
249
250 ++obj.dependency_count;
251 dep_iter->second.depend_by.push_back(&obj);
252 }
253 }
254
255 std::vector<detail::topological_sort_object_t *> run_order;
256 topological_sort(index_by_name, run_order);
257
258 for (size_t i = 0; i < run_order.size(); ++i) {
259 size_t idx = run_order.size() - 1 - i;
260 util::cli::shell_stream ss(std::cout);
262 << "[ On Exit ] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << "Running " << run_order[idx]->name
263 << std::endl;
264 reinterpret_cast<on_exit_ptr_type>(run_order[idx]->object)->run();
265 }
266
267 return 0;
268}
269
271 success_ = 0;
272 failed_ = 0;
273
274 clock_t all_begin_time = clock();
275 util::cli::shell_stream ss(std::cout);
277 << "[==========] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << "Running " << tests_.size()
278 << " test(s)" << std::endl;
279
280 for (test_data_type::iterator iter = tests_.begin(); iter != tests_.end(); ++iter) {
281 bool check_test_group_passed = run_cases_.empty();
282 bool check_test_group_has_cases = false;
283
284 if (!check_test_group_passed) {
285 check_test_group_passed = run_cases_.end() != run_cases_.find(iter->first);
286 }
287
288 if (!check_test_group_passed) {
289 check_test_group_has_cases = run_groups_.end() != run_groups_.find(iter->first);
290 }
291
292 // skip unknown groups
293 if (!check_test_group_passed && !check_test_group_has_cases) {
294 continue;
295 }
296
297 size_t run_group_count = 0;
298
299 ss() << std::endl
301 << "[----------] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << iter->second.size()
302 << " test case(s) from " << iter->first << std::endl;
303
304 clock_t test_begin_time = clock();
305 for (test_type::iterator iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) {
306 bool check_test_case_passed = run_cases_.empty() || check_test_group_passed;
307 if (!check_test_case_passed) {
308 check_test_case_passed = run_cases_.end() != run_cases_.find(iter2->first);
309
310 if (!check_test_case_passed) {
311 std::string full_name;
312 full_name.reserve(iter->first.size() + 1 + iter2->first.size());
313 full_name = iter->first + "." + iter2->first;
314 check_test_case_passed = run_cases_.end() != run_cases_.find(full_name);
315 }
316 }
317
318 // skip unknown cases
319 if (!check_test_case_passed) {
320 continue;
321 }
322
324 << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << iter->first << "." << iter2->first << std::endl;
325
326 clock_t case_begin_time = clock();
327 iter2->second->run();
328 clock_t case_end_time = clock();
329
330 if (0 == iter2->second->failed_) {
331 ++success_;
333 << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << iter->first << "." << iter2->first << " ("
334 << get_expire_time(case_begin_time, case_end_time) << ")" << std::endl;
335 } else {
336 ++failed_;
338 << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << iter->first << "." << iter2->first << " ("
339 << get_expire_time(case_begin_time, case_end_time) << ")" << std::endl;
340 }
341
342 ++run_group_count;
343 }
344
345 clock_t test_end_time = clock();
347 << "[----------] " << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << run_group_count
348 << " test case(s) from " << iter->first << " (" << get_expire_time(test_begin_time, test_end_time) << " total)"
349 << std::endl;
350 }
351
352 clock_t all_end_time = clock();
355 << " test(s) ran." << " (" << get_expire_time(all_begin_time, all_end_time) << " total)" << std::endl;
356
358 << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << success_ << " test case(s)." << std::endl;
359
360 if (failed_ > 0) {
362 << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << failed_ << " test case(s), listed below:" << std::endl;
363
364 for (test_data_type::iterator iter = tests_.begin(); iter != tests_.end(); ++iter) {
365 for (test_type::iterator iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) {
366 if (iter2->second->failed_ > 0) {
368 << util::cli::shell_font_style::SHELL_FONT_SPEC_NULL << iter->first << "." << iter2->first << std::endl;
369 }
370 }
371 }
372 }
373
374 return (0 == failed_) ? 0 : -failed_;
375}
376
377#endif
378
379void test_manager::set_cases(const std::vector<std::string> &case_names) {
380 run_cases_.clear();
381 run_groups_.clear();
382
383 for (size_t i = 0; i < case_names.size(); ++i) {
384 run_cases_.insert(case_names[i]);
385 std::string::size_type split_idx = case_names[i].find('.');
386 if (split_idx != std::string::npos) {
387 run_groups_.insert(case_names[i].substr(0, split_idx));
388 }
389 }
390}
391
393 static test_manager ret;
394 return ret;
395}
396
397std::string test_manager::get_expire_time(clock_t begin, clock_t end) {
398 std::stringstream ss;
399 double ms = 1000.0 * static_cast<double>(end - begin) / CLOCKS_PER_SEC;
400
401 ss << ms << " ms";
402
403 return ss.str();
404}
405
406void test_manager::set_counter_ptr(int *success_counter_ptr, int *failed_counter_ptr) {
408 if (nullptr != block) {
409 block->success_counter_ptr = success_counter_ptr;
410 block->failed_counter_ptr = failed_counter_ptr;
411 }
414}
415
418 if LIBCOPP_UTIL_LIKELY_CONDITION (nullptr != block && nullptr != block->success_counter_ptr) {
419 ++(*block->success_counter_ptr);
420 return;
421 }
422
423 util::cli::shell_stream ss(std::cerr);
425 << "[==========] "
426 << "Expect expression can not be used when not running test case." << std::endl;
427}
428
431 if LIBCOPP_UTIL_LIKELY_CONDITION (nullptr != block && nullptr != block->failed_counter_ptr) {
432 ++(*block->failed_counter_ptr);
433 return;
434 }
435
436 util::cli::shell_stream ss(std::cerr);
438 << "[==========] "
439 << "Expect expression can not be used when not running test case." << std::endl;
440}
441
443
445
446int run_tests(int argc, char *argv[]) {
447 std::vector<std::string> run_cases;
448 const char *version = "1.0.0";
449 bool is_help = false;
450 bool is_show_version = false;
451
453 cmd_opts->bind_cmd("-h, --help, help", util::cli::phoenix::set_const(is_help, true))
454 ->set_help_msg(" show help message and exit.");
455 cmd_opts->bind_cmd("-v, --version, version", util::cli::phoenix::set_const(is_show_version, true))
456 ->set_help_msg(" show version and exit.");
457 cmd_opts->bind_cmd("-r, --run, run", util::cli::phoenix::push_back(run_cases))
458 ->set_help_msg("[case names...] only run specify cases.");
459
460 cmd_opts->start(argc, argv);
461 if (is_help) {
462 std::cout << *cmd_opts << std::endl;
463 return 0;
464 }
465
466 if (is_show_version) {
467 std::cout << version << std::endl;
468 return 0;
469 }
470
471 test_manager::me().set_cases(run_cases);
473 int ret = test_manager::me().run();
475 return ret;
476}
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_
static void set_counter_ptr(int *success_counter_ptr, int *failed_counter_ptr)
event_on_start_type evt_on_starts_
test_data_type tests_
void append_event_on_exit(const std::string &event_name, on_exit_ptr_type)
virtual ~test_manager()
std::unordered_set< std::string > run_groups_
int run_event_on_start()
static void inc_failed_counter()
static void inc_success_counter()
int run_event_on_exit()
std::unordered_set< std::string > run_cases_
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 LIBCOPP_UTIL_LIKELY_CONDITION(__C)
static test_manager_tls_block_t g_global_counter_cache
test_manager_tls_block_t * get_test_manager_tls_block()
push_back_t< T > push_back(T &t)
set_const_t< T > set_const(T &t, const T &v)
std::list< topological_sort_object_t * > depend_by
int run_event_on_exit()
static void topological_sort(std::unordered_map< std::string, detail::topological_sort_object_t > &in, std::vector< detail::topological_sort_object_t * > &out)
int run_event_on_start()
int run_tests(int argc, char *argv[])