blob 5fbb56b7 (42063B) - Raw
1 const std = @import("std"); 2 const debug = std.debug; 3 const warn = debug.warn; 4 const build = std.build; 5 const os = std.os; 6 const StdIo = os.ChildProcess.StdIo; 7 const Term = os.ChildProcess.Term; 8 const Buffer = std.Buffer; 9 const io = std.io; 10 const mem = std.mem; 11 const fmt = std.fmt; 12 const ArrayList = std.ArrayList; 13 const builtin = @import("builtin"); 14 const Mode = builtin.Mode; 15 16 const compare_output = @import("compare_output.zig"); 17 const build_examples = @import("build_examples.zig"); 18 const compile_errors = @import("compile_errors.zig"); 19 const assemble_and_link = @import("assemble_and_link.zig"); 20 const runtime_safety = @import("runtime_safety.zig"); 21 const translate_c = @import("translate_c.zig"); 22 const gen_h = @import("gen_h.zig"); 23 24 const TestTarget = struct { 25 os: builtin.Os, 26 arch: builtin.Arch, 27 environ: builtin.Environ, 28 }; 29 30 const test_targets = []TestTarget { 31 TestTarget { 32 .os = builtin.Os.linux, 33 .arch = builtin.Arch.x86_64, 34 .environ = builtin.Environ.gnu, 35 }, 36 TestTarget { 37 .os = builtin.Os.macosx, 38 .arch = builtin.Arch.x86_64, 39 .environ = builtin.Environ.unknown, 40 }, 41 TestTarget { 42 .os = builtin.Os.windows, 43 .arch = builtin.Arch.x86_64, 44 .environ = builtin.Environ.msvc, 45 }, 46 }; 47 48 const max_stdout_size = 1 * 1024 * 1024; // 1 MB 49 50 pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) &build.Step { 51 const cases = b.allocator.create(CompareOutputContext) catch unreachable; 52 *cases = CompareOutputContext { 53 .b = b, 54 .step = b.step("test-compare-output", "Run the compare output tests"), 55 .test_index = 0, 56 .test_filter = test_filter, 57 }; 58 59 compare_output.addCases(cases); 60 61 return cases.step; 62 } 63 64 pub fn addRuntimeSafetyTests(b: &build.Builder, test_filter: ?[]const u8) &build.Step { 65 const cases = b.allocator.create(CompareOutputContext) catch unreachable; 66 *cases = CompareOutputContext { 67 .b = b, 68 .step = b.step("test-runtime-safety", "Run the runtime safety tests"), 69 .test_index = 0, 70 .test_filter = test_filter, 71 }; 72 73 runtime_safety.addCases(cases); 74 75 return cases.step; 76 } 77 78 pub fn addCompileErrorTests(b: &build.Builder, test_filter: ?[]const u8) &build.Step { 79 const cases = b.allocator.create(CompileErrorContext) catch unreachable; 80 *cases = CompileErrorContext { 81 .b = b, 82 .step = b.step("test-compile-errors", "Run the compile error tests"), 83 .test_index = 0, 84 .test_filter = test_filter, 85 }; 86 87 compile_errors.addCases(cases); 88 89 return cases.step; 90 } 91 92 pub fn addBuildExampleTests(b: &build.Builder, test_filter: ?[]const u8) &build.Step { 93 const cases = b.allocator.create(BuildExamplesContext) catch unreachable; 94 *cases = BuildExamplesContext { 95 .b = b, 96 .step = b.step("test-build-examples", "Build the examples"), 97 .test_index = 0, 98 .test_filter = test_filter, 99 }; 100 101 build_examples.addCases(cases); 102 103 return cases.step; 104 } 105 106 pub fn addAssembleAndLinkTests(b: &build.Builder, test_filter: ?[]const u8) &build.Step { 107 const cases = b.allocator.create(CompareOutputContext) catch unreachable; 108 *cases = CompareOutputContext { 109 .b = b, 110 .step = b.step("test-asm-link", "Run the assemble and link tests"), 111 .test_index = 0, 112 .test_filter = test_filter, 113 }; 114 115 assemble_and_link.addCases(cases); 116 117 return cases.step; 118 } 119 120 pub fn addTranslateCTests(b: &build.Builder, test_filter: ?[]const u8) &build.Step { 121 const cases = b.allocator.create(TranslateCContext) catch unreachable; 122 *cases = TranslateCContext { 123 .b = b, 124 .step = b.step("test-translate-c", "Run the C transation tests"), 125 .test_index = 0, 126 .test_filter = test_filter, 127 }; 128 129 translate_c.addCases(cases); 130 131 return cases.step; 132 } 133 134 pub fn addGenHTests(b: &build.Builder, test_filter: ?[]const u8) &build.Step { 135 const cases = b.allocator.create(GenHContext) catch unreachable; 136 *cases = GenHContext { 137 .b = b, 138 .step = b.step("test-gen-h", "Run the C header file generation tests"), 139 .test_index = 0, 140 .test_filter = test_filter, 141 }; 142 143 gen_h.addCases(cases); 144 145 return cases.step; 146 } 147 148 149 pub fn addPkgTests(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8, 150 name:[] const u8, desc: []const u8, with_lldb: bool) &build.Step 151 { 152 const step = b.step(b.fmt("test-{}", name), desc); 153 for (test_targets) |test_target| { 154 const is_native = (test_target.os == builtin.os and test_target.arch == builtin.arch); 155 for ([]Mode{Mode.Debug, Mode.ReleaseSafe, Mode.ReleaseFast, Mode.ReleaseSmall}) |mode| { 156 for ([]bool{false, true}) |link_libc| { 157 if (link_libc and !is_native) { 158 // don't assume we have a cross-compiling libc set up 159 continue; 160 } 161 const these_tests = b.addTest(root_src); 162 these_tests.setNamePrefix(b.fmt("{}-{}-{}-{}-{} ", name, @tagName(test_target.os), 163 @tagName(test_target.arch), @tagName(mode), if (link_libc) "c" else "bare")); 164 these_tests.setFilter(test_filter); 165 these_tests.setBuildMode(mode); 166 if (!is_native) { 167 these_tests.setTarget(test_target.arch, test_target.os, test_target.environ); 168 } 169 if (link_libc) { 170 these_tests.linkSystemLibrary("c"); 171 } 172 if (with_lldb) { 173 these_tests.setExecCmd([]?[]const u8{ 174 "lldb", null, "-o", "run", "-o", "bt", "-o", "exit"}); 175 } 176 step.dependOn(&these_tests.step); 177 } 178 } 179 } 180 return step; 181 } 182 183 pub const CompareOutputContext = struct { 184 b: &build.Builder, 185 step: &build.Step, 186 test_index: usize, 187 test_filter: ?[]const u8, 188 189 const Special = enum { 190 None, 191 Asm, 192 RuntimeSafety, 193 }; 194 195 const TestCase = struct { 196 name: []const u8, 197 sources: ArrayList(SourceFile), 198 expected_output: []const u8, 199 link_libc: bool, 200 special: Special, 201 cli_args: []const []const u8, 202 203 const SourceFile = struct { 204 filename: []const u8, 205 source: []const u8, 206 }; 207 208 pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) void { 209 self.sources.append(SourceFile { 210 .filename = filename, 211 .source = source, 212 }) catch unreachable; 213 } 214 215 pub fn setCommandLineArgs(self: &TestCase, args: []const []const u8) void { 216 self.cli_args = args; 217 } 218 }; 219 220 const RunCompareOutputStep = struct { 221 step: build.Step, 222 context: &CompareOutputContext, 223 exe_path: []const u8, 224 name: []const u8, 225 expected_output: []const u8, 226 test_index: usize, 227 cli_args: []const []const u8, 228 229 pub fn create(context: &CompareOutputContext, exe_path: []const u8, 230 name: []const u8, expected_output: []const u8, 231 cli_args: []const []const u8) &RunCompareOutputStep 232 { 233 const allocator = context.b.allocator; 234 const ptr = allocator.create(RunCompareOutputStep) catch unreachable; 235 *ptr = RunCompareOutputStep { 236 .context = context, 237 .exe_path = exe_path, 238 .name = name, 239 .expected_output = expected_output, 240 .test_index = context.test_index, 241 .step = build.Step.init("RunCompareOutput", allocator, make), 242 .cli_args = cli_args, 243 }; 244 context.test_index += 1; 245 return ptr; 246 } 247 248 fn make(step: &build.Step) !void { 249 const self = @fieldParentPtr(RunCompareOutputStep, "step", step); 250 const b = self.context.b; 251 252 const full_exe_path = b.pathFromRoot(self.exe_path); 253 var args = ArrayList([]const u8).init(b.allocator); 254 defer args.deinit(); 255 256 args.append(full_exe_path) catch unreachable; 257 for (self.cli_args) |arg| { 258 args.append(arg) catch unreachable; 259 } 260 261 warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); 262 263 const child = os.ChildProcess.init(args.toSliceConst(), b.allocator) catch unreachable; 264 defer child.deinit(); 265 266 child.stdin_behavior = StdIo.Ignore; 267 child.stdout_behavior = StdIo.Pipe; 268 child.stderr_behavior = StdIo.Pipe; 269 child.env_map = &b.env_map; 270 271 child.spawn() catch |err| debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err)); 272 273 var stdout = Buffer.initNull(b.allocator); 274 var stderr = Buffer.initNull(b.allocator); 275 276 var stdout_file_in_stream = io.FileInStream.init(&??child.stdout); 277 var stderr_file_in_stream = io.FileInStream.init(&??child.stderr); 278 279 stdout_file_in_stream.stream.readAllBuffer(&stdout, max_stdout_size) catch unreachable; 280 stderr_file_in_stream.stream.readAllBuffer(&stderr, max_stdout_size) catch unreachable; 281 282 const term = child.wait() catch |err| { 283 debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err)); 284 }; 285 switch (term) { 286 Term.Exited => |code| { 287 if (code != 0) { 288 warn("Process {} exited with error code {}\n", full_exe_path, code); 289 return error.TestFailed; 290 } 291 }, 292 else => { 293 warn("Process {} terminated unexpectedly\n", full_exe_path); 294 return error.TestFailed; 295 }, 296 } 297 298 299 if (!mem.eql(u8, self.expected_output, stdout.toSliceConst())) { 300 warn( 301 \\ 302 \\========= Expected this output: ========= 303 \\{} 304 \\================================================ 305 \\{} 306 \\ 307 , self.expected_output, stdout.toSliceConst()); 308 return error.TestFailed; 309 } 310 warn("OK\n"); 311 } 312 }; 313 314 const RuntimeSafetyRunStep = struct { 315 step: build.Step, 316 context: &CompareOutputContext, 317 exe_path: []const u8, 318 name: []const u8, 319 test_index: usize, 320 321 pub fn create(context: &CompareOutputContext, exe_path: []const u8, 322 name: []const u8) &RuntimeSafetyRunStep 323 { 324 const allocator = context.b.allocator; 325 const ptr = allocator.create(RuntimeSafetyRunStep) catch unreachable; 326 *ptr = RuntimeSafetyRunStep { 327 .context = context, 328 .exe_path = exe_path, 329 .name = name, 330 .test_index = context.test_index, 331 .step = build.Step.init("RuntimeSafetyRun", allocator, make), 332 }; 333 context.test_index += 1; 334 return ptr; 335 } 336 337 fn make(step: &build.Step) !void { 338 const self = @fieldParentPtr(RuntimeSafetyRunStep, "step", step); 339 const b = self.context.b; 340 341 const full_exe_path = b.pathFromRoot(self.exe_path); 342 343 warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); 344 345 const child = os.ChildProcess.init([][]u8{full_exe_path}, b.allocator) catch unreachable; 346 defer child.deinit(); 347 348 child.env_map = &b.env_map; 349 child.stdin_behavior = StdIo.Ignore; 350 child.stdout_behavior = StdIo.Ignore; 351 child.stderr_behavior = StdIo.Ignore; 352 353 const term = child.spawnAndWait() catch |err| { 354 debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err)); 355 }; 356 357 const expected_exit_code: i32 = 126; 358 switch (term) { 359 Term.Exited => |code| { 360 if (code != expected_exit_code) { 361 warn("\nProgram expected to exit with code {} " ++ 362 "but exited with code {}\n", expected_exit_code, code); 363 return error.TestFailed; 364 } 365 }, 366 Term.Signal => |sig| { 367 warn("\nProgram expected to exit with code {} " ++ 368 "but instead signaled {}\n", expected_exit_code, sig); 369 return error.TestFailed; 370 }, 371 else => { 372 warn("\nProgram expected to exit with code {}" ++ 373 " but exited in an unexpected way\n", expected_exit_code); 374 return error.TestFailed; 375 }, 376 } 377 378 warn("OK\n"); 379 } 380 }; 381 382 pub fn createExtra(self: &CompareOutputContext, name: []const u8, source: []const u8, 383 expected_output: []const u8, special: Special) TestCase 384 { 385 var tc = TestCase { 386 .name = name, 387 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), 388 .expected_output = expected_output, 389 .link_libc = false, 390 .special = special, 391 .cli_args = []const []const u8{}, 392 }; 393 const root_src_name = if (special == Special.Asm) "source.s" else "source.zig"; 394 tc.addSourceFile(root_src_name, source); 395 return tc; 396 } 397 398 pub fn create(self: &CompareOutputContext, name: []const u8, source: []const u8, 399 expected_output: []const u8) TestCase 400 { 401 return createExtra(self, name, source, expected_output, Special.None); 402 } 403 404 pub fn addC(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void { 405 var tc = self.create(name, source, expected_output); 406 tc.link_libc = true; 407 self.addCase(tc); 408 } 409 410 pub fn add(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void { 411 const tc = self.create(name, source, expected_output); 412 self.addCase(tc); 413 } 414 415 pub fn addAsm(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void { 416 const tc = self.createExtra(name, source, expected_output, Special.Asm); 417 self.addCase(tc); 418 } 419 420 pub fn addRuntimeSafety(self: &CompareOutputContext, name: []const u8, source: []const u8) void { 421 const tc = self.createExtra(name, source, undefined, Special.RuntimeSafety); 422 self.addCase(tc); 423 } 424 425 pub fn addCase(self: &CompareOutputContext, case: &const TestCase) void { 426 const b = self.b; 427 428 const root_src = os.path.join(b.allocator, b.cache_root, case.sources.items[0].filename) catch unreachable; 429 430 switch (case.special) { 431 Special.Asm => { 432 const annotated_case_name = fmt.allocPrint(self.b.allocator, "assemble-and-link {}", case.name) catch unreachable; 433 if (self.test_filter) |filter| { 434 if (mem.indexOf(u8, annotated_case_name, filter) == null) 435 return; 436 } 437 438 const exe = b.addExecutable("test", null); 439 exe.addAssemblyFile(root_src); 440 441 for (case.sources.toSliceConst()) |src_file| { 442 const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; 443 const write_src = b.addWriteFile(expanded_src_path, src_file.source); 444 exe.step.dependOn(&write_src.step); 445 } 446 447 const run_and_cmp_output = RunCompareOutputStep.create(self, exe.getOutputPath(), annotated_case_name, 448 case.expected_output, case.cli_args); 449 run_and_cmp_output.step.dependOn(&exe.step); 450 451 self.step.dependOn(&run_and_cmp_output.step); 452 }, 453 Special.None => { 454 for ([]Mode{Mode.Debug, Mode.ReleaseSafe, Mode.ReleaseFast, Mode.ReleaseSmall}) |mode| { 455 const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {} ({})", 456 "compare-output", case.name, @tagName(mode)) catch unreachable; 457 if (self.test_filter) |filter| { 458 if (mem.indexOf(u8, annotated_case_name, filter) == null) 459 continue; 460 } 461 462 const exe = b.addExecutable("test", root_src); 463 exe.setBuildMode(mode); 464 if (case.link_libc) { 465 exe.linkSystemLibrary("c"); 466 } 467 468 for (case.sources.toSliceConst()) |src_file| { 469 const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; 470 const write_src = b.addWriteFile(expanded_src_path, src_file.source); 471 exe.step.dependOn(&write_src.step); 472 } 473 474 const run_and_cmp_output = RunCompareOutputStep.create(self, exe.getOutputPath(), 475 annotated_case_name, case.expected_output, case.cli_args); 476 run_and_cmp_output.step.dependOn(&exe.step); 477 478 self.step.dependOn(&run_and_cmp_output.step); 479 } 480 }, 481 Special.RuntimeSafety => { 482 const annotated_case_name = fmt.allocPrint(self.b.allocator, "safety {}", case.name) catch unreachable; 483 if (self.test_filter) |filter| { 484 if (mem.indexOf(u8, annotated_case_name, filter) == null) 485 return; 486 } 487 488 const exe = b.addExecutable("test", root_src); 489 if (case.link_libc) { 490 exe.linkSystemLibrary("c"); 491 } 492 493 for (case.sources.toSliceConst()) |src_file| { 494 const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; 495 const write_src = b.addWriteFile(expanded_src_path, src_file.source); 496 exe.step.dependOn(&write_src.step); 497 } 498 499 const run_and_cmp_output = RuntimeSafetyRunStep.create(self, exe.getOutputPath(), annotated_case_name); 500 run_and_cmp_output.step.dependOn(&exe.step); 501 502 self.step.dependOn(&run_and_cmp_output.step); 503 }, 504 } 505 } 506 }; 507 508 pub const CompileErrorContext = struct { 509 b: &build.Builder, 510 step: &build.Step, 511 test_index: usize, 512 test_filter: ?[]const u8, 513 514 const TestCase = struct { 515 name: []const u8, 516 sources: ArrayList(SourceFile), 517 expected_errors: ArrayList([]const u8), 518 link_libc: bool, 519 is_exe: bool, 520 521 const SourceFile = struct { 522 filename: []const u8, 523 source: []const u8, 524 }; 525 526 pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) void { 527 self.sources.append(SourceFile { 528 .filename = filename, 529 .source = source, 530 }) catch unreachable; 531 } 532 533 pub fn addExpectedError(self: &TestCase, text: []const u8) void { 534 self.expected_errors.append(text) catch unreachable; 535 } 536 }; 537 538 const CompileCmpOutputStep = struct { 539 step: build.Step, 540 context: &CompileErrorContext, 541 name: []const u8, 542 test_index: usize, 543 case: &const TestCase, 544 build_mode: Mode, 545 546 pub fn create(context: &CompileErrorContext, name: []const u8, 547 case: &const TestCase, build_mode: Mode) &CompileCmpOutputStep 548 { 549 const allocator = context.b.allocator; 550 const ptr = allocator.create(CompileCmpOutputStep) catch unreachable; 551 *ptr = CompileCmpOutputStep { 552 .step = build.Step.init("CompileCmpOutput", allocator, make), 553 .context = context, 554 .name = name, 555 .test_index = context.test_index, 556 .case = case, 557 .build_mode = build_mode, 558 }; 559 context.test_index += 1; 560 return ptr; 561 } 562 563 fn make(step: &build.Step) !void { 564 const self = @fieldParentPtr(CompileCmpOutputStep, "step", step); 565 const b = self.context.b; 566 567 const root_src = os.path.join(b.allocator, b.cache_root, self.case.sources.items[0].filename) catch unreachable; 568 const obj_path = os.path.join(b.allocator, b.cache_root, "test.o") catch unreachable; 569 570 var zig_args = ArrayList([]const u8).init(b.allocator); 571 zig_args.append(b.zig_exe) catch unreachable; 572 573 zig_args.append(if (self.case.is_exe) "build-exe" else "build-obj") catch unreachable; 574 zig_args.append(b.pathFromRoot(root_src)) catch unreachable; 575 576 zig_args.append("--name") catch unreachable; 577 zig_args.append("test") catch unreachable; 578 579 zig_args.append("--output") catch unreachable; 580 zig_args.append(b.pathFromRoot(obj_path)) catch unreachable; 581 582 switch (self.build_mode) { 583 Mode.Debug => {}, 584 Mode.ReleaseSafe => zig_args.append("--release-safe") catch unreachable, 585 Mode.ReleaseFast => zig_args.append("--release-fast") catch unreachable, 586 Mode.ReleaseSmall => zig_args.append("--release-small") catch unreachable, 587 } 588 589 warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); 590 591 if (b.verbose) { 592 printInvocation(zig_args.toSliceConst()); 593 } 594 595 const child = os.ChildProcess.init(zig_args.toSliceConst(), b.allocator) catch unreachable; 596 defer child.deinit(); 597 598 child.env_map = &b.env_map; 599 child.stdin_behavior = StdIo.Ignore; 600 child.stdout_behavior = StdIo.Pipe; 601 child.stderr_behavior = StdIo.Pipe; 602 603 child.spawn() catch |err| debug.panic("Unable to spawn {}: {}\n", zig_args.items[0], @errorName(err)); 604 605 var stdout_buf = Buffer.initNull(b.allocator); 606 var stderr_buf = Buffer.initNull(b.allocator); 607 608 var stdout_file_in_stream = io.FileInStream.init(&??child.stdout); 609 var stderr_file_in_stream = io.FileInStream.init(&??child.stderr); 610 611 stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable; 612 stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable; 613 614 const term = child.wait() catch |err| { 615 debug.panic("Unable to spawn {}: {}\n", zig_args.items[0], @errorName(err)); 616 }; 617 switch (term) { 618 Term.Exited => |code| { 619 if (code == 0) { 620 return error.CompilationIncorrectlySucceeded; 621 } 622 }, 623 else => { 624 warn("Process {} terminated unexpectedly\n", b.zig_exe); 625 return error.TestFailed; 626 }, 627 } 628 629 630 const stdout = stdout_buf.toSliceConst(); 631 const stderr = stderr_buf.toSliceConst(); 632 633 if (stdout.len != 0) { 634 warn( 635 \\ 636 \\Expected empty stdout, instead found: 637 \\================================================ 638 \\{} 639 \\================================================ 640 \\ 641 , stdout); 642 return error.TestFailed; 643 } 644 645 for (self.case.expected_errors.toSliceConst()) |expected_error| { 646 if (mem.indexOf(u8, stderr, expected_error) == null) { 647 warn( 648 \\ 649 \\========= Expected this compile error: ========= 650 \\{} 651 \\================================================ 652 \\{} 653 \\ 654 , expected_error, stderr); 655 return error.TestFailed; 656 } 657 } 658 warn("OK\n"); 659 } 660 }; 661 662 fn printInvocation(args: []const []const u8) void { 663 for (args) |arg| { 664 warn("{} ", arg); 665 } 666 warn("\n"); 667 } 668 669 pub fn create(self: &CompileErrorContext, name: []const u8, source: []const u8, 670 expected_lines: ...) &TestCase 671 { 672 const tc = self.b.allocator.create(TestCase) catch unreachable; 673 *tc = TestCase { 674 .name = name, 675 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), 676 .expected_errors = ArrayList([]const u8).init(self.b.allocator), 677 .link_libc = false, 678 .is_exe = false, 679 }; 680 tc.addSourceFile(".tmp_source.zig", source); 681 comptime var arg_i = 0; 682 inline while (arg_i < expected_lines.len) : (arg_i += 1) { 683 tc.addExpectedError(expected_lines[arg_i]); 684 } 685 return tc; 686 } 687 688 pub fn addC(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) void { 689 var tc = self.create(name, source, expected_lines); 690 tc.link_libc = true; 691 self.addCase(tc); 692 } 693 694 pub fn addExe(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) void { 695 var tc = self.create(name, source, expected_lines); 696 tc.is_exe = true; 697 self.addCase(tc); 698 } 699 700 pub fn add(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) void { 701 const tc = self.create(name, source, expected_lines); 702 self.addCase(tc); 703 } 704 705 pub fn addCase(self: &CompileErrorContext, case: &const TestCase) void { 706 const b = self.b; 707 708 for ([]Mode{Mode.Debug, Mode.ReleaseFast}) |mode| { 709 const annotated_case_name = fmt.allocPrint(self.b.allocator, "compile-error {} ({})", 710 case.name, @tagName(mode)) catch unreachable; 711 if (self.test_filter) |filter| { 712 if (mem.indexOf(u8, annotated_case_name, filter) == null) 713 continue; 714 } 715 716 const compile_and_cmp_errors = CompileCmpOutputStep.create(self, annotated_case_name, case, mode); 717 self.step.dependOn(&compile_and_cmp_errors.step); 718 719 for (case.sources.toSliceConst()) |src_file| { 720 const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; 721 const write_src = b.addWriteFile(expanded_src_path, src_file.source); 722 compile_and_cmp_errors.step.dependOn(&write_src.step); 723 } 724 } 725 } 726 }; 727 728 pub const BuildExamplesContext = struct { 729 b: &build.Builder, 730 step: &build.Step, 731 test_index: usize, 732 test_filter: ?[]const u8, 733 734 pub fn addC(self: &BuildExamplesContext, root_src: []const u8) void { 735 self.addAllArgs(root_src, true); 736 } 737 738 pub fn add(self: &BuildExamplesContext, root_src: []const u8) void { 739 self.addAllArgs(root_src, false); 740 } 741 742 pub fn addBuildFile(self: &BuildExamplesContext, build_file: []const u8) void { 743 const b = self.b; 744 745 const annotated_case_name = b.fmt("build {} (Debug)", build_file); 746 if (self.test_filter) |filter| { 747 if (mem.indexOf(u8, annotated_case_name, filter) == null) 748 return; 749 } 750 751 var zig_args = ArrayList([]const u8).init(b.allocator); 752 const rel_zig_exe = os.path.relative(b.allocator, b.build_root, b.zig_exe) catch unreachable; 753 zig_args.append(rel_zig_exe) catch unreachable; 754 zig_args.append("build") catch unreachable; 755 756 zig_args.append("--build-file") catch unreachable; 757 zig_args.append(b.pathFromRoot(build_file)) catch unreachable; 758 759 zig_args.append("test") catch unreachable; 760 761 if (b.verbose) { 762 zig_args.append("--verbose") catch unreachable; 763 } 764 765 const run_cmd = b.addCommand(null, b.env_map, zig_args.toSliceConst()); 766 767 const log_step = b.addLog("PASS {}\n", annotated_case_name); 768 log_step.step.dependOn(&run_cmd.step); 769 770 self.step.dependOn(&log_step.step); 771 } 772 773 pub fn addAllArgs(self: &BuildExamplesContext, root_src: []const u8, link_libc: bool) void { 774 const b = self.b; 775 776 for ([]Mode{Mode.Debug, Mode.ReleaseSafe, Mode.ReleaseFast, Mode.ReleaseSmall}) |mode| { 777 const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {} ({})", 778 root_src, @tagName(mode)) catch unreachable; 779 if (self.test_filter) |filter| { 780 if (mem.indexOf(u8, annotated_case_name, filter) == null) 781 continue; 782 } 783 784 const exe = b.addExecutable("test", root_src); 785 exe.setBuildMode(mode); 786 if (link_libc) { 787 exe.linkSystemLibrary("c"); 788 } 789 790 const log_step = b.addLog("PASS {}\n", annotated_case_name); 791 log_step.step.dependOn(&exe.step); 792 793 self.step.dependOn(&log_step.step); 794 } 795 } 796 }; 797 798 pub const TranslateCContext = struct { 799 b: &build.Builder, 800 step: &build.Step, 801 test_index: usize, 802 test_filter: ?[]const u8, 803 804 const TestCase = struct { 805 name: []const u8, 806 sources: ArrayList(SourceFile), 807 expected_lines: ArrayList([]const u8), 808 allow_warnings: bool, 809 810 const SourceFile = struct { 811 filename: []const u8, 812 source: []const u8, 813 }; 814 815 pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) void { 816 self.sources.append(SourceFile { 817 .filename = filename, 818 .source = source, 819 }) catch unreachable; 820 } 821 822 pub fn addExpectedLine(self: &TestCase, text: []const u8) void { 823 self.expected_lines.append(text) catch unreachable; 824 } 825 }; 826 827 const TranslateCCmpOutputStep = struct { 828 step: build.Step, 829 context: &TranslateCContext, 830 name: []const u8, 831 test_index: usize, 832 case: &const TestCase, 833 834 pub fn create(context: &TranslateCContext, name: []const u8, case: &const TestCase) &TranslateCCmpOutputStep { 835 const allocator = context.b.allocator; 836 const ptr = allocator.create(TranslateCCmpOutputStep) catch unreachable; 837 *ptr = TranslateCCmpOutputStep { 838 .step = build.Step.init("ParseCCmpOutput", allocator, make), 839 .context = context, 840 .name = name, 841 .test_index = context.test_index, 842 .case = case, 843 }; 844 context.test_index += 1; 845 return ptr; 846 } 847 848 fn make(step: &build.Step) !void { 849 const self = @fieldParentPtr(TranslateCCmpOutputStep, "step", step); 850 const b = self.context.b; 851 852 const root_src = os.path.join(b.allocator, b.cache_root, self.case.sources.items[0].filename) catch unreachable; 853 854 var zig_args = ArrayList([]const u8).init(b.allocator); 855 zig_args.append(b.zig_exe) catch unreachable; 856 857 zig_args.append("translate-c") catch unreachable; 858 zig_args.append(b.pathFromRoot(root_src)) catch unreachable; 859 860 warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); 861 862 if (b.verbose) { 863 printInvocation(zig_args.toSliceConst()); 864 } 865 866 const child = os.ChildProcess.init(zig_args.toSliceConst(), b.allocator) catch unreachable; 867 defer child.deinit(); 868 869 child.env_map = &b.env_map; 870 child.stdin_behavior = StdIo.Ignore; 871 child.stdout_behavior = StdIo.Pipe; 872 child.stderr_behavior = StdIo.Pipe; 873 874 child.spawn() catch |err| debug.panic("Unable to spawn {}: {}\n", zig_args.toSliceConst()[0], @errorName(err)); 875 876 var stdout_buf = Buffer.initNull(b.allocator); 877 var stderr_buf = Buffer.initNull(b.allocator); 878 879 var stdout_file_in_stream = io.FileInStream.init(&??child.stdout); 880 var stderr_file_in_stream = io.FileInStream.init(&??child.stderr); 881 882 stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable; 883 stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable; 884 885 const term = child.wait() catch |err| { 886 debug.panic("Unable to spawn {}: {}\n", zig_args.toSliceConst()[0], @errorName(err)); 887 }; 888 switch (term) { 889 Term.Exited => |code| { 890 if (code != 0) { 891 warn("Compilation failed with exit code {}\n", code); 892 return error.TestFailed; 893 } 894 }, 895 Term.Signal => |code| { 896 warn("Compilation failed with signal {}\n", code); 897 return error.TestFailed; 898 }, 899 else => { 900 warn("Compilation terminated unexpectedly\n"); 901 return error.TestFailed; 902 }, 903 } 904 905 const stdout = stdout_buf.toSliceConst(); 906 const stderr = stderr_buf.toSliceConst(); 907 908 if (stderr.len != 0 and !self.case.allow_warnings) { 909 warn( 910 \\====== translate-c emitted warnings: ======= 911 \\{} 912 \\============================================ 913 \\ 914 , stderr); 915 return error.TestFailed; 916 } 917 918 for (self.case.expected_lines.toSliceConst()) |expected_line| { 919 if (mem.indexOf(u8, stdout, expected_line) == null) { 920 warn( 921 \\ 922 \\========= Expected this output: ================ 923 \\{} 924 \\================================================ 925 \\{} 926 \\ 927 , expected_line, stdout); 928 return error.TestFailed; 929 } 930 } 931 warn("OK\n"); 932 } 933 }; 934 935 fn printInvocation(args: []const []const u8) void { 936 for (args) |arg| { 937 warn("{} ", arg); 938 } 939 warn("\n"); 940 } 941 942 pub fn create(self: &TranslateCContext, allow_warnings: bool, filename: []const u8, name: []const u8, 943 source: []const u8, expected_lines: ...) &TestCase 944 { 945 const tc = self.b.allocator.create(TestCase) catch unreachable; 946 *tc = TestCase { 947 .name = name, 948 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), 949 .expected_lines = ArrayList([]const u8).init(self.b.allocator), 950 .allow_warnings = allow_warnings, 951 }; 952 tc.addSourceFile(filename, source); 953 comptime var arg_i = 0; 954 inline while (arg_i < expected_lines.len) : (arg_i += 1) { 955 tc.addExpectedLine(expected_lines[arg_i]); 956 } 957 return tc; 958 } 959 960 pub fn add(self: &TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void { 961 const tc = self.create(false, "source.h", name, source, expected_lines); 962 self.addCase(tc); 963 } 964 965 pub fn addC(self: &TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void { 966 const tc = self.create(false, "source.c", name, source, expected_lines); 967 self.addCase(tc); 968 } 969 970 pub fn addAllowWarnings(self: &TranslateCContext, name: []const u8, source: []const u8, expected_lines: ...) void { 971 const tc = self.create(true, "source.h", name, source, expected_lines); 972 self.addCase(tc); 973 } 974 975 pub fn addCase(self: &TranslateCContext, case: &const TestCase) void { 976 const b = self.b; 977 978 const annotated_case_name = fmt.allocPrint(self.b.allocator, "translate-c {}", case.name) catch unreachable; 979 if (self.test_filter) |filter| { 980 if (mem.indexOf(u8, annotated_case_name, filter) == null) 981 return; 982 } 983 984 const translate_c_and_cmp = TranslateCCmpOutputStep.create(self, annotated_case_name, case); 985 self.step.dependOn(&translate_c_and_cmp.step); 986 987 for (case.sources.toSliceConst()) |src_file| { 988 const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; 989 const write_src = b.addWriteFile(expanded_src_path, src_file.source); 990 translate_c_and_cmp.step.dependOn(&write_src.step); 991 } 992 } 993 }; 994 995 pub const GenHContext = struct { 996 b: &build.Builder, 997 step: &build.Step, 998 test_index: usize, 999 test_filter: ?[]const u8, 1000 1001 const TestCase = struct { 1002 name: []const u8, 1003 sources: ArrayList(SourceFile), 1004 expected_lines: ArrayList([]const u8), 1005 1006 const SourceFile = struct { 1007 filename: []const u8, 1008 source: []const u8, 1009 }; 1010 1011 pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) void { 1012 self.sources.append(SourceFile { 1013 .filename = filename, 1014 .source = source, 1015 }) catch unreachable; 1016 } 1017 1018 pub fn addExpectedLine(self: &TestCase, text: []const u8) void { 1019 self.expected_lines.append(text) catch unreachable; 1020 } 1021 }; 1022 1023 const GenHCmpOutputStep = struct { 1024 step: build.Step, 1025 context: &GenHContext, 1026 h_path: []const u8, 1027 name: []const u8, 1028 test_index: usize, 1029 case: &const TestCase, 1030 1031 pub fn create(context: &GenHContext, h_path: []const u8, name: []const u8, case: &const TestCase) &GenHCmpOutputStep { 1032 const allocator = context.b.allocator; 1033 const ptr = allocator.create(GenHCmpOutputStep) catch unreachable; 1034 *ptr = GenHCmpOutputStep { 1035 .step = build.Step.init("ParseCCmpOutput", allocator, make), 1036 .context = context, 1037 .h_path = h_path, 1038 .name = name, 1039 .test_index = context.test_index, 1040 .case = case, 1041 }; 1042 context.test_index += 1; 1043 return ptr; 1044 } 1045 1046 fn make(step: &build.Step) !void { 1047 const self = @fieldParentPtr(GenHCmpOutputStep, "step", step); 1048 const b = self.context.b; 1049 1050 warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); 1051 1052 const full_h_path = b.pathFromRoot(self.h_path); 1053 const actual_h = try io.readFileAlloc(b.allocator, full_h_path); 1054 1055 for (self.case.expected_lines.toSliceConst()) |expected_line| { 1056 if (mem.indexOf(u8, actual_h, expected_line) == null) { 1057 warn( 1058 \\ 1059 \\========= Expected this output: ================ 1060 \\{} 1061 \\================================================ 1062 \\{} 1063 \\ 1064 , expected_line, actual_h); 1065 return error.TestFailed; 1066 } 1067 } 1068 warn("OK\n"); 1069 } 1070 }; 1071 1072 fn printInvocation(args: []const []const u8) void { 1073 for (args) |arg| { 1074 warn("{} ", arg); 1075 } 1076 warn("\n"); 1077 } 1078 1079 pub fn create(self: &GenHContext, filename: []const u8, name: []const u8, 1080 source: []const u8, expected_lines: ...) &TestCase 1081 { 1082 const tc = self.b.allocator.create(TestCase) catch unreachable; 1083 *tc = TestCase { 1084 .name = name, 1085 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator), 1086 .expected_lines = ArrayList([]const u8).init(self.b.allocator), 1087 }; 1088 tc.addSourceFile(filename, source); 1089 comptime var arg_i = 0; 1090 inline while (arg_i < expected_lines.len) : (arg_i += 1) { 1091 tc.addExpectedLine(expected_lines[arg_i]); 1092 } 1093 return tc; 1094 } 1095 1096 pub fn add(self: &GenHContext, name: []const u8, source: []const u8, expected_lines: ...) void { 1097 const tc = self.create("test.zig", name, source, expected_lines); 1098 self.addCase(tc); 1099 } 1100 1101 pub fn addCase(self: &GenHContext, case: &const TestCase) void { 1102 const b = self.b; 1103 const root_src = os.path.join(b.allocator, b.cache_root, case.sources.items[0].filename) catch unreachable; 1104 1105 const mode = builtin.Mode.Debug; 1106 const annotated_case_name = fmt.allocPrint(self.b.allocator, "gen-h {} ({})", case.name, @tagName(mode)) catch unreachable; 1107 if (self.test_filter) |filter| { 1108 if (mem.indexOf(u8, annotated_case_name, filter) == null) 1109 return; 1110 } 1111 1112 const obj = b.addObject("test", root_src); 1113 obj.setBuildMode(mode); 1114 1115 for (case.sources.toSliceConst()) |src_file| { 1116 const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; 1117 const write_src = b.addWriteFile(expanded_src_path, src_file.source); 1118 obj.step.dependOn(&write_src.step); 1119 } 1120 1121 const cmp_h = GenHCmpOutputStep.create(self, obj.getOutputHPath(), annotated_case_name, case); 1122 cmp_h.step.dependOn(&obj.step); 1123 1124 self.step.dependOn(&cmp_h.step); 1125 } 1126 };