authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-04-26 22:55:11+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-04-28 18:35:01+02:00
log46db5e2a44b38c92e97a438c4b6a67627cc12d16
tree0e0ea203a5af85654647f5aecee644e35a364e9d
parentbc370311cb76f570debf38f5d822a268f1dace83

test: unroll into multiple cases, provide default parsers

Provide default parsers for obvious config options such as `CrossTarget` or `Backend` (or any enum for that matter). Unroll iterator loops into multiple cases - we need to create a Cartesian product for all possibilities specified in the test manifest.

2 files changed, 98 insertions(+), 70 deletions(-)

src/test.zig+98-69
......@@ -237,10 +237,10 @@ const TestManifest = struct {
237237 }
238238 };
239239
240 fn ConfigValueIterator(comptime T: type, comptime ParseFn: type) type {
240 fn ConfigValueIterator(comptime T: type) type {
241241 return struct {
242242 inner: std.mem.SplitIterator(u8),
243 parse_fn: ParseFn,
243 parse_fn: ParseFn(T),
244244
245245 fn next(self: *@This()) ?T {
246246 const next_raw = self.inner.next() orelse return null;
......@@ -320,28 +320,34 @@ const TestManifest = struct {
320320 return manifest;
321321 }
322322
323 fn getConfigForKey(
323 fn getConfigForKeyCustomParser(
324324 self: TestManifest,
325325 key: []const u8,
326326 comptime T: type,
327 parse_fn: fn ([]const u8) ?T,
328 ) ConfigValueIterator(T, @TypeOf(parse_fn)) {
329 const delimiter = ",";
330 var inner: std.mem.SplitIterator(u8) = if (self.config_map.get(key)) |bytes| .{
331 .buffer = bytes,
332 .delimiter = delimiter,
333 .index = 0,
334 } else .{
335 .buffer = undefined,
336 .delimiter = delimiter,
337 .index = null,
338 };
339 return ConfigValueIterator(T, @TypeOf(parse_fn)){
340 .inner = inner,
327 parse_fn: ParseFn(T),
328 ) ConfigValueIterator(T) {
329 const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.@"type", key);
330 return ConfigValueIterator(T){
331 .inner = std.mem.split(u8, bytes, ","),
341332 .parse_fn = parse_fn,
342333 };
343334 }
344335
336 fn getConfigForKey(
337 self: TestManifest,
338 key: []const u8,
339 comptime T: type,
340 ) ConfigValueIterator(T) {
341 return self.getConfigForKeyCustomParser(key, T, getDefaultParser(T));
342 }
343
344 fn getConfigForKeyAssertSingle(self: TestManifest, key: []const u8, comptime T: type) T {
345 var it = self.getConfigForKey(key, T);
346 const res = it.next().?;
347 assert(it.next() == null);
348 return res;
349 }
350
345351 fn trailing(self: TestManifest) TrailingIterator {
346352 return .{
347353 .inner = std.mem.tokenize(u8, self.trailing_bytes, "\r\n"),
......@@ -356,6 +362,40 @@ const TestManifest = struct {
356362 }
357363 return out.toOwnedSlice();
358364 }
365
366 fn ParseFn(comptime T: type) type {
367 return fn ([]const u8) ?T;
368 }
369
370 fn getDefaultParser(comptime T: type) ParseFn(T) {
371 switch (@typeInfo(T)) {
372 .Int => return struct {
373 fn parse(str: []const u8) ?T {
374 return std.fmt.parseInt(T, str, 0) catch null;
375 }
376 }.parse,
377 .Bool => return struct {
378 fn parse(str: []const u8) ?T {
379 const as_int = std.fmt.parseInt(u1, str, 0) catch return null;
380 return as_int > 0;
381 }
382 }.parse,
383 .Enum => return struct {
384 fn parse(str: []const u8) ?T {
385 return std.meta.stringToEnum(T, str);
386 }
387 }.parse,
388 .Struct => if (comptime std.mem.eql(u8, @typeName(T), "CrossTarget")) return struct {
389 fn parse(str: []const u8) ?T {
390 var opts = CrossTarget.ParseOptions{
391 .arch_os_abi = str,
392 };
393 return CrossTarget.parse(opts) catch null;
394 }
395 }.parse else @compileError("no default parser for " ++ @typeName(T)),
396 else => @compileError("no default parser for " ++ @typeName(T)),
397 }
398 }
359399};
360400
361401pub const TestContext = struct {
......@@ -401,10 +441,6 @@ pub const TestContext = struct {
401441 stage1,
402442 stage2,
403443 llvm,
404
405 fn parse(str: []const u8) ?Backend {
406 return std.meta.stringToEnum(Backend, str);
407 }
408444 };
409445
410446 /// A `Case` consists of a list of `Update`. The same `Compilation` is used for each
......@@ -899,7 +935,7 @@ pub const TestContext = struct {
899935
900936 pub fn addTestCasesFromDir(ctx: *TestContext, dir: std.fs.Dir, strategy: Strategy) void {
901937 var current_file: []const u8 = "none";
902 addTestCasesFromDirInner(ctx, dir, strategy, &current_file) catch |err| {
938 ctx.addTestCasesFromDirInner(dir, strategy, &current_file) catch |err| {
903939 std.debug.panic("test harness failed to process file '{s}': {s}\n", .{
904940 current_file, @errorName(err),
905941 });
......@@ -974,11 +1010,10 @@ pub const TestContext = struct {
9741010 /// that if any errors occur the caller knows it happened during this file.
9751011 current_file: *[]const u8,
9761012 ) !void {
977 var opt_case: ?*Case = null;
1013 var cases = std.ArrayList(*Case).init(ctx.arena);
9781014
9791015 var it = dir.iterate();
9801016 var filenames = std.ArrayList([]const u8).init(ctx.arena);
981 defer filenames.deinit();
9821017
9831018 while (try it.next()) |entry| {
9841019 if (entry.kind != .File) continue;
......@@ -1021,7 +1056,7 @@ pub const TestContext = struct {
10211056 if (new_parts.test_index != null and new_parts.test_index.? != 0) return error.InvalidIncrementalTestIndex;
10221057
10231058 if (strategy == .independent)
1024 opt_case = null; // Generate a new independent test case for this update
1059 cases.clearRetainingCapacity(); // Generate a new independent test case for this update
10251060 }
10261061 }
10271062 prev_filename = filename;
......@@ -1032,59 +1067,53 @@ pub const TestContext = struct {
10321067 // Parse the manifest
10331068 var manifest = try TestManifest.parse(ctx.arena, src);
10341069
1035 switch (manifest.@"type") {
1036 .@"error" => {
1037 const case = opt_case orelse case: {
1038 const case = try ctx.cases.addOne();
1039 const backend = manifest.getConfigForKey("backend", Backend, Backend.parse).next().?;
1040 case.* = .{
1041 .name = "none",
1042 .target = .{},
1043 .backend = backend,
1044 .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator),
1045 .is_test = false,
1046 .output_mode = .Obj,
1047 .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator),
1048 };
1049 opt_case = case;
1050 break :case case;
1051 };
1052 const errors = try manifest.trailingAlloc(ctx.arena);
1070 if (cases.items.len == 0) {
1071 var backends = manifest.getConfigForKey("backend", Backend);
1072 var targets = manifest.getConfigForKey("target", CrossTarget);
1073 const is_test = manifest.getConfigForKeyAssertSingle("is_test", bool);
1074 const output_mode = manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode);
10531075
1054 switch (strategy) {
1055 .independent => {
1056 case.addError(src, errors);
1057 },
1058 .incremental => {
1059 case.addErrorNamed("update", src, errors);
1060 },
1061 }
1062 },
1063 .run => {
1064 const case = opt_case orelse case: {
1076 // Cross-product to get all possible test combinations
1077 while (backends.next()) |backend| {
1078 while (targets.next()) |target| {
10651079 const case = try ctx.cases.addOne();
1066 const backend = manifest.getConfigForKey("backend", Backend, Backend.parse).next().?;
10671080 case.* = .{
10681081 .name = "none",
1069 .target = .{},
1082 .target = target,
10701083 .backend = backend,
10711084 .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator),
1072 .is_test = false,
1073 .output_mode = .Exe,
1085 .is_test = is_test,
1086 .output_mode = output_mode,
10741087 .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator),
10751088 };
1076 opt_case = case;
1077 break :case case;
1078 };
1079
1080 var output = std.ArrayList(u8).init(ctx.arena);
1081 var trailing_it = manifest.trailing();
1082 while (trailing_it.next()) |line| {
1083 try output.appendSlice(line);
1089 try cases.append(case);
10841090 }
1085 case.addCompareOutput(src, output.toOwnedSlice());
1086 },
1087 .cli => @panic("TODO cli tests"),
1091 }
1092 }
1093
1094 for (cases.items) |case| {
1095 switch (manifest.@"type") {
1096 .@"error" => {
1097 const errors = try manifest.trailingAlloc(ctx.arena);
1098 switch (strategy) {
1099 .independent => {
1100 case.addError(src, errors);
1101 },
1102 .incremental => {
1103 case.addErrorNamed("update", src, errors);
1104 },
1105 }
1106 },
1107 .run => {
1108 var output = std.ArrayList(u8).init(ctx.arena);
1109 var trailing_it = manifest.trailing();
1110 while (trailing_it.next()) |line| {
1111 try output.appendSlice(line);
1112 }
1113 case.addCompareOutput(src, output.toOwnedSlice());
1114 },
1115 .cli => @panic("TODO cli tests"),
1116 }
10881117 }
10891118 }
10901119 }
test/incremental/add.0.zig-1
......@@ -7,5 +7,4 @@ fn add(a: u32, b: u32) void {
77}
88
99// run
10// backend=stage2
1110//