test-runner.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /*
  2. * Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Variant.h>
  7. #include <LibCore/Directory.h>
  8. #include <LibCore/Process.h>
  9. #include <LibCore/System.h>
  10. #include <LibDiff/Format.h>
  11. #include <LibDiff/Generator.h>
  12. #include <LibFileSystem/FileSystem.h>
  13. #include <LibFileSystem/TempFile.h>
  14. #include <LibTest/TestCase.h>
  15. struct TestDescription {
  16. struct Flag {
  17. StringView name;
  18. bool dump_ast = false;
  19. bool dump_cfg = false;
  20. };
  21. Vector<StringView> sources;
  22. Vector<Flag> flags;
  23. };
  24. constexpr StringView stderr_capture_filename = "stderr"sv;
  25. constexpr StringView compiler_binary_name = "JSSpecCompiler"sv;
  26. constexpr StringView relative_path_to_test = "Tests"sv;
  27. constexpr TestDescription::Flag always_dump_all = {
  28. .name = "all"sv,
  29. .dump_ast = true,
  30. .dump_cfg = true
  31. };
  32. constexpr TestDescription::Flag dump_after_frontend = {
  33. .name = "reference-resolving"sv,
  34. .dump_ast = true,
  35. .dump_cfg = false
  36. };
  37. const Array regression_tests = {
  38. TestDescription {
  39. .sources = { "simple.cpp"sv },
  40. .flags = { always_dump_all },
  41. },
  42. TestDescription {
  43. .sources = {
  44. "spec-no-new-line-after-dot.xml"sv,
  45. "spec-single-function-simple.xml"sv,
  46. },
  47. .flags = { dump_after_frontend },
  48. }
  49. };
  50. static const LexicalPath path_to_compiler_binary = [] {
  51. auto path_to_self = LexicalPath(MUST(Core::System::current_executable_path())).parent();
  52. return LexicalPath::join(path_to_self.string(), compiler_binary_name);
  53. }();
  54. static const LexicalPath path_to_tests_directory { relative_path_to_test };
  55. Vector<ByteString> build_command_line_arguments(LexicalPath const& test_source, TestDescription const& description)
  56. {
  57. Vector<ByteString> result;
  58. StringBuilder dump_ast_option;
  59. StringBuilder dump_cfg_option;
  60. for (auto const& flag : description.flags) {
  61. if (flag.dump_ast) {
  62. if (!dump_ast_option.is_empty())
  63. dump_ast_option.append(","sv);
  64. dump_ast_option.append(flag.name);
  65. }
  66. if (flag.dump_cfg) {
  67. if (!dump_cfg_option.is_empty())
  68. dump_cfg_option.append(","sv);
  69. dump_cfg_option.append(flag.name);
  70. }
  71. }
  72. if (!dump_ast_option.is_empty())
  73. result.append(ByteString::formatted("--dump-ast={}", dump_ast_option.string_view()));
  74. if (!dump_cfg_option.is_empty())
  75. result.append(ByteString::formatted("--dump-cfg={}", dump_cfg_option.string_view()));
  76. if (test_source.has_extension(".cpp"sv))
  77. result.append("-xc++"sv);
  78. result.append(test_source.string());
  79. return result;
  80. }
  81. ErrorOr<ByteBuffer> read(LexicalPath const& path)
  82. {
  83. auto file = TRY(Core::File::open(path.string(), Core::File::OpenMode::Read));
  84. return MUST(file->read_until_eof());
  85. }
  86. void check_expectations(LexicalPath const& path_to_expectation, LexicalPath const& path_to_captured_output, bool should_update_expectations)
  87. {
  88. struct PathPair {
  89. LexicalPath expectation;
  90. LexicalPath result;
  91. };
  92. Vector<PathPair> file_pairs_to_check;
  93. file_pairs_to_check.append({
  94. .expectation = path_to_expectation,
  95. .result = path_to_captured_output,
  96. });
  97. auto out = MUST(Core::File::standard_error());
  98. for (auto const& [expectation_path, result_path] : file_pairs_to_check) {
  99. auto result_content = read(result_path);
  100. if (should_update_expectations && !result_content.is_error()) {
  101. using namespace FileSystem;
  102. MUST(copy_file_or_directory(expectation_path.string(), result_path.string(),
  103. RecursionMode::Disallowed, LinkMode::Disallowed, AddDuplicateFileMarker::No));
  104. }
  105. auto expectation = read(expectation_path);
  106. bool read_successfully = !(expectation.is_error() || result_content.is_error());
  107. EXPECT(read_successfully);
  108. if (read_successfully) {
  109. bool are_equal = expectation.value() == result_content.value();
  110. EXPECT(are_equal);
  111. if (!are_equal) {
  112. dbgln("Files {} and {} do not match!", expectation_path.string(), result_path.string());
  113. auto maybe_diff = Diff::from_text(expectation.value(), result_content.value());
  114. if (!maybe_diff.is_error()) {
  115. for (auto const& hunk : maybe_diff.value())
  116. MUST(Diff::write_unified(hunk, *out, Diff::ColorOutput::Yes));
  117. }
  118. }
  119. }
  120. }
  121. }
  122. TEST_CASE(test_regression)
  123. {
  124. auto* update_expectations_env = getenv("JSSC_UPDATE_EXPECTATIONS");
  125. bool should_update_expectations = false;
  126. if (update_expectations_env != nullptr && strcmp(update_expectations_env, "1") == 0)
  127. should_update_expectations = true;
  128. auto temp_directory = MUST(FileSystem::TempFile::create_temp_directory());
  129. auto path_to_captured_stderr = LexicalPath::join(temp_directory->path(), stderr_capture_filename);
  130. for (auto const& test_description : regression_tests) {
  131. for (auto const& source : test_description.sources) {
  132. dbgln("Running {}...", source);
  133. auto path_to_test = LexicalPath::join(path_to_tests_directory.string(), source);
  134. auto path_to_expectation = LexicalPath::join(path_to_tests_directory.string(), ByteString::formatted("{}.expectation", source));
  135. auto process = MUST(Core::Process::spawn({
  136. .executable = path_to_compiler_binary.string(),
  137. .arguments = build_command_line_arguments(path_to_test, test_description),
  138. .file_actions = {
  139. Core::FileAction::OpenFile {
  140. .path = path_to_captured_stderr.string(),
  141. .mode = Core::File::OpenMode::Write,
  142. .fd = STDERR_FILENO,
  143. },
  144. },
  145. }));
  146. bool exited_with_code_0 = MUST(process.wait_for_termination());
  147. EXPECT(exited_with_code_0);
  148. if (!exited_with_code_0) {
  149. auto captured_output = read(path_to_captured_stderr);
  150. if (!captured_output.is_error()) {
  151. StringView stderr_output_view = captured_output.value();
  152. dbgln("Compiler invocation failed. Captured output:\n{}", stderr_output_view);
  153. }
  154. } else {
  155. check_expectations(path_to_expectation, path_to_captured_stderr, should_update_expectations);
  156. }
  157. }
  158. }
  159. }