TestRunner.h 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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)
  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. {
  33. VERIFY(!s_the);
  34. s_the = this;
  35. }
  36. virtual ~TestRunner() { s_the = nullptr; };
  37. virtual void run(String test_glob);
  38. const Test::Counts& counts() const { return m_counts; }
  39. bool is_printing_progress() const { return m_print_progress; }
  40. protected:
  41. static TestRunner* s_the;
  42. void print_test_results() const;
  43. void print_test_results_as_json() const;
  44. virtual Vector<String> get_test_paths() const = 0;
  45. virtual void do_run_single_test(const String&, size_t current_test_index, size_t num_tests) = 0;
  46. virtual const Vector<String>* get_failed_test_names() const { return nullptr; }
  47. String m_test_root;
  48. bool m_print_times;
  49. bool m_print_progress;
  50. bool m_print_json;
  51. double m_total_elapsed_time_in_ms { 0 };
  52. Test::Counts m_counts;
  53. };
  54. inline void cleanup()
  55. {
  56. // Clear the taskbar progress.
  57. if (TestRunner::the() && TestRunner::the()->is_printing_progress())
  58. warn("\033]9;-1;\033\\");
  59. }
  60. [[noreturn]] inline void cleanup_and_exit()
  61. {
  62. cleanup();
  63. exit(1);
  64. }
  65. inline double get_time_in_ms()
  66. {
  67. struct timeval tv1;
  68. auto return_code = gettimeofday(&tv1, nullptr);
  69. VERIFY(return_code >= 0);
  70. return static_cast<double>(tv1.tv_sec) * 1000.0 + static_cast<double>(tv1.tv_usec) / 1000.0;
  71. }
  72. template<typename Callback>
  73. inline void iterate_directory_recursively(const String& directory_path, Callback callback)
  74. {
  75. Core::DirIterator directory_iterator(directory_path, Core::DirIterator::Flags::SkipDots);
  76. while (directory_iterator.has_next()) {
  77. auto name = directory_iterator.next_path();
  78. struct stat st = {};
  79. if (fstatat(directory_iterator.fd(), name.characters(), &st, AT_SYMLINK_NOFOLLOW) < 0)
  80. continue;
  81. bool is_directory = S_ISDIR(st.st_mode);
  82. auto full_path = String::formatted("{}/{}", directory_path, name);
  83. if (is_directory && name != "/Fixtures"sv) {
  84. iterate_directory_recursively(full_path, callback);
  85. } else if (!is_directory) {
  86. callback(full_path);
  87. }
  88. }
  89. }
  90. inline void TestRunner::run(String test_glob)
  91. {
  92. size_t progress_counter = 0;
  93. auto test_paths = get_test_paths();
  94. for (auto& path : test_paths) {
  95. if (!path.matches(test_glob))
  96. continue;
  97. ++progress_counter;
  98. do_run_single_test(path, progress_counter, test_paths.size());
  99. if (m_print_progress)
  100. warn("\033]9;{};{};\033\\", progress_counter, test_paths.size());
  101. }
  102. if (m_print_progress)
  103. warn("\033]9;-1;\033\\");
  104. if (!m_print_json)
  105. print_test_results();
  106. else
  107. print_test_results_as_json();
  108. }
  109. enum Modifier {
  110. BG_RED,
  111. BG_GREEN,
  112. FG_RED,
  113. FG_GREEN,
  114. FG_ORANGE,
  115. FG_GRAY,
  116. FG_BLACK,
  117. FG_BOLD,
  118. ITALIC,
  119. CLEAR,
  120. };
  121. inline void print_modifiers(Vector<Modifier> modifiers)
  122. {
  123. for (auto& modifier : modifiers) {
  124. auto code = [&] {
  125. switch (modifier) {
  126. case BG_RED:
  127. return "\033[48;2;255;0;102m";
  128. case BG_GREEN:
  129. return "\033[48;2;102;255;0m";
  130. case FG_RED:
  131. return "\033[38;2;255;0;102m";
  132. case FG_GREEN:
  133. return "\033[38;2;102;255;0m";
  134. case FG_ORANGE:
  135. return "\033[38;2;255;102;0m";
  136. case FG_GRAY:
  137. return "\033[38;2;135;139;148m";
  138. case FG_BLACK:
  139. return "\033[30m";
  140. case FG_BOLD:
  141. return "\033[1m";
  142. case ITALIC:
  143. return "\033[3m";
  144. case CLEAR:
  145. return "\033[0m";
  146. }
  147. VERIFY_NOT_REACHED();
  148. }();
  149. out("{}", code);
  150. }
  151. }
  152. inline void TestRunner::print_test_results() const
  153. {
  154. out("\nTest Suites: ");
  155. if (m_counts.suites_failed) {
  156. print_modifiers({ FG_RED });
  157. out("{} failed, ", m_counts.suites_failed);
  158. print_modifiers({ CLEAR });
  159. }
  160. if (m_counts.suites_passed) {
  161. print_modifiers({ FG_GREEN });
  162. out("{} passed, ", m_counts.suites_passed);
  163. print_modifiers({ CLEAR });
  164. }
  165. outln("{} total", m_counts.suites_failed + m_counts.suites_passed);
  166. out("Tests: ");
  167. if (m_counts.tests_failed) {
  168. print_modifiers({ FG_RED });
  169. out("{} failed, ", m_counts.tests_failed);
  170. print_modifiers({ CLEAR });
  171. }
  172. if (m_counts.tests_skipped) {
  173. print_modifiers({ FG_ORANGE });
  174. out("{} skipped, ", m_counts.tests_skipped);
  175. print_modifiers({ CLEAR });
  176. }
  177. if (m_counts.tests_passed) {
  178. print_modifiers({ FG_GREEN });
  179. out("{} passed, ", m_counts.tests_passed);
  180. print_modifiers({ CLEAR });
  181. }
  182. outln("{} total", m_counts.tests_failed + m_counts.tests_skipped + m_counts.tests_passed);
  183. outln("Files: {} total", m_counts.files_total);
  184. out("Time: ");
  185. if (m_total_elapsed_time_in_ms < 1000.0) {
  186. outln("{}ms", static_cast<int>(m_total_elapsed_time_in_ms));
  187. } else {
  188. outln("{:>.3}s", m_total_elapsed_time_in_ms / 1000.0);
  189. }
  190. if (auto* failed_tests = get_failed_test_names(); failed_tests && !failed_tests->is_empty()) {
  191. outln("Failed tests: {}", *failed_tests);
  192. }
  193. outln();
  194. }
  195. inline void TestRunner::print_test_results_as_json() const
  196. {
  197. JsonObject suites;
  198. suites.set("failed", m_counts.suites_failed);
  199. suites.set("passed", m_counts.suites_passed);
  200. suites.set("total", m_counts.suites_failed + m_counts.suites_passed);
  201. JsonObject tests;
  202. tests.set("failed", m_counts.tests_failed);
  203. tests.set("passed", m_counts.tests_passed);
  204. tests.set("skipped", m_counts.tests_skipped);
  205. tests.set("total", m_counts.tests_failed + m_counts.tests_passed + m_counts.tests_skipped);
  206. JsonObject results;
  207. results.set("suites", suites);
  208. results.set("tests", tests);
  209. JsonObject root;
  210. root.set("results", results);
  211. root.set("files_total", m_counts.files_total);
  212. root.set("duration", m_total_elapsed_time_in_ms / 1000.0);
  213. outln("{}", root.to_string());
  214. }
  215. }