TestRunner.h 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  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/Format.h>
  11. #include <AK/JsonObject.h>
  12. #include <AK/JsonValue.h>
  13. #include <AK/QuickSort.h>
  14. #include <AK/String.h>
  15. #include <AK/Vector.h>
  16. #include <LibCore/DirIterator.h>
  17. #include <LibTest/Results.h>
  18. #include <fcntl.h>
  19. #include <sys/time.h>
  20. namespace Test {
  21. class TestRunner {
  22. public:
  23. static TestRunner* the()
  24. {
  25. return s_the;
  26. }
  27. TestRunner(String test_root, bool print_times, bool print_progress, bool print_json, bool detailed_json = false)
  28. : m_test_root(move(test_root))
  29. , m_print_times(print_times)
  30. , m_print_progress(print_progress)
  31. , m_print_json(print_json)
  32. , m_detailed_json(detailed_json)
  33. {
  34. VERIFY(!s_the);
  35. s_the = this;
  36. }
  37. virtual ~TestRunner() { s_the = nullptr; };
  38. virtual void run(String test_glob);
  39. Test::Counts const& counts() const { return m_counts; }
  40. bool is_printing_progress() const { return m_print_progress; }
  41. bool needs_detailed_suites() const { return m_detailed_json; }
  42. Vector<Test::Suite> const& suites() const { return *m_suites; }
  43. Vector<Test::Suite>& ensure_suites()
  44. {
  45. if (!m_suites.has_value())
  46. m_suites = Vector<Suite> {};
  47. return *m_suites;
  48. }
  49. protected:
  50. static TestRunner* s_the;
  51. void print_test_results() const;
  52. void print_test_results_as_json() const;
  53. virtual Vector<String> get_test_paths() const = 0;
  54. virtual void do_run_single_test(String const&, size_t current_test_index, size_t num_tests) = 0;
  55. virtual Vector<String> const* get_failed_test_names() const { return nullptr; }
  56. String m_test_root;
  57. bool m_print_times;
  58. bool m_print_progress;
  59. bool m_print_json;
  60. bool m_detailed_json;
  61. double m_total_elapsed_time_in_ms { 0 };
  62. Test::Counts m_counts;
  63. Optional<Vector<Test::Suite>> m_suites;
  64. };
  65. inline void cleanup()
  66. {
  67. // Clear the taskbar progress.
  68. if (TestRunner::the() && TestRunner::the()->is_printing_progress())
  69. warn("\033]9;-1;\033\\");
  70. }
  71. [[noreturn]] inline void cleanup_and_exit()
  72. {
  73. cleanup();
  74. exit(1);
  75. }
  76. inline double get_time_in_ms()
  77. {
  78. struct timeval tv1;
  79. auto return_code = gettimeofday(&tv1, nullptr);
  80. VERIFY(return_code >= 0);
  81. return static_cast<double>(tv1.tv_sec) * 1000.0 + static_cast<double>(tv1.tv_usec) / 1000.0;
  82. }
  83. template<typename Callback>
  84. inline void iterate_directory_recursively(String const& directory_path, Callback callback)
  85. {
  86. Core::DirIterator directory_iterator(directory_path, Core::DirIterator::Flags::SkipDots);
  87. while (directory_iterator.has_next()) {
  88. auto name = directory_iterator.next_path();
  89. struct stat st = {};
  90. if (fstatat(directory_iterator.fd(), name.characters(), &st, AT_SYMLINK_NOFOLLOW) < 0)
  91. continue;
  92. bool is_directory = S_ISDIR(st.st_mode);
  93. auto full_path = String::formatted("{}/{}", directory_path, name);
  94. if (is_directory && name != "/Fixtures"sv) {
  95. iterate_directory_recursively(full_path, callback);
  96. } else if (!is_directory) {
  97. callback(full_path);
  98. }
  99. }
  100. }
  101. inline void TestRunner::run(String test_glob)
  102. {
  103. size_t progress_counter = 0;
  104. auto test_paths = get_test_paths();
  105. for (auto& path : test_paths) {
  106. if (!path.matches(test_glob))
  107. continue;
  108. ++progress_counter;
  109. do_run_single_test(path, progress_counter, test_paths.size());
  110. if (m_print_progress)
  111. warn("\033]9;{};{};\033\\", progress_counter, test_paths.size());
  112. }
  113. if (m_print_progress)
  114. warn("\033]9;-1;\033\\");
  115. if (!m_print_json)
  116. print_test_results();
  117. else
  118. print_test_results_as_json();
  119. }
  120. enum Modifier {
  121. BG_RED,
  122. BG_GREEN,
  123. FG_RED,
  124. FG_GREEN,
  125. FG_ORANGE,
  126. FG_GRAY,
  127. FG_BLACK,
  128. FG_BOLD,
  129. ITALIC,
  130. CLEAR,
  131. };
  132. inline void print_modifiers(Vector<Modifier> modifiers)
  133. {
  134. for (auto& modifier : modifiers) {
  135. auto code = [&] {
  136. switch (modifier) {
  137. case BG_RED:
  138. return "\033[48;2;255;0;102m";
  139. case BG_GREEN:
  140. return "\033[48;2;102;255;0m";
  141. case FG_RED:
  142. return "\033[38;2;255;0;102m";
  143. case FG_GREEN:
  144. return "\033[38;2;102;255;0m";
  145. case FG_ORANGE:
  146. return "\033[38;2;255;102;0m";
  147. case FG_GRAY:
  148. return "\033[38;2;135;139;148m";
  149. case FG_BLACK:
  150. return "\033[30m";
  151. case FG_BOLD:
  152. return "\033[1m";
  153. case ITALIC:
  154. return "\033[3m";
  155. case CLEAR:
  156. return "\033[0m";
  157. }
  158. VERIFY_NOT_REACHED();
  159. }();
  160. out("{}", code);
  161. }
  162. }
  163. inline void TestRunner::print_test_results() const
  164. {
  165. out("\nTest Suites: ");
  166. if (m_counts.suites_failed) {
  167. print_modifiers({ FG_RED });
  168. out("{} failed, ", m_counts.suites_failed);
  169. print_modifiers({ CLEAR });
  170. }
  171. if (m_counts.suites_passed) {
  172. print_modifiers({ FG_GREEN });
  173. out("{} passed, ", m_counts.suites_passed);
  174. print_modifiers({ CLEAR });
  175. }
  176. outln("{} total", m_counts.suites_failed + m_counts.suites_passed);
  177. out("Tests: ");
  178. if (m_counts.tests_failed) {
  179. print_modifiers({ FG_RED });
  180. out("{} failed, ", m_counts.tests_failed);
  181. print_modifiers({ CLEAR });
  182. }
  183. if (m_counts.tests_skipped) {
  184. print_modifiers({ FG_ORANGE });
  185. out("{} skipped, ", m_counts.tests_skipped);
  186. print_modifiers({ CLEAR });
  187. }
  188. if (m_counts.tests_passed) {
  189. print_modifiers({ FG_GREEN });
  190. out("{} passed, ", m_counts.tests_passed);
  191. print_modifiers({ CLEAR });
  192. }
  193. outln("{} total", m_counts.tests_failed + m_counts.tests_skipped + m_counts.tests_passed);
  194. outln("Files: {} total", m_counts.files_total);
  195. out("Time: ");
  196. if (m_total_elapsed_time_in_ms < 1000.0) {
  197. outln("{}ms", static_cast<int>(m_total_elapsed_time_in_ms));
  198. } else {
  199. outln("{:>.3}s", m_total_elapsed_time_in_ms / 1000.0);
  200. }
  201. if (auto* failed_tests = get_failed_test_names(); failed_tests && !failed_tests->is_empty()) {
  202. outln("Failed tests: {}", *failed_tests);
  203. }
  204. outln();
  205. }
  206. inline void TestRunner::print_test_results_as_json() const
  207. {
  208. JsonObject root;
  209. if (needs_detailed_suites()) {
  210. auto& suites = this->suites();
  211. u64 duration_us = 0;
  212. JsonObject tests;
  213. for (auto& suite : suites) {
  214. for (auto& case_ : suite.tests) {
  215. duration_us += case_.duration_us;
  216. StringView result_name;
  217. switch (case_.result) {
  218. case Result::Pass:
  219. result_name = "PASSED";
  220. break;
  221. case Result::Fail:
  222. result_name = "FAILED";
  223. break;
  224. case Result::Skip:
  225. result_name = "SKIPPED";
  226. break;
  227. case Result::Crashed:
  228. result_name = "PROCESS_ERROR";
  229. break;
  230. }
  231. auto name = suite.name;
  232. if (name == "__$$TOP_LEVEL$$__"sv)
  233. name = String::empty();
  234. auto path = LexicalPath::relative_path(suite.path, m_test_root);
  235. tests.set(String::formatted("{}/{}::{}", path, name, case_.name), result_name);
  236. }
  237. }
  238. root.set("duration", static_cast<double>(duration_us) / 1000000.);
  239. root.set("results", move(tests));
  240. } else {
  241. JsonObject suites;
  242. suites.set("failed", m_counts.suites_failed);
  243. suites.set("passed", m_counts.suites_passed);
  244. suites.set("total", m_counts.suites_failed + m_counts.suites_passed);
  245. JsonObject tests;
  246. tests.set("failed", m_counts.tests_failed);
  247. tests.set("passed", m_counts.tests_passed);
  248. tests.set("skipped", m_counts.tests_skipped);
  249. tests.set("total", m_counts.tests_failed + m_counts.tests_passed + m_counts.tests_skipped);
  250. JsonObject results;
  251. results.set("suites", suites);
  252. results.set("tests", tests);
  253. root.set("results", results);
  254. root.set("files_total", m_counts.files_total);
  255. root.set("duration", m_total_elapsed_time_in_ms / 1000.0);
  256. }
  257. outln("{}", root.to_string());
  258. }
  259. }