TestRunner.h 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. /*
  2. * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
  3. * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
  4. * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
  5. * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
  6. *
  7. * SPDX-License-Identifier: BSD-2-Clause
  8. */
  9. #pragma once
  10. #include <AK/DeprecatedString.h>
  11. #include <AK/Format.h>
  12. #include <AK/JsonObject.h>
  13. #include <AK/JsonValue.h>
  14. #include <AK/LexicalPath.h>
  15. #include <AK/QuickSort.h>
  16. #include <AK/Vector.h>
  17. #include <LibTest/Results.h>
  18. #include <LibTest/TestRunnerUtil.h>
  19. namespace Test {
  20. class TestRunner {
  21. public:
  22. static TestRunner* the()
  23. {
  24. return s_the;
  25. }
  26. TestRunner(DeprecatedString test_root, bool print_times, bool print_progress, bool print_json, bool detailed_json = false)
  27. : m_test_root(move(test_root))
  28. , m_print_times(print_times)
  29. , m_print_progress(print_progress)
  30. , m_print_json(print_json)
  31. , m_detailed_json(detailed_json)
  32. {
  33. VERIFY(!s_the);
  34. s_the = this;
  35. }
  36. virtual ~TestRunner() { s_the = nullptr; };
  37. virtual void run(DeprecatedString test_glob);
  38. Test::Counts const& counts() const { return m_counts; }
  39. bool is_printing_progress() const { return m_print_progress; }
  40. bool needs_detailed_suites() const { return m_detailed_json; }
  41. Vector<Test::Suite> const& suites() const { return *m_suites; }
  42. Vector<Test::Suite>& ensure_suites()
  43. {
  44. if (!m_suites.has_value())
  45. m_suites = Vector<Suite> {};
  46. return *m_suites;
  47. }
  48. protected:
  49. static TestRunner* s_the;
  50. void print_test_results() const;
  51. void print_test_results_as_json() const;
  52. virtual Vector<DeprecatedString> get_test_paths() const = 0;
  53. virtual void do_run_single_test(DeprecatedString const&, size_t current_test_index, size_t num_tests) = 0;
  54. virtual Vector<DeprecatedString> const* get_failed_test_names() const { return nullptr; }
  55. DeprecatedString m_test_root;
  56. bool m_print_times;
  57. bool m_print_progress;
  58. bool m_print_json;
  59. bool m_detailed_json;
  60. double m_total_elapsed_time_in_ms { 0 };
  61. Test::Counts m_counts;
  62. Optional<Vector<Test::Suite>> m_suites;
  63. };
  64. inline void cleanup()
  65. {
  66. // Clear the taskbar progress.
  67. if (TestRunner::the() && TestRunner::the()->is_printing_progress())
  68. warn("\033]9;-1;\033\\");
  69. }
  70. [[noreturn]] inline void cleanup_and_exit()
  71. {
  72. cleanup();
  73. exit(1);
  74. }
  75. inline void TestRunner::run(DeprecatedString test_glob)
  76. {
  77. size_t progress_counter = 0;
  78. auto test_paths = get_test_paths();
  79. for (auto& path : test_paths) {
  80. if (!path.matches(test_glob))
  81. continue;
  82. ++progress_counter;
  83. do_run_single_test(path, progress_counter, test_paths.size());
  84. if (m_print_progress)
  85. warn("\033]9;{};{};\033\\", progress_counter, test_paths.size());
  86. }
  87. if (m_print_progress)
  88. warn("\033]9;-1;\033\\");
  89. if (!m_print_json)
  90. print_test_results();
  91. else
  92. print_test_results_as_json();
  93. }
  94. enum Modifier {
  95. BG_RED,
  96. BG_GREEN,
  97. FG_RED,
  98. FG_GREEN,
  99. FG_ORANGE,
  100. FG_GRAY,
  101. FG_BLACK,
  102. FG_BOLD,
  103. ITALIC,
  104. CLEAR,
  105. };
  106. inline void print_modifiers(Vector<Modifier> modifiers)
  107. {
  108. for (auto& modifier : modifiers) {
  109. auto code = [&] {
  110. switch (modifier) {
  111. case BG_RED:
  112. return "\033[48;2;255;0;102m";
  113. case BG_GREEN:
  114. return "\033[48;2;102;255;0m";
  115. case FG_RED:
  116. return "\033[38;2;255;0;102m";
  117. case FG_GREEN:
  118. return "\033[38;2;102;255;0m";
  119. case FG_ORANGE:
  120. return "\033[38;2;255;102;0m";
  121. case FG_GRAY:
  122. return "\033[38;2;135;139;148m";
  123. case FG_BLACK:
  124. return "\033[30m";
  125. case FG_BOLD:
  126. return "\033[1m";
  127. case ITALIC:
  128. return "\033[3m";
  129. case CLEAR:
  130. return "\033[0m";
  131. }
  132. VERIFY_NOT_REACHED();
  133. }();
  134. out("{}", code);
  135. }
  136. }
  137. inline void TestRunner::print_test_results() const
  138. {
  139. out("\nTest Suites: ");
  140. if (m_counts.suites_failed) {
  141. print_modifiers({ FG_RED });
  142. out("{} failed, ", m_counts.suites_failed);
  143. print_modifiers({ CLEAR });
  144. }
  145. if (m_counts.suites_passed) {
  146. print_modifiers({ FG_GREEN });
  147. out("{} passed, ", m_counts.suites_passed);
  148. print_modifiers({ CLEAR });
  149. }
  150. outln("{} total", m_counts.suites_failed + m_counts.suites_passed);
  151. out("Tests: ");
  152. if (m_counts.tests_failed) {
  153. print_modifiers({ FG_RED });
  154. out("{} failed, ", m_counts.tests_failed);
  155. print_modifiers({ CLEAR });
  156. }
  157. if (m_counts.tests_skipped) {
  158. print_modifiers({ FG_ORANGE });
  159. out("{} skipped, ", m_counts.tests_skipped);
  160. print_modifiers({ CLEAR });
  161. }
  162. if (m_counts.tests_passed) {
  163. print_modifiers({ FG_GREEN });
  164. out("{} passed, ", m_counts.tests_passed);
  165. print_modifiers({ CLEAR });
  166. }
  167. outln("{} total", m_counts.tests_failed + m_counts.tests_skipped + m_counts.tests_passed);
  168. outln("Files: {} total", m_counts.files_total);
  169. out("Time: ");
  170. if (m_total_elapsed_time_in_ms < 1000.0) {
  171. outln("{}ms", static_cast<int>(m_total_elapsed_time_in_ms));
  172. } else {
  173. outln("{:>.3}s", m_total_elapsed_time_in_ms / 1000.0);
  174. }
  175. if (auto* failed_tests = get_failed_test_names(); failed_tests && !failed_tests->is_empty()) {
  176. outln("Failed tests: {}", *failed_tests);
  177. }
  178. outln();
  179. }
  180. inline void TestRunner::print_test_results_as_json() const
  181. {
  182. JsonObject root;
  183. if (needs_detailed_suites()) {
  184. auto& suites = this->suites();
  185. u64 duration_us = 0;
  186. JsonObject tests;
  187. for (auto& suite : suites) {
  188. for (auto& case_ : suite.tests) {
  189. duration_us += case_.duration_us;
  190. StringView result_name;
  191. switch (case_.result) {
  192. case Result::Pass:
  193. result_name = "PASSED"sv;
  194. break;
  195. case Result::Fail:
  196. result_name = "FAILED"sv;
  197. break;
  198. case Result::Skip:
  199. result_name = "SKIPPED"sv;
  200. break;
  201. case Result::Crashed:
  202. result_name = "PROCESS_ERROR"sv;
  203. break;
  204. }
  205. auto name = suite.name;
  206. if (name == "__$$TOP_LEVEL$$__"sv)
  207. name = DeprecatedString::empty();
  208. auto path = LexicalPath::relative_path(suite.path, m_test_root);
  209. tests.set(DeprecatedString::formatted("{}/{}::{}", path, name, case_.name), result_name);
  210. }
  211. }
  212. root.set("duration", static_cast<double>(duration_us) / 1000000.);
  213. root.set("results", move(tests));
  214. } else {
  215. JsonObject suites;
  216. suites.set("failed", m_counts.suites_failed);
  217. suites.set("passed", m_counts.suites_passed);
  218. suites.set("total", m_counts.suites_failed + m_counts.suites_passed);
  219. JsonObject tests;
  220. tests.set("failed", m_counts.tests_failed);
  221. tests.set("passed", m_counts.tests_passed);
  222. tests.set("skipped", m_counts.tests_skipped);
  223. tests.set("total", m_counts.tests_failed + m_counts.tests_passed + m_counts.tests_skipped);
  224. JsonObject results;
  225. results.set("suites", suites);
  226. results.set("tests", tests);
  227. root.set("results", results);
  228. root.set("files_total", m_counts.files_total);
  229. root.set("duration", m_total_elapsed_time_in_ms / 1000.0);
  230. }
  231. outln("{}", root.to_deprecated_string());
  232. }
  233. }