| author | |
| committer | |
| log | 4470c3b285523251143a844ca7821d457fdae8af |
| tree | 7c02d9a545a90efb3c8aacb284b5a33753539eaa |
| parent | 0497aa7eeb1b2ee00840b66313dc49eb65ffac0d |
| signature | Commit is signed but in an unrecognized format. |
7 files changed, 548 insertions(+), 437 deletions(-)
build.zig+36-19| ... | @@ -30,6 +30,7 @@ const ArtifactGroup = struct { | ... | @@ -30,6 +30,7 @@ const ArtifactGroup = struct { |
| 30 | transform_cli: *Step.Compile, | 30 | transform_cli: *Step.Compile, |
| 31 | loader_cli: *Step.Compile, | 31 | loader_cli: *Step.Compile, |
| 32 | 32 | ||
| 33 | cli_mod: *Module, | ||
| 33 | core_mod: *Module, | 34 | core_mod: *Module, |
| 34 | loader_mod: *Module, | 35 | loader_mod: *Module, |
| 35 | config_mod: *Module, | 36 | config_mod: *Module, |
| ... | @@ -59,14 +60,6 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { | ... | @@ -59,14 +60,6 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { |
| 59 | break :config_mod config.createModule(); | 60 | break :config_mod config.createModule(); |
| 60 | }; | 61 | }; |
| 61 | 62 | ||
| 62 | // Cli helper | ||
| 63 | const cli_mod = b.createModule(.{ | ||
| 64 | .root_source_file = b.path("src/Cli.zig"), | ||
| 65 | .optimize = options.optimize, | ||
| 66 | .target = options.target, | ||
| 67 | .strip = false, | ||
| 68 | }); | ||
| 69 | |||
| 70 | // Core | 63 | // Core |
| 71 | const core_mod = b.createModule(.{ | 64 | const core_mod = b.createModule(.{ |
| 72 | .root_source_file = b.path("src/core/mod.zig"), | 65 | .root_source_file = b.path("src/core/mod.zig"), |
| ... | @@ -103,10 +96,18 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { | ... | @@ -103,10 +96,18 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { |
| 103 | const core_mod_tests = createModuleTests(core_mod, options); | 96 | const core_mod_tests = createModuleTests(core_mod, options); |
| 104 | const loader_mod_tests = createModuleTests(loader_mod, options); | 97 | const loader_mod_tests = createModuleTests(loader_mod, options); |
| 105 | 98 | ||
| 99 | // Cli helper module | ||
| 100 | const cli_mod = b.createModule(.{ | ||
| 101 | .root_source_file = b.path("src/Cli.zig"), | ||
| 102 | .optimize = options.optimize, | ||
| 103 | .target = options.target, | ||
| 104 | .strip = false, | ||
| 105 | }); | ||
| 106 | |||
| 106 | // Compiler test CLI | 107 | // Compiler test CLI |
| 107 | const compiler_tests = b.addExecutable(.{ | 108 | const compiler_tests = b.addExecutable(.{ |
| 108 | .name = "compiler_tests", | 109 | .name = "compiler_tests", |
| 109 | .root_source_file = b.path("test/compiler.zig"), | 110 | .root_source_file = b.path("test/compiler_tests.zig"), |
| 110 | .target = options.target, | 111 | .target = options.target, |
| 111 | .optimize = options.optimize, | 112 | .optimize = options.optimize, |
| 112 | .use_lld = use_lld, | 113 | .use_lld = use_lld, |
| ... | @@ -134,6 +135,7 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { | ... | @@ -134,6 +135,7 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { |
| 134 | if (options.config.have_embedded_quickjs) { | 135 | if (options.config.have_embedded_quickjs) { |
| 135 | cli.root_module.addImport("quickjs", quickjs.module("quickjs-libc")); | 136 | cli.root_module.addImport("quickjs", quickjs.module("quickjs-libc")); |
| 136 | } | 137 | } |
| 138 | cli.root_module.addImport("Cli", cli_mod); | ||
| 137 | 139 | ||
| 138 | // Compiler Dev CLI | 140 | // Compiler Dev CLI |
| 139 | const compiler_cli = b.addExecutable(.{ | 141 | const compiler_cli = b.addExecutable(.{ |
| ... | @@ -151,6 +153,7 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { | ... | @@ -151,6 +153,7 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { |
| 151 | if (options.config.have_embedded_quickjs) { | 153 | if (options.config.have_embedded_quickjs) { |
| 152 | cli.root_module.addImport("quickjs", quickjs.module("quickjs-libc")); | 154 | cli.root_module.addImport("quickjs", quickjs.module("quickjs-libc")); |
| 153 | } | 155 | } |
| 156 | compiler_cli.root_module.addImport("Cli", cli_mod); | ||
| 154 | 157 | ||
| 155 | const format_cli = b.addExecutable(.{ | 158 | const format_cli = b.addExecutable(.{ |
| 156 | .name = "format", | 159 | .name = "format", |
| ... | @@ -165,6 +168,7 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { | ... | @@ -165,6 +168,7 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { |
| 165 | format_cli.root_module.addImport("format", format_mod); | 168 | format_cli.root_module.addImport("format", format_mod); |
| 166 | format_cli.root_module.addImport("config", config_mod); | 169 | format_cli.root_module.addImport("config", config_mod); |
| 167 | format_cli.root_module.addImport("tracer", tracer.module("tracer")); | 170 | format_cli.root_module.addImport("tracer", tracer.module("tracer")); |
| 171 | format_cli.root_module.addImport("Cli", cli_mod); | ||
| 168 | 172 | ||
| 169 | const loader_cli = b.addExecutable(.{ | 173 | const loader_cli = b.addExecutable(.{ |
| 170 | .name = "loader", | 174 | .name = "loader", |
| ... | @@ -175,9 +179,10 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { | ... | @@ -175,9 +179,10 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { |
| 175 | .use_llvm = use_llvm, | 179 | .use_llvm = use_llvm, |
| 176 | .single_threaded = true, | 180 | .single_threaded = true, |
| 177 | }); | 181 | }); |
| 178 | format_cli.root_module.addImport("loader", loader_mod); | 182 | loader_cli.root_module.addImport("loader", loader_mod); |
| 179 | format_cli.root_module.addImport("config", config_mod); | 183 | loader_cli.root_module.addImport("config", config_mod); |
| 180 | format_cli.root_module.addImport("tracer", tracer.module("tracer")); | 184 | loader_cli.root_module.addImport("tracer", tracer.module("tracer")); |
| 185 | loader_cli.root_module.addImport("Cli", cli_mod); | ||
| 181 | 186 | ||
| 182 | const transform_cli = b.addExecutable(.{ | 187 | const transform_cli = b.addExecutable(.{ |
| 183 | .name = "transform", | 188 | .name = "transform", |
| ... | @@ -191,12 +196,14 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { | ... | @@ -191,12 +196,14 @@ fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup { |
| 191 | transform_cli.root_module.addImport("core", core_mod); | 196 | transform_cli.root_module.addImport("core", core_mod); |
| 192 | transform_cli.root_module.addImport("config", config_mod); | 197 | transform_cli.root_module.addImport("config", config_mod); |
| 193 | transform_cli.root_module.addImport("tracer", tracer.module("tracer")); | 198 | transform_cli.root_module.addImport("tracer", tracer.module("tracer")); |
| 199 | transform_cli.root_module.addImport("Cli", cli_mod); | ||
| 194 | 200 | ||
| 195 | return .{ | 201 | return .{ |
| 196 | .build = b, | 202 | .build = b, |
| 197 | .opts = options, | 203 | .opts = options, |
| 198 | 204 | ||
| 199 | .cli = cli, | 205 | .cli = cli, |
| 206 | .cli_mod = cli_mod, | ||
| 200 | .compiler_cli = compiler_cli, | 207 | .compiler_cli = compiler_cli, |
| 201 | .format_cli = format_cli, | 208 | .format_cli = format_cli, |
| 202 | .loader_cli = loader_cli, | 209 | .loader_cli = loader_cli, |
| ... | @@ -399,13 +406,14 @@ pub fn build(b: *Build) !void { | ... | @@ -399,13 +406,14 @@ pub fn build(b: *Build) !void { |
| 399 | 406 | ||
| 400 | b.installArtifact(main.cli); | 407 | b.installArtifact(main.cli); |
| 401 | 408 | ||
| 402 | inline for (.{ | 409 | const run_templates = .{ |
| 403 | .{ main.cli, "run" }, | 410 | .{ main.cli, "run" }, |
| 404 | .{ minimal.compiler_cli, "compiler" }, | 411 | .{ minimal.compiler_cli, "compiler" }, |
| 405 | .{ minimal.format_cli, "format" }, | 412 | .{ minimal.format_cli, "format" }, |
| 406 | .{ minimal.loader_cli, "loader" }, | 413 | .{ minimal.loader_cli, "loader" }, |
| 407 | .{ minimal.transform_cli, "transform" }, | 414 | .{ minimal.transform_cli, "transform" }, |
| 408 | }) |entry| { | 415 | }; |
| 416 | inline for (run_templates) |entry| { | ||
| 409 | const artifact, const name = entry; | 417 | const artifact, const name = entry; |
| 410 | 418 | ||
| 411 | const run_cmd = b.addRunArtifact(artifact); | 419 | const run_cmd = b.addRunArtifact(artifact); |
| ... | @@ -425,8 +433,12 @@ pub fn build(b: *Build) !void { | ... | @@ -425,8 +433,12 @@ pub fn build(b: *Build) !void { |
| 425 | }) |entry| { | 433 | }) |entry| { |
| 426 | const artifact, const name, const description = entry; | 434 | const artifact, const name, const description = entry; |
| 427 | const run_cmd = b.addRunArtifact(artifact); | 435 | const run_cmd = b.addRunArtifact(artifact); |
| 428 | if (b.args) |args| { | 436 | if (artifact == main.compiler_tests) { |
| 429 | run_cmd.addArgs(args); | 437 | if (b.args) |args| { |
| 438 | run_cmd.addArgs(args); | ||
| 439 | } else { | ||
| 440 | run_cmd.enableTestRunnerMode(); | ||
| 441 | } | ||
| 430 | } | 442 | } |
| 431 | const run_step = b.step("test-" ++ name, "Test " ++ description); | 443 | const run_step = b.step("test-" ++ name, "Test " ++ description); |
| 432 | run_step.dependOn(&run_cmd.step); | 444 | run_step.dependOn(&run_cmd.step); |
| ... | @@ -550,9 +562,14 @@ pub fn build(b: *Build) !void { | ... | @@ -550,9 +562,14 @@ pub fn build(b: *Build) !void { |
| 550 | { | 562 | { |
| 551 | _ = b.top_level_steps.swapRemove("uninstall"); // this step does not work | 563 | _ = b.top_level_steps.swapRemove("uninstall"); // this step does not work |
| 552 | 564 | ||
| 553 | const all_step = b.step("all", "Execute all of the above"); | 565 | const all_step = b.step("all", "Build everything (used by CI)"); |
| 554 | const steps = b.top_level_steps.values(); | 566 | all_step.dependOn(&b.top_level_steps.get("install").?.step); |
| 555 | for (steps[0 .. steps.len - 1]) |step| all_step.dependOn(&step.step); | 567 | all_step.dependOn(&b.top_level_steps.get("package").?.step); |
| 568 | all_step.dependOn(&b.top_level_steps.get("test").?.step); | ||
| 569 | all_step.dependOn(&b.top_level_steps.get("manual").?.step); | ||
| 570 | inline for (run_templates) |run| { | ||
| 571 | all_step.dependOn(&run.@"0".step); | ||
| 572 | } | ||
| 556 | } | 573 | } |
| 557 | } | 574 | } |
| 558 | 575 |
ci+1-1| ... | @@ -10,7 +10,7 @@ commit_short=$(echo "$commit" | cut -c -9) | ... | @@ -10,7 +10,7 @@ commit_short=$(echo "$commit" | cut -c -9) |
| 10 | 10 | ||
| 11 | typos | 11 | typos |
| 12 | dprint check | 12 | dprint check |
| 13 | ./build test package --summary all | 13 | ./build all --summary new |
| 14 | 14 | ||
| 15 | if [ -n "$BUILDKITE" ]; then | 15 | if [ -n "$BUILDKITE" ]; then |
| 16 | pushd zig-out/package | 16 | pushd zig-out/package |
src/cli_compiler.zig+1-1| ... | @@ -130,7 +130,7 @@ pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)) | ... | @@ -130,7 +130,7 @@ pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)) |
| 130 | 130 | ||
| 131 | const std = @import("std"); | 131 | const std = @import("std"); |
| 132 | const builtin = @import("builtin"); | 132 | const builtin = @import("builtin"); |
| 133 | const Cli = @import("Cli.zig"); | 133 | const Cli = @import("Cli"); |
| 134 | 134 | ||
| 135 | fn dumpState(comp: *const Comp, dump_path: []const u8) !void { | 135 | fn dumpState(comp: *const Comp, dump_path: []const u8) !void { |
| 136 | const file = try std.fs.cwd().createFile(dump_path, .{}); | 136 | const file = try std.fs.cwd().createFile(dump_path, .{}); |
src/cli_transform.zig+1-1| ... | @@ -94,4 +94,4 @@ pub fn main() !u8 { | ... | @@ -94,4 +94,4 @@ pub fn main() !u8 { |
| 94 | 94 | ||
| 95 | const std = @import("std"); | 95 | const std = @import("std"); |
| 96 | const core = @import("core"); | 96 | const core = @import("core"); |
| 97 | const Cli = @import("Cli.zig"); | 97 | const Cli = @import("Cli"); |
src/main.zig+1-1| ... | @@ -434,7 +434,7 @@ const tracer = @import("tracer"); | ... | @@ -434,7 +434,7 @@ const tracer = @import("tracer"); |
| 434 | pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)); | 434 | pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)); |
| 435 | 435 | ||
| 436 | const builtin = @import("builtin"); | 436 | const builtin = @import("builtin"); |
| 437 | const Cli = @import("Cli.zig"); | 437 | const Cli = @import("Cli"); |
| 438 | 438 | ||
| 439 | fn dumpState(comp: *const Comp, dump_path: []const u8) !void { | 439 | fn dumpState(comp: *const Comp, dump_path: []const u8) !void { |
| 440 | const file = try std.fs.cwd().createFile(dump_path, .{}); | 440 | const file = try std.fs.cwd().createFile(dump_path, .{}); |
test/compiler.zig deleted-414| ... | @@ -1,414 +0,0 @@ | ||
| 1 | //! This program executes compiler tests found in this directory. Additionally, | ||
| 2 | //! it can be run as a standalone program to compile and execute a single test. | ||
| 3 | //! | ||
| 4 | //! See the "Testing" section in `architecture.md` for more information on the | ||
| 5 | //! structure of these tests, and `zig build test-compiler -- --help` for more | ||
| 6 | //! details on the arguments this supports. | ||
| 7 | |||
| 8 | const cli_config: Cli = .{ | ||
| 9 | .options = &.{ | ||
| 10 | .{ .name = "--test-root", .desc = "root directory of all tests", .kind = .string }, | ||
| 11 | .{ .name = "--all-logs", .desc = "Always print compiler logs" }, | ||
| 12 | .{ .name = "--no-threads", .desc = "Avoid using a thread pool" }, | ||
| 13 | .{ .name = "--dump-ir", .desc = "Dump intermediate representations" }, | ||
| 14 | }, | ||
| 15 | .positionals = .single, | ||
| 16 | .help = .{ | ||
| 17 | .usage = "[options...] [test filter]", | ||
| 18 | }, | ||
| 19 | }; | ||
| 20 | |||
| 21 | /// This file is included for all behavior tests for the JavaScript backend. | ||
| 22 | const helpers = | ||
| 23 | \\(function (self) { | ||
| 24 | \\ function assert(condition) { | ||
| 25 | \\ if (!condition) { | ||
| 26 | \\ throw new Error("Assertion failure"); | ||
| 27 | \\ } | ||
| 28 | \\ } | ||
| 29 | \\ | ||
| 30 | \\ function assertEqual(actual, expected) { | ||
| 31 | \\ if (actual !== expected) { | ||
| 32 | \\ var err; | ||
| 33 | \\ try { | ||
| 34 | \\ err = new Error("Expected " + expected + ", but got " + actual); | ||
| 35 | \\ } catch (e) { | ||
| 36 | \\ err = new Error("Assertion failure"); | ||
| 37 | \\ } | ||
| 38 | \\ throw err; | ||
| 39 | \\ } | ||
| 40 | \\ } | ||
| 41 | \\ | ||
| 42 | \\ function print(str) { | ||
| 43 | \\ console.log(str); | ||
| 44 | \\ } | ||
| 45 | \\ | ||
| 46 | \\ self.assert = assert; | ||
| 47 | \\ self.assertEqual = assertEqual; | ||
| 48 | \\ self.print = print; | ||
| 49 | \\})(typeof globalThis !== "undefined" ? globalThis : this); | ||
| 50 | \\ | ||
| 51 | ; | ||
| 52 | |||
| 53 | const CaseOptions = struct { | ||
| 54 | dump_ir: bool = false, | ||
| 55 | sync: bool = false, | ||
| 56 | }; | ||
| 57 | |||
| 58 | const Case = struct { | ||
| 59 | path: []const u8, | ||
| 60 | display: []const u8, | ||
| 61 | success: bool = false, | ||
| 62 | logs: []const u8 = "", | ||
| 63 | failure: ?struct { | ||
| 64 | err: anyerror, | ||
| 65 | stack: core.StoredTrace, | ||
| 66 | } = null, | ||
| 67 | |||
| 68 | const Kind = enum { | ||
| 69 | behavior, | ||
| 70 | analysis, | ||
| 71 | transform_error, | ||
| 72 | }; | ||
| 73 | }; | ||
| 74 | |||
| 75 | fn runTestCase( | ||
| 76 | backing_allocator: std.mem.Allocator, | ||
| 77 | file_path: []const u8, | ||
| 78 | case_options: CaseOptions, | ||
| 79 | out: std.io.AnyWriter, | ||
| 80 | ) !void { | ||
| 81 | var gpa = std.heap.GeneralPurposeAllocator(.{ | ||
| 82 | .stack_trace_frames = 100, | ||
| 83 | }){ .backing_allocator = backing_allocator }; | ||
| 84 | const alloc = gpa.allocator(); | ||
| 85 | |||
| 86 | var backend = core.Js{}; | ||
| 87 | var comp = try core.Comp.init(.{ | ||
| 88 | .gpa = tracer.tracingAllocator(&alloc), | ||
| 89 | .options = .{ .dump_ir = case_options.dump_ir }, | ||
| 90 | .backend = .{ .js = &backend }, | ||
| 91 | .out = .{ .writer = out, .tty = .escape_codes }, | ||
| 92 | }); | ||
| 93 | |||
| 94 | const main_file = try comp.getFile(file_path); | ||
| 95 | |||
| 96 | var fail_report: core.Report = undefined; | ||
| 97 | const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) { | ||
| 98 | error.TransformFail => { | ||
| 99 | try out.print("Transform failed:\n", .{}); | ||
| 100 | try fail_report.dump(.{ .comp = .{ .comp = &comp, .file = main_file } }, comp.out); | ||
| 101 | return error.TransformFail; | ||
| 102 | }, | ||
| 103 | error.ParseFail => { | ||
| 104 | // Already printed. | ||
| 105 | return error.ParseFail; | ||
| 106 | }, | ||
| 107 | else => return err, | ||
| 108 | }; | ||
| 109 | |||
| 110 | if (case_options.dump_ir) { | ||
| 111 | try teg.dumpToWriter(out, .escape_codes); | ||
| 112 | } | ||
| 113 | |||
| 114 | const entry_point = try comp.enqueueEntryModule(main_file); | ||
| 115 | try comp.runAllJobs(); | ||
| 116 | |||
| 117 | if (comp.temp_flag_had_errors) | ||
| 118 | return error.SemaErrors; | ||
| 119 | |||
| 120 | if (teg.report.warnings.len > 0) | ||
| 121 | return error.TegWarnings; | ||
| 122 | |||
| 123 | var js_out: std.ArrayListUnmanaged(u8) = .{}; | ||
| 124 | defer js_out.deinit(alloc); | ||
| 125 | |||
| 126 | try js_out.appendSlice(alloc, helpers); | ||
| 127 | try js_out.appendSlice(alloc, "\n\n(function() {\n\n"); | ||
| 128 | |||
| 129 | const start_i = js_out.items.len; | ||
| 130 | try backend.link(&comp, entry_point, js_out.writer(alloc).any()); | ||
| 131 | |||
| 132 | try out.writeAll("--- JS CODE ---\n"); | ||
| 133 | try out.writeAll(js_out.items[start_i..]); | ||
| 134 | try out.writeAll("--- JS CODE ---\n"); | ||
| 135 | |||
| 136 | if (isOnlyComments(js_out.items[start_i..])) { | ||
| 137 | try out.writeAll("Behavior tests should output code!"); | ||
| 138 | return error.EmptyFile; | ||
| 139 | } | ||
| 140 | |||
| 141 | try js_out.appendSlice(alloc, "\n\n})();"); | ||
| 142 | try js_out.append(alloc, 0); | ||
| 143 | |||
| 144 | const runtime = qjs.JS_NewRuntime() orelse | ||
| 145 | return error.UnexpectedError; | ||
| 146 | defer qjs.JS_FreeRuntime(runtime); | ||
| 147 | const js_ctx = qjs.JS_NewContext(runtime) orelse | ||
| 148 | return error.UnexpectedError; | ||
| 149 | defer qjs.JS_FreeContext(js_ctx); | ||
| 150 | |||
| 151 | const result = qjs.JS_Eval( | ||
| 152 | js_ctx, | ||
| 153 | js_out.items.ptr, | ||
| 154 | js_out.items.len - 1, | ||
| 155 | "test.js", | ||
| 156 | qjs.JS_EVAL_TYPE_GLOBAL, | ||
| 157 | ); | ||
| 158 | defer qjs.JS_FreeValue(js_ctx, result); | ||
| 159 | |||
| 160 | if (qjs.JS_IsException(result) != 0) { | ||
| 161 | const exception = qjs.JS_GetException(js_ctx); | ||
| 162 | defer qjs.JS_FreeValue(js_ctx, exception); | ||
| 163 | |||
| 164 | const exception_str = qjs.JS_ToCString(js_ctx, exception); | ||
| 165 | defer qjs.JS_FreeCString(js_ctx, exception_str); | ||
| 166 | |||
| 167 | const slice = std.mem.span(exception_str); | ||
| 168 | |||
| 169 | try out.print("Exception: {s}\n", .{slice}); | ||
| 170 | |||
| 171 | return error.RuntimeFailed; | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | fn isOnlyComments(input: []const u8) bool { | ||
| 176 | var rest = input; | ||
| 177 | while (rest.len > 0) { | ||
| 178 | if (rest.len == 1) return false; | ||
| 179 | if (rest[0] == '/' and rest[1] == '/') { | ||
| 180 | rest = rest[std.mem.indexOfScalar(u8, rest, '\n') orelse return true ..]; | ||
| 181 | } else { | ||
| 182 | return false; | ||
| 183 | } | ||
| 184 | } | ||
| 185 | return true; | ||
| 186 | } | ||
| 187 | |||
| 188 | const threadlocal_state = struct { | ||
| 189 | threadlocal var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); | ||
| 190 | }; | ||
| 191 | |||
| 192 | pub fn runTestCaseWrap( | ||
| 193 | gpa: std.mem.Allocator, | ||
| 194 | case: *Case, | ||
| 195 | case_options: CaseOptions, | ||
| 196 | progress: std.Progress.Node, | ||
| 197 | ) void { | ||
| 198 | const node = progress.start(case.display, 0); | ||
| 199 | defer node.end(); | ||
| 200 | |||
| 201 | const alloc = threadlocal_state.arena.allocator(); | ||
| 202 | defer _ = threadlocal_state.arena.reset(.{ .retain_with_limit = std.mem.page_size * 8 }); | ||
| 203 | |||
| 204 | var logs: std.ArrayListUnmanaged(u8) = .{}; | ||
| 205 | defer logs.deinit(alloc); | ||
| 206 | |||
| 207 | defer case.logs = gpa.dupe(u8, logs.items) catch | ||
| 208 | "[logs failed to allocate]"; | ||
| 209 | |||
| 210 | runTestCase( | ||
| 211 | alloc, | ||
| 212 | case.path, | ||
| 213 | case_options, | ||
| 214 | if (case_options.sync) | ||
| 215 | std.io.getStdErr().writer().any() | ||
| 216 | else | ||
| 217 | logs.writer(alloc).any(), | ||
| 218 | ) catch |err| { | ||
| 219 | const stack = switch (err) { | ||
| 220 | error.TransformFail, | ||
| 221 | error.ParseFail, | ||
| 222 | error.TegWarnings, | ||
| 223 | error.SemaErrors, | ||
| 224 | error.AnalysisFail, | ||
| 225 | => core.StoredTrace.empty, | ||
| 226 | |||
| 227 | else => core.StoredTrace.from(@errorReturnTrace()), | ||
| 228 | }; | ||
| 229 | |||
| 230 | case.failure = .{ | ||
| 231 | .err = err, | ||
| 232 | .stack = stack, | ||
| 233 | }; | ||
| 234 | |||
| 235 | return; | ||
| 236 | }; | ||
| 237 | |||
| 238 | case.success = true; | ||
| 239 | } | ||
| 240 | |||
| 241 | pub fn main() !void { | ||
| 242 | // unicode props is asserted to be initialized before core.Tokenizer is used | ||
| 243 | // this will result in a static page allocation, which is never cleaned up. | ||
| 244 | try core.unicode_props.init(); | ||
| 245 | var gpa = std.heap.GeneralPurposeAllocator(.{ | ||
| 246 | .stack_trace_frames = 100, | ||
| 247 | .thread_safe = true, | ||
| 248 | }){}; | ||
| 249 | |||
| 250 | defer _ = gpa.deinit(); | ||
| 251 | const alloc = gpa.allocator(); | ||
| 252 | |||
| 253 | const program_name = "zig build test-compiler --"; | ||
| 254 | _, const args = try Cli.programArgsAlloc(alloc, ""); | ||
| 255 | defer Cli.programArgsFree(alloc, args); | ||
| 256 | |||
| 257 | const opts = cli_config.parse(program_name, args); | ||
| 258 | |||
| 259 | const test_root = opts.test_root orelse "test/behavior"; | ||
| 260 | const case_options: CaseOptions = .{ | ||
| 261 | .dump_ir = opts.dump_ir orelse false, | ||
| 262 | .sync = opts.no_threads orelse false, | ||
| 263 | }; | ||
| 264 | const all_logs = opts.all_logs orelse false; | ||
| 265 | const filter: ?[]const u8 = opts.positionals; | ||
| 266 | |||
| 267 | const some_logs = all_logs or args.len > 1; | ||
| 268 | |||
| 269 | const root = try std.fs.cwd().openDir(test_root, .{ .iterate = true }); | ||
| 270 | |||
| 271 | var test_cases: std.ArrayListUnmanaged(Case) = .{}; | ||
| 272 | defer test_cases.deinit(alloc); | ||
| 273 | defer for (test_cases.items) |test_case| { | ||
| 274 | alloc.free(test_case.path); | ||
| 275 | }; | ||
| 276 | |||
| 277 | var path_buf: std.BoundedArray(u8, std.fs.max_path_bytes) = .{}; | ||
| 278 | path_buf.len = @intCast((try std.os.getFdPath(root.fd, &path_buf.buffer)).len); | ||
| 279 | try iterateTestCases(alloc, root, path_buf.len, &path_buf, &test_cases, filter); | ||
| 280 | |||
| 281 | if (test_cases.items.len == 0) { | ||
| 282 | if (filter) |f| { | ||
| 283 | std.debug.print("No test cases match filter \"{s}\"\n", .{f}); | ||
| 284 | } else { | ||
| 285 | std.debug.print("No test cases in root \"{s}\"", .{test_root}); | ||
| 286 | } | ||
| 287 | std.process.exit(1); | ||
| 288 | } | ||
| 289 | |||
| 290 | const progress = std.Progress.start(.{ .root_name = "Behavior Tests" }); | ||
| 291 | defer progress.end(); | ||
| 292 | progress.setEstimatedTotalItems(test_cases.items.len); | ||
| 293 | |||
| 294 | var wg: std.Thread.WaitGroup = .{}; | ||
| 295 | |||
| 296 | var pool: std.Thread.Pool = undefined; | ||
| 297 | if (!case_options.sync) try pool.init(.{ .allocator = alloc }); | ||
| 298 | defer if (!case_options.sync) pool.deinit(); | ||
| 299 | |||
| 300 | for (test_cases.items) |*test_case| { | ||
| 301 | const call_args = .{ | ||
| 302 | alloc, | ||
| 303 | test_case, | ||
| 304 | case_options, | ||
| 305 | progress, | ||
| 306 | }; | ||
| 307 | |||
| 308 | if (case_options.sync) { | ||
| 309 | @call(.auto, runTestCaseWrap, call_args); | ||
| 310 | } else { | ||
| 311 | pool.spawnWg(&wg, runTestCaseWrap, call_args); | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | wg.wait(); | ||
| 316 | |||
| 317 | var num_pass: usize = 0; | ||
| 318 | for (test_cases.items) |*case| { | ||
| 319 | if (case.success) { | ||
| 320 | num_pass += 1; | ||
| 321 | if (some_logs) { | ||
| 322 | std.debug.print("PASS: {s}\n", .{case.display}); | ||
| 323 | } | ||
| 324 | if (all_logs) { | ||
| 325 | std.debug.print("{s}", .{case.logs}); | ||
| 326 | } | ||
| 327 | } else if (case.failure) |*fail| { | ||
| 328 | std.debug.print("FAIL: {s}\n", .{case.display}); | ||
| 329 | std.debug.print("{s}", .{case.logs}); | ||
| 330 | if (fail.stack.index > 0) { | ||
| 331 | std.debug.print("Error return trace:\n", .{}); | ||
| 332 | std.debug.dumpStackTrace(fail.stack.trace()); | ||
| 333 | } | ||
| 334 | } else { | ||
| 335 | // success was never set true but failure was left null | ||
| 336 | // this means runTestCaseWrap was never called | ||
| 337 | std.debug.print("INVALID STATE: {s}\n", .{case.path}); | ||
| 338 | } | ||
| 339 | if (case.logs.len > 0) | ||
| 340 | alloc.free(case.logs); | ||
| 341 | } | ||
| 342 | |||
| 343 | if (some_logs) { | ||
| 344 | std.debug.print("Complete: {d} PASS / {d} TOTAL\n", .{ num_pass, test_cases.items.len }); | ||
| 345 | } | ||
| 346 | } | ||
| 347 | |||
| 348 | fn iterateTestCases( | ||
| 349 | alloc: std.mem.Allocator, | ||
| 350 | root: Dir, | ||
| 351 | root_path_len: usize, | ||
| 352 | path_buf: *std.BoundedArray(u8, std.fs.max_path_bytes), | ||
| 353 | test_cases: *std.ArrayListUnmanaged(Case), | ||
| 354 | filter: ?[]const u8, | ||
| 355 | ) !void { | ||
| 356 | var it = root.iterate(); | ||
| 357 | while (try it.next()) |entry| { | ||
| 358 | switch (entry.kind) { | ||
| 359 | .file => { | ||
| 360 | if (entry.name.len > 0 and entry.name[0] == '.') | ||
| 361 | continue; | ||
| 362 | |||
| 363 | const path = try std.fs.path.join(alloc, &.{ | ||
| 364 | path_buf.slice(), | ||
| 365 | entry.name, | ||
| 366 | }); | ||
| 367 | |||
| 368 | if (filter) |f| { | ||
| 369 | if (std.mem.indexOf(u8, path, f) == null) { | ||
| 370 | alloc.free(path); | ||
| 371 | continue; | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | try test_cases.append(alloc, .{ | ||
| 376 | .path = path, | ||
| 377 | .display = path[root_path_len + 1 ..], | ||
| 378 | }); | ||
| 379 | }, | ||
| 380 | .directory => { | ||
| 381 | const top = path_buf.len; | ||
| 382 | defer path_buf.len = top; | ||
| 383 | |||
| 384 | try path_buf.append(std.fs.path.sep); | ||
| 385 | try path_buf.appendSlice(entry.name); | ||
| 386 | |||
| 387 | const sub_dir = try root.openDir(entry.name, .{ .iterate = true }); | ||
| 388 | try iterateTestCases(alloc, sub_dir, root_path_len, path_buf, test_cases, filter); | ||
| 389 | }, | ||
| 390 | else => return error.UnsupportedFileType, | ||
| 391 | } | ||
| 392 | } | ||
| 393 | } | ||
| 394 | |||
| 395 | fn getFlag(name: []const u8, full_arg: []const u8) ?[]const u8 { | ||
| 396 | if (full_arg.len > name.len + 1 and | ||
| 397 | full_arg[name.len] == '=' and | ||
| 398 | std.mem.startsWith(u8, name, full_arg)) | ||
| 399 | { | ||
| 400 | return full_arg[name.len + 1 ..]; | ||
| 401 | } | ||
| 402 | |||
| 403 | return null; | ||
| 404 | } | ||
| 405 | |||
| 406 | const std = @import("std"); | ||
| 407 | const Dir = std.fs.Dir; | ||
| 408 | |||
| 409 | const Cli = @import("Cli"); | ||
| 410 | const core = @import("core"); | ||
| 411 | const qjs = @import("quickjs"); | ||
| 412 | |||
| 413 | const tracer = @import("tracer"); | ||
| 414 | pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)); | ||
test/compiler_tests.zig created+508| ... | @@ -0,0 +1,508 @@ | ||
| 1 | //! This program executes compiler tests found in this directory. Additionally, | ||
| 2 | //! it can be run as a standalone program to compile and execute a single test. | ||
| 3 | //! | ||
| 4 | //! See the "Testing" section in `architecture.md` for more information on the | ||
| 5 | //! structure of these tests, and `zig build test-compiler -- --help` for more | ||
| 6 | //! details on the arguments this supports. | ||
| 7 | |||
| 8 | const cli_config: Cli = .{ | ||
| 9 | .options = &.{ | ||
| 10 | .{ .name = "--test-root", .desc = "root directory of all tests", .kind = .string }, | ||
| 11 | .{ .name = "--all-logs", .desc = "Always print compiler logs" }, | ||
| 12 | .{ .name = "--no-threads", .desc = "Avoid using a thread pool" }, | ||
| 13 | .{ .name = "--dump-ir", .desc = "Dump intermediate representations" }, | ||
| 14 | |||
| 15 | .{ .name = "--listen", .desc = "Integrate with the Zig Build System IPC", .kind = .string }, | ||
| 16 | .{ .name = "--seed", .desc = "Set random number seed (unused)", .kind = .string }, | ||
| 17 | .{ .name = "--cache-dir", .desc = "Set cache dir (unused)", .kind = .string }, | ||
| 18 | }, | ||
| 19 | .positionals = .single, | ||
| 20 | .help = .{ | ||
| 21 | .usage = "[options...] [test filter]", | ||
| 22 | }, | ||
| 23 | }; | ||
| 24 | |||
| 25 | /// This file is included for all behavior tests for the JavaScript backend. | ||
| 26 | const helpers = | ||
| 27 | \\(function (self) { | ||
| 28 | \\ function assert(condition) { | ||
| 29 | \\ if (!condition) { | ||
| 30 | \\ throw new Error("Assertion failure"); | ||
| 31 | \\ } | ||
| 32 | \\ } | ||
| 33 | \\ | ||
| 34 | \\ function assertEqual(actual, expected) { | ||
| 35 | \\ if (actual !== expected) { | ||
| 36 | \\ var err; | ||
| 37 | \\ try { | ||
| 38 | \\ err = new Error("Expected " + expected + ", but got " + actual); | ||
| 39 | \\ } catch (e) { | ||
| 40 | \\ err = new Error("Assertion failure"); | ||
| 41 | \\ } | ||
| 42 | \\ throw err; | ||
| 43 | \\ } | ||
| 44 | \\ } | ||
| 45 | \\ | ||
| 46 | \\ function print(str) { | ||
| 47 | \\ console.log(str); | ||
| 48 | \\ } | ||
| 49 | \\ | ||
| 50 | \\ self.assert = assert; | ||
| 51 | \\ self.assertEqual = assertEqual; | ||
| 52 | \\ self.print = print; | ||
| 53 | \\})(typeof globalThis !== "undefined" ? globalThis : this); | ||
| 54 | \\ | ||
| 55 | ; | ||
| 56 | |||
| 57 | const CaseOptions = struct { | ||
| 58 | dump_ir: bool = false, | ||
| 59 | sync: bool = false, | ||
| 60 | }; | ||
| 61 | |||
| 62 | const Case = struct { | ||
| 63 | path: []const u8, | ||
| 64 | display: []const u8, | ||
| 65 | success: bool = false, | ||
| 66 | logs: []const u8 = "", | ||
| 67 | failure: ?struct { | ||
| 68 | err: anyerror, | ||
| 69 | stack: core.StoredTrace, | ||
| 70 | } = null, | ||
| 71 | |||
| 72 | const Kind = enum { | ||
| 73 | behavior, | ||
| 74 | analysis, | ||
| 75 | transform_error, | ||
| 76 | }; | ||
| 77 | }; | ||
| 78 | |||
| 79 | fn runTestCase( | ||
| 80 | backing_allocator: std.mem.Allocator, | ||
| 81 | file_path: []const u8, | ||
| 82 | case_options: CaseOptions, | ||
| 83 | out: std.io.AnyWriter, | ||
| 84 | ) !void { | ||
| 85 | var gpa = std.heap.GeneralPurposeAllocator(.{ | ||
| 86 | .stack_trace_frames = 100, | ||
| 87 | }){ .backing_allocator = backing_allocator }; | ||
| 88 | const alloc = gpa.allocator(); | ||
| 89 | |||
| 90 | var backend = core.Js{}; | ||
| 91 | var comp = try core.Comp.init(.{ | ||
| 92 | .gpa = tracer.tracingAllocator(&alloc), | ||
| 93 | .options = .{ .dump_ir = case_options.dump_ir }, | ||
| 94 | .backend = .{ .js = &backend }, | ||
| 95 | .out = .{ .writer = out, .tty = .escape_codes }, | ||
| 96 | }); | ||
| 97 | |||
| 98 | const main_file = try comp.getFile(file_path); | ||
| 99 | |||
| 100 | var fail_report: core.Report = undefined; | ||
| 101 | const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) { | ||
| 102 | error.TransformFail => { | ||
| 103 | try out.print("Transform failed:\n", .{}); | ||
| 104 | try fail_report.dump(.{ .comp = .{ .comp = &comp, .file = main_file } }, comp.out); | ||
| 105 | return error.TransformFail; | ||
| 106 | }, | ||
| 107 | error.ParseFail => { | ||
| 108 | // Already printed. | ||
| 109 | return error.ParseFail; | ||
| 110 | }, | ||
| 111 | else => return err, | ||
| 112 | }; | ||
| 113 | |||
| 114 | if (case_options.dump_ir) { | ||
| 115 | try teg.dumpToWriter(out, .escape_codes); | ||
| 116 | } | ||
| 117 | |||
| 118 | const entry_point = try comp.enqueueEntryModule(main_file); | ||
| 119 | try comp.runAllJobs(); | ||
| 120 | |||
| 121 | if (comp.temp_flag_had_errors) | ||
| 122 | return error.SemaErrors; | ||
| 123 | |||
| 124 | if (teg.report.warnings.len > 0) | ||
| 125 | return error.TegWarnings; | ||
| 126 | |||
| 127 | var js_out: std.ArrayListUnmanaged(u8) = .{}; | ||
| 128 | defer js_out.deinit(alloc); | ||
| 129 | |||
| 130 | try js_out.appendSlice(alloc, helpers); | ||
| 131 | try js_out.appendSlice(alloc, "\n\n(function() {\n\n"); | ||
| 132 | |||
| 133 | const start_i = js_out.items.len; | ||
| 134 | try backend.link(&comp, entry_point, js_out.writer(alloc).any()); | ||
| 135 | |||
| 136 | try out.writeAll("--- COMPILED CODE ---\n"); | ||
| 137 | try out.writeAll(js_out.items[start_i..]); | ||
| 138 | try out.writeAll("--- RUNTIME OUTPUT ---\n"); | ||
| 139 | |||
| 140 | if (isOnlyComments(js_out.items[start_i..])) { | ||
| 141 | try out.writeAll("Behavior tests should output code!"); | ||
| 142 | return error.EmptyFile; | ||
| 143 | } | ||
| 144 | |||
| 145 | try js_out.appendSlice(alloc, "\n\n})();"); | ||
| 146 | try js_out.append(alloc, 0); | ||
| 147 | |||
| 148 | const runtime = qjs.JS_NewRuntime() orelse | ||
| 149 | return error.UnexpectedError; | ||
| 150 | defer qjs.JS_FreeRuntime(runtime); | ||
| 151 | const js_ctx = qjs.JS_NewContext(runtime) orelse | ||
| 152 | return error.UnexpectedError; | ||
| 153 | defer qjs.JS_FreeContext(js_ctx); | ||
| 154 | |||
| 155 | const result = qjs.JS_Eval( | ||
| 156 | js_ctx, | ||
| 157 | js_out.items.ptr, | ||
| 158 | js_out.items.len - 1, | ||
| 159 | "test.js", | ||
| 160 | qjs.JS_EVAL_TYPE_GLOBAL, | ||
| 161 | ); | ||
| 162 | defer qjs.JS_FreeValue(js_ctx, result); | ||
| 163 | |||
| 164 | if (qjs.JS_IsException(result) != 0) { | ||
| 165 | const exception = qjs.JS_GetException(js_ctx); | ||
| 166 | defer qjs.JS_FreeValue(js_ctx, exception); | ||
| 167 | |||
| 168 | const exception_str = qjs.JS_ToCString(js_ctx, exception); | ||
| 169 | defer qjs.JS_FreeCString(js_ctx, exception_str); | ||
| 170 | |||
| 171 | const slice = std.mem.span(exception_str); | ||
| 172 | |||
| 173 | try out.print("Exception: {s}\n", .{slice}); | ||
| 174 | |||
| 175 | return error.RuntimeFailed; | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | fn isOnlyComments(input: []const u8) bool { | ||
| 180 | var rest = input; | ||
| 181 | while (rest.len > 0) { | ||
| 182 | if (rest.len == 1) return false; | ||
| 183 | if (rest[0] == '/' and rest[1] == '/') { | ||
| 184 | rest = rest[std.mem.indexOfScalar(u8, rest, '\n') orelse return true ..]; | ||
| 185 | } else { | ||
| 186 | return false; | ||
| 187 | } | ||
| 188 | } | ||
| 189 | return true; | ||
| 190 | } | ||
| 191 | |||
| 192 | const threadlocal_state = struct { | ||
| 193 | threadlocal var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); | ||
| 194 | }; | ||
| 195 | |||
| 196 | pub fn runTestCaseWrap( | ||
| 197 | gpa: std.mem.Allocator, | ||
| 198 | case: *Case, | ||
| 199 | case_options: CaseOptions, | ||
| 200 | progress: std.Progress.Node, | ||
| 201 | ) void { | ||
| 202 | const node = progress.start(case.display, 0); | ||
| 203 | defer node.end(); | ||
| 204 | |||
| 205 | const alloc = threadlocal_state.arena.allocator(); | ||
| 206 | defer _ = threadlocal_state.arena.reset(.{ .retain_with_limit = std.mem.page_size * 8 }); | ||
| 207 | |||
| 208 | var logs: std.ArrayListUnmanaged(u8) = .{}; | ||
| 209 | defer logs.deinit(alloc); | ||
| 210 | |||
| 211 | defer case.logs = gpa.dupe(u8, logs.items) catch | ||
| 212 | "[logs failed to allocate]"; | ||
| 213 | |||
| 214 | runTestCase( | ||
| 215 | alloc, | ||
| 216 | case.path, | ||
| 217 | case_options, | ||
| 218 | if (case_options.sync) | ||
| 219 | std.io.getStdErr().writer().any() | ||
| 220 | else | ||
| 221 | logs.writer(alloc).any(), | ||
| 222 | ) catch |err| { | ||
| 223 | const stack = switch (err) { | ||
| 224 | error.TransformFail, | ||
| 225 | error.ParseFail, | ||
| 226 | error.TegWarnings, | ||
| 227 | error.SemaErrors, | ||
| 228 | error.AnalysisFail, | ||
| 229 | => core.StoredTrace.empty, | ||
| 230 | |||
| 231 | else => core.StoredTrace.from(@errorReturnTrace()), | ||
| 232 | }; | ||
| 233 | |||
| 234 | case.failure = .{ | ||
| 235 | .err = err, | ||
| 236 | .stack = stack, | ||
| 237 | }; | ||
| 238 | |||
| 239 | return; | ||
| 240 | }; | ||
| 241 | |||
| 242 | case.success = true; | ||
| 243 | } | ||
| 244 | |||
| 245 | pub fn main() !void { | ||
| 246 | // unicode props is asserted to be initialized before core.Tokenizer is used | ||
| 247 | // this will result in a static page allocation, which is never cleaned up. | ||
| 248 | try core.unicode_props.init(); | ||
| 249 | var gpa = std.heap.GeneralPurposeAllocator(.{ | ||
| 250 | .stack_trace_frames = 100, | ||
| 251 | .thread_safe = true, | ||
| 252 | }){}; | ||
| 253 | |||
| 254 | defer _ = gpa.deinit(); | ||
| 255 | const alloc = gpa.allocator(); | ||
| 256 | |||
| 257 | const program_name = "zig build test-compiler --"; | ||
| 258 | _, const args = try Cli.programArgsAlloc(alloc, ""); | ||
| 259 | defer Cli.programArgsFree(alloc, args); | ||
| 260 | |||
| 261 | const opts = cli_config.parse(program_name, args); | ||
| 262 | |||
| 263 | const root_path = opts.test_root orelse "test/behavior"; | ||
| 264 | const case_options: CaseOptions = .{ | ||
| 265 | .dump_ir = opts.dump_ir orelse false, | ||
| 266 | .sync = opts.no_threads orelse false, | ||
| 267 | }; | ||
| 268 | const all_logs = opts.all_logs orelse false; | ||
| 269 | const filter: ?[]const u8 = opts.positionals; | ||
| 270 | |||
| 271 | const some_logs = all_logs or args.len > 1; | ||
| 272 | |||
| 273 | const root = try std.fs.cwd().openDir(root_path, .{ .iterate = true }); | ||
| 274 | |||
| 275 | var test_cases: std.ArrayListUnmanaged(Case) = .{}; | ||
| 276 | defer test_cases.deinit(alloc); | ||
| 277 | defer for (test_cases.items) |test_case| { | ||
| 278 | alloc.free(test_case.path); | ||
| 279 | }; | ||
| 280 | |||
| 281 | var path_buf: std.BoundedArray(u8, std.fs.max_path_bytes) = .{}; | ||
| 282 | path_buf.len = @intCast((try std.os.getFdPath(root.fd, &path_buf.buffer)).len); | ||
| 283 | try iterateTestCases(alloc, root, path_buf.len, &path_buf, &test_cases, filter); | ||
| 284 | |||
| 285 | if (test_cases.items.len == 0) { | ||
| 286 | if (filter) |f| { | ||
| 287 | std.debug.print("No test cases match filter \"{s}\"\n", .{f}); | ||
| 288 | } else { | ||
| 289 | std.debug.print("No test cases in root \"{s}\"", .{root_path}); | ||
| 290 | } | ||
| 291 | std.process.exit(1); | ||
| 292 | } | ||
| 293 | |||
| 294 | const progress = std.Progress.start(.{ .root_name = "Behavior Tests" }); | ||
| 295 | defer progress.end(); | ||
| 296 | progress.setEstimatedTotalItems(test_cases.items.len); | ||
| 297 | |||
| 298 | var wg: std.Thread.WaitGroup = .{}; | ||
| 299 | |||
| 300 | var pool: std.Thread.Pool = undefined; | ||
| 301 | if (!case_options.sync) try pool.init(.{ .allocator = alloc }); | ||
| 302 | defer if (!case_options.sync) pool.deinit(); | ||
| 303 | |||
| 304 | for (test_cases.items) |*test_case| { | ||
| 305 | const call_args = .{ | ||
| 306 | alloc, | ||
| 307 | test_case, | ||
| 308 | case_options, | ||
| 309 | progress, | ||
| 310 | }; | ||
| 311 | |||
| 312 | if (case_options.sync) { | ||
| 313 | @call(.auto, runTestCaseWrap, call_args); | ||
| 314 | } else { | ||
| 315 | pool.spawnWg(&wg, runTestCaseWrap, call_args); | ||
| 316 | } | ||
| 317 | } | ||
| 318 | |||
| 319 | wg.wait(); | ||
| 320 | |||
| 321 | if (opts.listen) |listen| { | ||
| 322 | try serveResults(gpa.allocator(), listen, test_cases.items); | ||
| 323 | return; | ||
| 324 | } | ||
| 325 | |||
| 326 | var num_pass: usize = 0; | ||
| 327 | for (test_cases.items) |*case| { | ||
| 328 | if (case.success) { | ||
| 329 | num_pass += 1; | ||
| 330 | if (some_logs) { | ||
| 331 | std.debug.print("PASS: {s}\n", .{case.display}); | ||
| 332 | } | ||
| 333 | if (all_logs) { | ||
| 334 | std.debug.print("{s}", .{case.logs}); | ||
| 335 | } | ||
| 336 | } else if (case.failure) |*fail| { | ||
| 337 | std.debug.print("FAIL: {s}\n", .{case.display}); | ||
| 338 | std.debug.print("{s}", .{case.logs}); | ||
| 339 | if (fail.stack.index > 0) { | ||
| 340 | std.debug.print("Error return trace:\n", .{}); | ||
| 341 | std.debug.dumpStackTrace(fail.stack.trace()); | ||
| 342 | } | ||
| 343 | } else { | ||
| 344 | // success was never set true but failure was left null | ||
| 345 | // this means runTestCaseWrap was never called | ||
| 346 | std.debug.print("INVALID STATE: {s}\n", .{case.path}); | ||
| 347 | } | ||
| 348 | if (case.logs.len > 0) | ||
| 349 | alloc.free(case.logs); | ||
| 350 | } | ||
| 351 | |||
| 352 | if (some_logs) { | ||
| 353 | std.debug.print("Complete: {d} PASS / {d} TOTAL\n", .{ num_pass, test_cases.items.len }); | ||
| 354 | } | ||
| 355 | } | ||
| 356 | |||
| 357 | const builtin = @import("builtin"); | ||
| 358 | |||
| 359 | var log_err_count: usize = 0; | ||
| 360 | var fba_buffer: [8192]u8 = undefined; | ||
| 361 | var fba = std.heap.FixedBufferAllocator.init(&fba_buffer); | ||
| 362 | |||
| 363 | /// This is the minimum amount of work to report to the Zig Build System the | ||
| 364 | /// results of the unit tests. Instead of running tests on-demand, it runs | ||
| 365 | /// them in a thread-pool, then reports the results. | ||
| 366 | fn serveResults(gpa: std.mem.Allocator, listen: []const u8, finished_cases: []Case) !void { | ||
| 367 | if (!std.mem.eql(u8, listen, "-")) @panic("Server only supports --listen=-"); | ||
| 368 | |||
| 369 | var server = try std.zig.Server.init(.{ | ||
| 370 | .gpa = fba.allocator(), | ||
| 371 | .in = std.io.getStdIn(), | ||
| 372 | .out = std.io.getStdOut(), | ||
| 373 | .zig_version = builtin.zig_version_string, | ||
| 374 | }); | ||
| 375 | defer server.deinit(); | ||
| 376 | |||
| 377 | while (true) { | ||
| 378 | const hdr = try server.receiveMessage(); | ||
| 379 | switch (hdr.tag) { | ||
| 380 | .exit => { | ||
| 381 | return std.process.exit(0); | ||
| 382 | }, | ||
| 383 | .query_test_metadata => { | ||
| 384 | var string_bytes: std.ArrayListUnmanaged(u8) = .{}; | ||
| 385 | defer string_bytes.deinit(gpa); | ||
| 386 | try string_bytes.append(gpa, 0); // Reserve 0 for null. | ||
| 387 | |||
| 388 | const names = try gpa.alloc(u32, finished_cases.len); | ||
| 389 | defer gpa.free(names); | ||
| 390 | const expected_panic_msgs = try gpa.alloc(u32, finished_cases.len); | ||
| 391 | defer gpa.free(expected_panic_msgs); | ||
| 392 | |||
| 393 | for (finished_cases, names, expected_panic_msgs) |case, *name, *expected_panic_msg| { | ||
| 394 | name.* = @as(u32, @intCast(string_bytes.items.len)); | ||
| 395 | const prefix = "compiler test: "; | ||
| 396 | try string_bytes.ensureUnusedCapacity(gpa, prefix.len + case.display.len + 1); | ||
| 397 | string_bytes.appendSliceAssumeCapacity(prefix); | ||
| 398 | string_bytes.appendSliceAssumeCapacity(case.display); | ||
| 399 | string_bytes.appendAssumeCapacity(0); | ||
| 400 | expected_panic_msg.* = 0; | ||
| 401 | } | ||
| 402 | |||
| 403 | try server.serveTestMetadata(.{ | ||
| 404 | .names = names, | ||
| 405 | .expected_panic_msgs = expected_panic_msgs, | ||
| 406 | .string_bytes = string_bytes.items, | ||
| 407 | }); | ||
| 408 | }, | ||
| 409 | .run_test => { | ||
| 410 | log_err_count = 0; | ||
| 411 | const index = try server.receiveBody_u32(); | ||
| 412 | const case = &finished_cases[index]; | ||
| 413 | if (case.failure) |*fail| { | ||
| 414 | std.debug.print("{s}\n{s}", .{ @errorName(fail.err), case.logs }); | ||
| 415 | if (fail.stack.index > 0) { | ||
| 416 | std.debug.print("Error return trace:\n", .{}); | ||
| 417 | std.debug.dumpStackTrace(fail.stack.trace()); | ||
| 418 | } | ||
| 419 | } | ||
| 420 | try server.serveTestResults(.{ | ||
| 421 | .index = index, | ||
| 422 | .flags = .{ | ||
| 423 | .fail = case.failure != null, | ||
| 424 | .skip = false, | ||
| 425 | .leak = false, | ||
| 426 | .fuzz = false, | ||
| 427 | .log_err_count = std.math.lossyCast( | ||
| 428 | @TypeOf(@as(std.zig.Server.Message.TestResults.Flags, undefined).log_err_count), | ||
| 429 | log_err_count, | ||
| 430 | ), | ||
| 431 | }, | ||
| 432 | }); | ||
| 433 | }, | ||
| 434 | else => { | ||
| 435 | std.debug.print("unsupported message: {x}\n", .{@intFromEnum(hdr.tag)}); | ||
| 436 | std.process.exit(1); | ||
| 437 | }, | ||
| 438 | } | ||
| 439 | } | ||
| 440 | } | ||
| 441 | |||
| 442 | fn iterateTestCases( | ||
| 443 | alloc: std.mem.Allocator, | ||
| 444 | root: Dir, | ||
| 445 | root_path_len: usize, | ||
| 446 | path_buf: *std.BoundedArray(u8, std.fs.max_path_bytes), | ||
| 447 | test_cases: *std.ArrayListUnmanaged(Case), | ||
| 448 | filter: ?[]const u8, | ||
| 449 | ) !void { | ||
| 450 | var it = root.iterate(); | ||
| 451 | while (try it.next()) |entry| { | ||
| 452 | switch (entry.kind) { | ||
| 453 | .file => { | ||
| 454 | if (entry.name.len > 0 and entry.name[0] == '.') | ||
| 455 | continue; | ||
| 456 | |||
| 457 | const path = try std.fs.path.join(alloc, &.{ | ||
| 458 | path_buf.slice(), | ||
| 459 | entry.name, | ||
| 460 | }); | ||
| 461 | |||
| 462 | if (filter) |f| { | ||
| 463 | if (std.mem.indexOf(u8, path, f) == null) { | ||
| 464 | alloc.free(path); | ||
| 465 | continue; | ||
| 466 | } | ||
| 467 | } | ||
| 468 | |||
| 469 | try test_cases.append(alloc, .{ | ||
| 470 | .path = path, | ||
| 471 | .display = path[root_path_len + 1 ..], | ||
| 472 | }); | ||
| 473 | }, | ||
| 474 | .directory => { | ||
| 475 | const top = path_buf.len; | ||
| 476 | defer path_buf.len = top; | ||
| 477 | |||
| 478 | try path_buf.append(std.fs.path.sep); | ||
| 479 | try path_buf.appendSlice(entry.name); | ||
| 480 | |||
| 481 | const sub_dir = try root.openDir(entry.name, .{ .iterate = true }); | ||
| 482 | try iterateTestCases(alloc, sub_dir, root_path_len, path_buf, test_cases, filter); | ||
| 483 | }, | ||
| 484 | else => return error.UnsupportedFileType, | ||
| 485 | } | ||
| 486 | } | ||
| 487 | } | ||
| 488 | |||
| 489 | fn getFlag(name: []const u8, full_arg: []const u8) ?[]const u8 { | ||
| 490 | if (full_arg.len > name.len + 1 and | ||
| 491 | full_arg[name.len] == '=' and | ||
| 492 | std.mem.startsWith(u8, name, full_arg)) | ||
| 493 | { | ||
| 494 | return full_arg[name.len + 1 ..]; | ||
| 495 | } | ||
| 496 | |||
| 497 | return null; | ||
| 498 | } | ||
| 499 | |||
| 500 | const std = @import("std"); | ||
| 501 | const Dir = std.fs.Dir; | ||
| 502 | |||
| 503 | const Cli = @import("Cli"); | ||
| 504 | const core = @import("core"); | ||
| 505 | const qjs = @import("quickjs"); | ||
| 506 | |||
| 507 | const tracer = @import("tracer"); | ||
| 508 | pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode)); | ||