main.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. #include "main.h"
  2. #include "gtest.h"
  3. #include <library/cpp/string_utils/relaxed_escaper/relaxed_escaper.h>
  4. #include <library/cpp/testing/common/env.h>
  5. #include <library/cpp/testing/hook/hook.h>
  6. #include <util/generic/scope.h>
  7. #include <util/string/join.h>
  8. #include <util/system/src_root.h>
  9. #include <fstream>
  10. namespace {
  11. bool StartsWith(const char* str, const char* pre) {
  12. return strncmp(pre, str, strlen(pre)) == 0;
  13. }
  14. void Unsupported(const char* flag) {
  15. std::cerr << "This GTest wrapper does not support flag " << flag << std::endl;
  16. exit(2);
  17. }
  18. void Unknown(const char* flag) {
  19. std::cerr << "Unknown support flag " << flag << std::endl;
  20. exit(2);
  21. }
  22. std::pair<std::string_view, std::string_view> ParseName(std::string_view name) {
  23. auto pos = name.find("::");
  24. if (pos == std::string_view::npos) {
  25. return {name, "*"};
  26. } else {
  27. return {name.substr(0, pos), name.substr(pos + 2, name.size())};
  28. }
  29. }
  30. std::pair<std::string_view, std::string_view> ParseParam(std::string_view param) {
  31. auto pos = param.find("=");
  32. if (pos == std::string_view::npos) {
  33. return {param, ""};
  34. } else {
  35. return {param.substr(0, pos), param.substr(pos + 1, param.size())};
  36. }
  37. }
  38. constexpr std::string_view StripRoot(std::string_view f) noexcept {
  39. return ::NPrivate::StripRoot(::NPrivate::TStaticBuf(f.data(), f.size())).As<std::string_view>();
  40. }
  41. std::string EscapeJson(std::string_view str) {
  42. TString result;
  43. NEscJ::EscapeJ<true, true>(str, result);
  44. return result;
  45. }
  46. class TTraceWriter: public ::testing::EmptyTestEventListener {
  47. public:
  48. explicit TTraceWriter(std::ostream* trace)
  49. : Trace_(trace)
  50. {
  51. }
  52. private:
  53. void OnTestProgramStart(const testing::UnitTest& test) override {
  54. auto ts = std::chrono::duration_cast<std::chrono::duration<double>>(
  55. std::chrono::system_clock::now().time_since_epoch());
  56. for (int i = 0; i < test.total_test_suite_count(); ++i) {
  57. auto suite = test.GetTestSuite(i);
  58. for (int j = 0; j < suite->total_test_count(); ++j) {
  59. auto testInfo = suite->GetTestInfo(j);
  60. if (testInfo->is_reportable() && !testInfo->should_run()) {
  61. PrintTestStatus(*testInfo, "skipped", "test is disabled", {}, ts);
  62. }
  63. }
  64. }
  65. }
  66. void OnTestStart(const ::testing::TestInfo& testInfo) override {
  67. // We fully format this marker before printing it to stderr/stdout because we want to print it atomically.
  68. // If we were to write `std::cout << "\n###subtest-finished:" << name`, there would be a chance that
  69. // someone else could sneak in and print something between `"\n###subtest-finished"` and `name`
  70. // (this happens when test binary uses both `Cout` and `std::cout`).
  71. auto marker = Join("", "\n###subtest-started:", testInfo.test_suite_name(), "::", testInfo.name(), "\n");
  72. // Theoretically, we don't need to flush both `Cerr` and `std::cerr` here because both ultimately
  73. // result in calling `fflush(stderr)`. However, there may be additional buffering logic
  74. // going on (custom `std::cerr.tie()`, for example), so just to be sure, we flush both of them.
  75. std::cout << std::flush;
  76. Cout << marker << Flush;
  77. std::cerr << std::flush;
  78. Cerr << marker << Flush;
  79. auto ts = std::chrono::duration_cast<std::chrono::duration<double>>(
  80. std::chrono::system_clock::now().time_since_epoch());
  81. (*Trace_)
  82. << "{"
  83. << "\"name\": \"subtest-started\", "
  84. << "\"timestamp\": " << std::setprecision(14) << ts.count() << ", "
  85. << "\"value\": {"
  86. << "\"class\": " << EscapeJson(testInfo.test_suite_name()) << ", "
  87. << "\"subtest\": " << EscapeJson(testInfo.name())
  88. << "}"
  89. << "}"
  90. << "\n"
  91. << std::flush;
  92. }
  93. void OnTestPartResult(const testing::TestPartResult& result) override {
  94. if (!result.passed()) {
  95. if (result.file_name()) {
  96. std::cerr << StripRoot(result.file_name()) << ":" << result.line_number() << ":" << "\n";
  97. }
  98. std::cerr << result.message() << "\n";
  99. std::cerr << std::flush;
  100. }
  101. }
  102. void OnTestEnd(const ::testing::TestInfo& testInfo) override {
  103. auto ts = std::chrono::duration_cast<std::chrono::duration<double>>(
  104. std::chrono::system_clock::now().time_since_epoch());
  105. std::string_view status = "good";
  106. if (testInfo.result()->Failed()) {
  107. status = "fail";
  108. } else if (testInfo.result()->Skipped()) {
  109. status = "skipped";
  110. }
  111. std::ostringstream messages;
  112. std::unordered_map<std::string, double> properties;
  113. {
  114. if (testInfo.value_param()) {
  115. messages << "Value param:\n " << testInfo.value_param() << "\n";
  116. }
  117. if (testInfo.type_param()) {
  118. messages << "Type param:\n " << testInfo.type_param() << "\n";
  119. }
  120. std::string_view sep;
  121. for (int i = 0; i < testInfo.result()->total_part_count(); ++i) {
  122. auto part = testInfo.result()->GetTestPartResult(i);
  123. if (part.failed()) {
  124. messages << sep;
  125. if (part.file_name()) {
  126. messages << StripRoot(part.file_name()) << ":" << part.line_number() << ":\n";
  127. }
  128. messages << part.message();
  129. messages << "\n";
  130. sep = "\n";
  131. }
  132. }
  133. for (int i = 0; i < testInfo.result()->test_property_count(); ++i) {
  134. auto& property = testInfo.result()->GetTestProperty(i);
  135. double value;
  136. try {
  137. value = std::stod(property.value());
  138. } catch (std::invalid_argument&) {
  139. messages
  140. << sep
  141. << "Arcadia CI only supports numeric properties, property "
  142. << property.key() << "=" << EscapeJson(property.value()) << " is not a number\n";
  143. std::cerr
  144. << "Arcadia CI only supports numeric properties, property "
  145. << property.key() << "=" << EscapeJson(property.value()) << " is not a number\n"
  146. << std::flush;
  147. status = "fail";
  148. sep = "\n";
  149. continue;
  150. } catch (std::out_of_range&) {
  151. messages
  152. << sep
  153. << "Property " << property.key() << "=" << EscapeJson(property.value())
  154. << " is too big for a double precision value\n";
  155. std::cerr
  156. << "Property " << property.key() << "=" << EscapeJson(property.value())
  157. << " is too big for a double precision value\n"
  158. << std::flush;
  159. status = "fail";
  160. sep = "\n";
  161. continue;
  162. }
  163. properties[property.key()] = value;
  164. }
  165. }
  166. auto marker = Join("", "\n###subtest-finished:", testInfo.test_suite_name(), "::", testInfo.name(), "\n");
  167. std::cout << std::flush;
  168. Cout << marker << Flush;
  169. std::cerr << std::flush;
  170. Cerr << marker << Flush;
  171. PrintTestStatus(testInfo, status, messages.str(), properties, ts);
  172. }
  173. void PrintTestStatus(
  174. const ::testing::TestInfo& testInfo,
  175. std::string_view status,
  176. std::string_view messages,
  177. const std::unordered_map<std::string, double>& properties,
  178. std::chrono::duration<double> ts)
  179. {
  180. (*Trace_)
  181. << "{"
  182. << "\"name\": \"subtest-finished\", "
  183. << "\"timestamp\": " << std::setprecision(14) << ts.count() << ", "
  184. << "\"value\": {"
  185. << "\"class\": " << EscapeJson(testInfo.test_suite_name()) << ", "
  186. << "\"subtest\": " << EscapeJson(testInfo.name()) << ", "
  187. << "\"comment\": " << EscapeJson(messages) << ", "
  188. << "\"status\": " << EscapeJson(status) << ", "
  189. << "\"time\": " << (testInfo.result()->elapsed_time() * (1 / 1000.0)) << ", "
  190. << "\"metrics\": {";
  191. {
  192. std::string_view sep = "";
  193. for (auto& [key, value]: properties) {
  194. (*Trace_) << sep << EscapeJson(key) << ": " << value;
  195. sep = ", ";
  196. }
  197. }
  198. (*Trace_)
  199. << "}"
  200. << "}"
  201. << "}"
  202. << "\n"
  203. << std::flush;
  204. }
  205. std::ostream* Trace_;
  206. };
  207. }
  208. int NGTest::Main(int argc, char** argv) {
  209. auto flags = ParseFlags(argc, argv);
  210. ::testing::GTEST_FLAG(filter) = flags.Filter;
  211. std::ofstream trace;
  212. if (!flags.TracePath.empty()) {
  213. trace.open(flags.TracePath, (flags.AppendTrace ? std::ios::app : std::ios::out) | std::ios::binary);
  214. if (!trace.is_open()) {
  215. std::cerr << "Failed to open file " << flags.TracePath << " for write" << std::endl;
  216. exit(2);
  217. }
  218. UnsetDefaultReporter();
  219. SetTraceReporter(&trace);
  220. }
  221. NTesting::THook::CallBeforeInit();
  222. ::testing::InitGoogleMock(&flags.GtestArgc, flags.GtestArgv.data());
  223. ListTests(flags.ListLevel, flags.ListPath);
  224. NTesting::THook::CallBeforeRun();
  225. Y_DEFER { NTesting::THook::CallAfterRun(); };
  226. return RUN_ALL_TESTS();
  227. }
  228. NGTest::TFlags NGTest::ParseFlags(int argc, char** argv) {
  229. TFlags result;
  230. std::ostringstream filtersPos;
  231. std::string_view filterPosSep = "";
  232. std::ostringstream filtersNeg;
  233. std::string_view filterNegSep = "";
  234. if (argc > 0) {
  235. result.GtestArgv.push_back(argv[0]);
  236. }
  237. for (int i = 1; i < argc; ++i) {
  238. auto name = argv[i];
  239. if (strcmp(name, "--help") == 0) {
  240. result.GtestArgv.push_back(name);
  241. break;
  242. } else if (StartsWith(name, "--gtest_") || StartsWith(name, "--gmock_")) {
  243. result.GtestArgv.push_back(name);
  244. } else if (strcmp(name, "--list") == 0 || strcmp(name, "-l") == 0) {
  245. result.ListLevel = std::max(result.ListLevel, 1);
  246. } else if (strcmp(name, "--list-verbose") == 0) {
  247. result.ListLevel = std::max(result.ListLevel, 2);
  248. } else if (strcmp(name, "--print-before-suite") == 0) {
  249. Unsupported("--print-before-suite");
  250. } else if (strcmp(name, "--print-before-test") == 0) {
  251. Unsupported("--print-before-test");
  252. } else if (strcmp(name, "--show-fails") == 0) {
  253. Unsupported("--show-fails");
  254. } else if (strcmp(name, "--dont-show-fails") == 0) {
  255. Unsupported("--dont-show-fails");
  256. } else if (strcmp(name, "--print-times") == 0) {
  257. Unsupported("--print-times");
  258. } else if (strcmp(name, "--from") == 0) {
  259. Unsupported("--from");
  260. } else if (strcmp(name, "--to") == 0) {
  261. Unsupported("--to");
  262. } else if (strcmp(name, "--fork-tests") == 0) {
  263. Unsupported("--fork-tests");
  264. } else if (strcmp(name, "--is-forked-internal") == 0) {
  265. Unsupported("--is-forked-internal");
  266. } else if (strcmp(name, "--loop") == 0) {
  267. Unsupported("--loop");
  268. } else if (strcmp(name, "--trace-path") == 0 || strcmp(name, "--trace-path-append") == 0) {
  269. ++i;
  270. if (i >= argc) {
  271. std::cerr << "Missing value for argument --trace-path" << std::endl;
  272. exit(2);
  273. } else if (!result.TracePath.empty()) {
  274. std::cerr << "Multiple --trace-path or --trace-path-append given" << std::endl;
  275. exit(2);
  276. }
  277. result.TracePath = argv[i];
  278. result.AppendTrace = strcmp(name, "--trace-path-append") == 0;
  279. } else if (strcmp(name, "--list-path") == 0) {
  280. ++i;
  281. if (i >= argc) {
  282. std::cerr << "Missing value for argument --list-path" << std::endl;
  283. exit(2);
  284. }
  285. result.ListPath = argv[i];
  286. } else if (strcmp(name, "--test-param") == 0) {
  287. ++i;
  288. if (i >= argc) {
  289. std::cerr << "Missing value for argument --test-param" << std::endl;
  290. exit(2);
  291. }
  292. auto [key, value] = ParseParam(argv[i]);
  293. Singleton<NPrivate::TTestEnv>()->AddTestParam(key, value);
  294. } else if (StartsWith(name, "--")) {
  295. Unknown(name);
  296. } else if (*name == '-') {
  297. auto [suite, test] = ParseName(name + 1);
  298. filtersNeg << filterNegSep << suite << "." << test;
  299. filterNegSep = ":";
  300. } else if (*name == '+') {
  301. auto [suite, test] = ParseName(name + 1);
  302. filtersPos << filterPosSep << suite << "." << test;
  303. filterPosSep = ":";
  304. } else {
  305. auto [suite, test] = ParseName(name);
  306. filtersPos << filterPosSep << suite << "." << test;
  307. filterPosSep = ":";
  308. }
  309. }
  310. if (!filtersPos.str().empty() || !filtersNeg.str().empty()) {
  311. result.Filter = filtersPos.str();
  312. if (!filtersNeg.str().empty()) {
  313. result.Filter += "-";
  314. result.Filter += filtersNeg.str();
  315. }
  316. }
  317. // Main-like functions need a null sentinel at the end of `argv' argument.
  318. // This sentinel is not counted in `argc' argument.
  319. result.GtestArgv.push_back(nullptr);
  320. result.GtestArgc = static_cast<int>(result.GtestArgv.size()) - 1;
  321. return result;
  322. }
  323. void NGTest::ListTests(int listLevel, const std::string& listPath) {
  324. // NOTE: do not use `std::endl`, use `\n`; `std::endl` produces `\r\n`s on windows,
  325. // and ya make does not handle them well.
  326. if (listLevel > 0) {
  327. std::ostream* listOut = &std::cout;
  328. std::ofstream listFile;
  329. if (!listPath.empty()) {
  330. listFile.open(listPath, std::ios::out | std::ios::binary);
  331. if (!listFile.is_open()) {
  332. std::cerr << "Failed to open file " << listPath << " for write" << std::endl;
  333. exit(2);
  334. }
  335. listOut = &listFile;
  336. }
  337. for (int i = 0; i < testing::UnitTest::GetInstance()->total_test_suite_count(); ++i) {
  338. auto suite = testing::UnitTest::GetInstance()->GetTestSuite(i);
  339. if (listLevel > 1) {
  340. for (int j = 0; j < suite->total_test_count(); ++j) {
  341. auto test = suite->GetTestInfo(j);
  342. (*listOut) << suite->name() << "::" << test->name() << "\n";
  343. }
  344. } else {
  345. (*listOut) << suite->name() << "\n";
  346. }
  347. }
  348. (*listOut) << std::flush;
  349. exit(0);
  350. }
  351. }
  352. void NGTest::UnsetDefaultReporter() {
  353. ::testing::TestEventListeners& listeners = ::testing::UnitTest::GetInstance()->listeners();
  354. delete listeners.Release(listeners.default_result_printer());
  355. }
  356. void NGTest::SetTraceReporter(std::ostream* traceFile) {
  357. ::testing::TestEventListeners& listeners = ::testing::UnitTest::GetInstance()->listeners();
  358. listeners.Append(new TTraceWriter{traceFile});
  359. }