| ... | @@ -89,6 +89,8 @@ has_side_effects: bool, | ... | @@ -89,6 +89,8 @@ has_side_effects: bool, |
| 89 | /// If this is a Zig unit test binary, this tracks the indexes of the unit | 89 | /// If this is a Zig unit test binary, this tracks the indexes of the unit |
| 90 | /// tests that are also fuzz tests. | 90 | /// tests that are also fuzz tests. |
| 91 | fuzz_tests: std.ArrayListUnmanaged(u32), | 91 | fuzz_tests: std.ArrayListUnmanaged(u32), |
| | 92 | cached_test_metadata: ?CachedTestMetadata = null, |
| | 93 | |
| 92 | /// Populated during the fuzz phase if this run step corresponds to a unit test | 94 | /// Populated during the fuzz phase if this run step corresponds to a unit test |
| 93 | /// executable that contains fuzz tests. | 95 | /// executable that contains fuzz tests. |
| 94 | rebuilt_executable: ?[]const u8, | 96 | rebuilt_executable: ?[]const u8, |
| ... | @@ -754,7 +756,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { | ... | @@ -754,7 +756,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 754 | b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path }); | 756 | b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path }); |
| 755 | } | 757 | } |
| 756 | | 758 | |
| 757 | try runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node); | 759 | try runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node, null); |
| 758 | if (!has_side_effects) try step.writeManifestAndWatch(&man); | 760 | if (!has_side_effects) try step.writeManifestAndWatch(&man); |
| 759 | return; | 761 | return; |
| 760 | }; | 762 | }; |
| ... | @@ -784,7 +786,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { | ... | @@ -784,7 +786,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 784 | b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path }); | 786 | b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path }); |
| 785 | } | 787 | } |
| 786 | | 788 | |
| 787 | try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node); | 789 | try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, null); |
| 788 | | 790 | |
| 789 | const dep_file_dir = std.fs.cwd(); | 791 | const dep_file_dir = std.fs.cwd(); |
| 790 | const dep_file_basename = dep_output_file.generated_file.getPath(); | 792 | const dep_file_basename = dep_output_file.generated_file.getPath(); |
| ... | @@ -843,6 +845,38 @@ fn make(step: *Step, options: Step.MakeOptions) !void { | ... | @@ -843,6 +845,38 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 843 | ); | 845 | ); |
| 844 | } | 846 | } |
| 845 | | 847 | |
| | 848 | pub fn rerunInFuzzMode(run: *Run, unit_test_index: u32, prog_node: std.Progress.Node) !void { |
| | 849 | const step = &run.step; |
| | 850 | const b = step.owner; |
| | 851 | const arena = b.allocator; |
| | 852 | var argv_list: std.ArrayListUnmanaged([]const u8) = .{}; |
| | 853 | for (run.argv.items) |arg| { |
| | 854 | switch (arg) { |
| | 855 | .bytes => |bytes| { |
| | 856 | try argv_list.append(arena, bytes); |
| | 857 | }, |
| | 858 | .lazy_path => |file| { |
| | 859 | const file_path = file.lazy_path.getPath2(b, step); |
| | 860 | try argv_list.append(arena, b.fmt("{s}{s}", .{ file.prefix, file_path })); |
| | 861 | }, |
| | 862 | .directory_source => |file| { |
| | 863 | const file_path = file.lazy_path.getPath2(b, step); |
| | 864 | try argv_list.append(arena, b.fmt("{s}{s}", .{ file.prefix, file_path })); |
| | 865 | }, |
| | 866 | .artifact => |pa| { |
| | 867 | const artifact = pa.artifact; |
| | 868 | const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?; |
| | 869 | try argv_list.append(arena, b.fmt("{s}{s}", .{ pa.prefix, file_path })); |
| | 870 | }, |
| | 871 | .output_file, .output_directory => unreachable, |
| | 872 | } |
| | 873 | } |
| | 874 | const has_side_effects = false; |
| | 875 | const rand_int = std.crypto.random.int(u64); |
| | 876 | const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); |
| | 877 | try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, unit_test_index); |
| | 878 | } |
| | 879 | |
| 846 | fn populateGeneratedPaths( | 880 | fn populateGeneratedPaths( |
| 847 | arena: std.mem.Allocator, | 881 | arena: std.mem.Allocator, |
| 848 | output_placeholders: []const IndexedOutput, | 882 | output_placeholders: []const IndexedOutput, |
| ... | @@ -921,6 +955,7 @@ fn runCommand( | ... | @@ -921,6 +955,7 @@ fn runCommand( |
| 921 | has_side_effects: bool, | 955 | has_side_effects: bool, |
| 922 | output_dir_path: []const u8, | 956 | output_dir_path: []const u8, |
| 923 | prog_node: std.Progress.Node, | 957 | prog_node: std.Progress.Node, |
| | 958 | fuzz_unit_test_index: ?u32, |
| 924 | ) !void { | 959 | ) !void { |
| 925 | const step = &run.step; | 960 | const step = &run.step; |
| 926 | const b = step.owner; | 961 | const b = step.owner; |
| ... | @@ -939,7 +974,7 @@ fn runCommand( | ... | @@ -939,7 +974,7 @@ fn runCommand( |
| 939 | var interp_argv = std.ArrayList([]const u8).init(b.allocator); | 974 | var interp_argv = std.ArrayList([]const u8).init(b.allocator); |
| 940 | defer interp_argv.deinit(); | 975 | defer interp_argv.deinit(); |
| 941 | | 976 | |
| 942 | const result = spawnChildAndCollect(run, argv, has_side_effects, prog_node) catch |err| term: { | 977 | const result = spawnChildAndCollect(run, argv, has_side_effects, prog_node, fuzz_unit_test_index) catch |err| term: { |
| 943 | // InvalidExe: cpu arch mismatch | 978 | // InvalidExe: cpu arch mismatch |
| 944 | // FileNotFound: can happen with a wrong dynamic linker path | 979 | // FileNotFound: can happen with a wrong dynamic linker path |
| 945 | if (err == error.InvalidExe or err == error.FileNotFound) interpret: { | 980 | if (err == error.InvalidExe or err == error.FileNotFound) interpret: { |
| ... | @@ -1075,7 +1110,7 @@ fn runCommand( | ... | @@ -1075,7 +1110,7 @@ fn runCommand( |
| 1075 | | 1110 | |
| 1076 | try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items); | 1111 | try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items); |
| 1077 | | 1112 | |
| 1078 | break :term spawnChildAndCollect(run, interp_argv.items, has_side_effects, prog_node) catch |e| { | 1113 | break :term spawnChildAndCollect(run, interp_argv.items, has_side_effects, prog_node, fuzz_unit_test_index) catch |e| { |
| 1079 | if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; | 1114 | if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; |
| 1080 | | 1115 | |
| 1081 | return step.fail("unable to spawn interpreter {s}: {s}", .{ | 1116 | return step.fail("unable to spawn interpreter {s}: {s}", .{ |
| ... | @@ -1090,6 +1125,15 @@ fn runCommand( | ... | @@ -1090,6 +1125,15 @@ fn runCommand( |
| 1090 | step.result_duration_ns = result.elapsed_ns; | 1125 | step.result_duration_ns = result.elapsed_ns; |
| 1091 | step.result_peak_rss = result.peak_rss; | 1126 | step.result_peak_rss = result.peak_rss; |
| 1092 | step.test_results = result.stdio.test_results; | 1127 | step.test_results = result.stdio.test_results; |
| | 1128 | if (result.stdio.test_metadata) |tm| |
| | 1129 | run.cached_test_metadata = tm.toCachedTestMetadata(); |
| | 1130 | |
| | 1131 | const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items; |
| | 1132 | |
| | 1133 | if (fuzz_unit_test_index != null) { |
| | 1134 | try step.handleChildProcessTerm(result.term, cwd, final_argv); |
| | 1135 | return; |
| | 1136 | } |
| 1093 | | 1137 | |
| 1094 | // Capture stdout and stderr to GeneratedFile objects. | 1138 | // Capture stdout and stderr to GeneratedFile objects. |
| 1095 | const Stream = struct { | 1139 | const Stream = struct { |
| ... | @@ -1126,8 +1170,6 @@ fn runCommand( | ... | @@ -1126,8 +1170,6 @@ fn runCommand( |
| 1126 | } | 1170 | } |
| 1127 | } | 1171 | } |
| 1128 | | 1172 | |
| 1129 | const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items; | | |
| 1130 | | | |
| 1131 | switch (run.stdio) { | 1173 | switch (run.stdio) { |
| 1132 | .check => |checks| for (checks.items) |check| switch (check) { | 1174 | .check => |checks| for (checks.items) |check| switch (check) { |
| 1133 | .expect_stderr_exact => |expected_bytes| { | 1175 | .expect_stderr_exact => |expected_bytes| { |
| ... | @@ -1253,10 +1295,16 @@ fn spawnChildAndCollect( | ... | @@ -1253,10 +1295,16 @@ fn spawnChildAndCollect( |
| 1253 | argv: []const []const u8, | 1295 | argv: []const []const u8, |
| 1254 | has_side_effects: bool, | 1296 | has_side_effects: bool, |
| 1255 | prog_node: std.Progress.Node, | 1297 | prog_node: std.Progress.Node, |
| | 1298 | fuzz_unit_test_index: ?u32, |
| 1256 | ) !ChildProcResult { | 1299 | ) !ChildProcResult { |
| 1257 | const b = run.step.owner; | 1300 | const b = run.step.owner; |
| 1258 | const arena = b.allocator; | 1301 | const arena = b.allocator; |
| 1259 | | 1302 | |
| | 1303 | if (fuzz_unit_test_index != null) { |
| | 1304 | assert(!has_side_effects); |
| | 1305 | assert(run.stdio == .zig_test); |
| | 1306 | } |
| | 1307 | |
| 1260 | var child = std.process.Child.init(argv, arena); | 1308 | var child = std.process.Child.init(argv, arena); |
| 1261 | if (run.cwd) |lazy_cwd| { | 1309 | if (run.cwd) |lazy_cwd| { |
| 1262 | child.cwd = lazy_cwd.getPath2(b, &run.step); | 1310 | child.cwd = lazy_cwd.getPath2(b, &run.step); |
| ... | @@ -1306,7 +1354,7 @@ fn spawnChildAndCollect( | ... | @@ -1306,7 +1354,7 @@ fn spawnChildAndCollect( |
| 1306 | var timer = try std.time.Timer.start(); | 1354 | var timer = try std.time.Timer.start(); |
| 1307 | | 1355 | |
| 1308 | const result = if (run.stdio == .zig_test) | 1356 | const result = if (run.stdio == .zig_test) |
| 1309 | evalZigTest(run, &child, prog_node) | 1357 | evalZigTest(run, &child, prog_node, fuzz_unit_test_index) |
| 1310 | else | 1358 | else |
| 1311 | evalGeneric(run, &child); | 1359 | evalGeneric(run, &child); |
| 1312 | | 1360 | |
| ... | @@ -1332,6 +1380,7 @@ fn evalZigTest( | ... | @@ -1332,6 +1380,7 @@ fn evalZigTest( |
| 1332 | run: *Run, | 1380 | run: *Run, |
| 1333 | child: *std.process.Child, | 1381 | child: *std.process.Child, |
| 1334 | prog_node: std.Progress.Node, | 1382 | prog_node: std.Progress.Node, |
| | 1383 | fuzz_unit_test_index: ?u32, |
| 1335 | ) !StdIoResult { | 1384 | ) !StdIoResult { |
| 1336 | const gpa = run.step.owner.allocator; | 1385 | const gpa = run.step.owner.allocator; |
| 1337 | const arena = run.step.owner.allocator; | 1386 | const arena = run.step.owner.allocator; |
| ... | @@ -1342,7 +1391,12 @@ fn evalZigTest( | ... | @@ -1342,7 +1391,12 @@ fn evalZigTest( |
| 1342 | }); | 1391 | }); |
| 1343 | defer poller.deinit(); | 1392 | defer poller.deinit(); |
| 1344 | | 1393 | |
| 1345 | try sendMessage(child.stdin.?, .query_test_metadata); | 1394 | if (fuzz_unit_test_index) |index| { |
| | 1395 | try sendRunTestMessage(child.stdin.?, .start_fuzzing, index); |
| | 1396 | } else { |
| | 1397 | run.fuzz_tests.clearRetainingCapacity(); |
| | 1398 | try sendMessage(child.stdin.?, .query_test_metadata); |
| | 1399 | } |
| 1346 | | 1400 | |
| 1347 | const Header = std.zig.Server.Message.Header; | 1401 | const Header = std.zig.Server.Message.Header; |
| 1348 | | 1402 | |
| ... | @@ -1360,8 +1414,6 @@ fn evalZigTest( | ... | @@ -1360,8 +1414,6 @@ fn evalZigTest( |
| 1360 | var sub_prog_node: ?std.Progress.Node = null; | 1414 | var sub_prog_node: ?std.Progress.Node = null; |
| 1361 | defer if (sub_prog_node) |n| n.end(); | 1415 | defer if (sub_prog_node) |n| n.end(); |
| 1362 | | 1416 | |
| 1363 | run.fuzz_tests.clearRetainingCapacity(); | | |
| 1364 | | | |
| 1365 | poll: while (true) { | 1417 | poll: while (true) { |
| 1366 | while (stdout.readableLength() < @sizeOf(Header)) { | 1418 | while (stdout.readableLength() < @sizeOf(Header)) { |
| 1367 | if (!(try poller.poll())) break :poll; | 1419 | if (!(try poller.poll())) break :poll; |
| ... | @@ -1382,6 +1434,7 @@ fn evalZigTest( | ... | @@ -1382,6 +1434,7 @@ fn evalZigTest( |
| 1382 | } | 1434 | } |
| 1383 | }, | 1435 | }, |
| 1384 | .test_metadata => { | 1436 | .test_metadata => { |
| | 1437 | assert(fuzz_unit_test_index == null); |
| 1385 | const TmHdr = std.zig.Server.Message.TestMetadata; | 1438 | const TmHdr = std.zig.Server.Message.TestMetadata; |
| 1386 | const tm_hdr = @as(*align(1) const TmHdr, @ptrCast(body)); | 1439 | const tm_hdr = @as(*align(1) const TmHdr, @ptrCast(body)); |
| 1387 | test_count = tm_hdr.tests_len; | 1440 | test_count = tm_hdr.tests_len; |
| ... | @@ -1410,6 +1463,7 @@ fn evalZigTest( | ... | @@ -1410,6 +1463,7 @@ fn evalZigTest( |
| 1410 | try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); | 1463 | try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); |
| 1411 | }, | 1464 | }, |
| 1412 | .test_results => { | 1465 | .test_results => { |
| | 1466 | assert(fuzz_unit_test_index == null); |
| 1413 | const md = metadata.?; | 1467 | const md = metadata.?; |
| 1414 | | 1468 | |
| 1415 | const TrHdr = std.zig.Server.Message.TestResults; | 1469 | const TrHdr = std.zig.Server.Message.TestResults; |
| ... | @@ -1479,7 +1533,23 @@ const TestMetadata = struct { | ... | @@ -1479,7 +1533,23 @@ const TestMetadata = struct { |
| 1479 | next_index: u32, | 1533 | next_index: u32, |
| 1480 | prog_node: std.Progress.Node, | 1534 | prog_node: std.Progress.Node, |
| 1481 | | 1535 | |
| | 1536 | fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata { |
| | 1537 | return .{ |
| | 1538 | .names = tm.names, |
| | 1539 | .string_bytes = tm.string_bytes, |
| | 1540 | }; |
| | 1541 | } |
| | 1542 | |
| 1482 | fn testName(tm: TestMetadata, index: u32) []const u8 { | 1543 | fn testName(tm: TestMetadata, index: u32) []const u8 { |
| | 1544 | return tm.toCachedTestMetadata().testName(index); |
| | 1545 | } |
| | 1546 | }; |
| | 1547 | |
| | 1548 | pub const CachedTestMetadata = struct { |
| | 1549 | names: []const u32, |
| | 1550 | string_bytes: []const u8, |
| | 1551 | |
| | 1552 | pub fn testName(tm: CachedTestMetadata, index: u32) []const u8 { |
| 1483 | return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0); | 1553 | return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0); |
| 1484 | } | 1554 | } |
| 1485 | }; | 1555 | }; |
| ... | @@ -1495,7 +1565,7 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr | ... | @@ -1495,7 +1565,7 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr |
| 1495 | if (sub_prog_node.*) |n| n.end(); | 1565 | if (sub_prog_node.*) |n| n.end(); |
| 1496 | sub_prog_node.* = metadata.prog_node.start(name, 0); | 1566 | sub_prog_node.* = metadata.prog_node.start(name, 0); |
| 1497 | | 1567 | |
| 1498 | try sendRunTestMessage(in, i); | 1568 | try sendRunTestMessage(in, .run_test, i); |
| 1499 | return; | 1569 | return; |
| 1500 | } else { | 1570 | } else { |
| 1501 | try sendMessage(in, .exit); | 1571 | try sendMessage(in, .exit); |
| ... | @@ -1510,9 +1580,9 @@ fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { | ... | @@ -1510,9 +1580,9 @@ fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { |
| 1510 | try file.writeAll(std.mem.asBytes(&header)); | 1580 | try file.writeAll(std.mem.asBytes(&header)); |
| 1511 | } | 1581 | } |
| 1512 | | 1582 | |
| 1513 | fn sendRunTestMessage(file: std.fs.File, index: u32) !void { | 1583 | fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index: u32) !void { |
| 1514 | const header: std.zig.Client.Message.Header = .{ | 1584 | const header: std.zig.Client.Message.Header = .{ |
| 1515 | .tag = .run_test, | 1585 | .tag = tag, |
| 1516 | .bytes_len = 4, | 1586 | .bytes_len = 4, |
| 1517 | }; | 1587 | }; |
| 1518 | const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index); | 1588 | const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index); |