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