| ... | @@ -215,7 +215,37 @@ const TestManifest = struct { | ... | @@ -215,7 +215,37 @@ const TestManifest = struct { |
| 215 | } | 215 | } |
| 216 | | 216 | |
| 217 | fn parse(arena: Allocator, bytes: []const u8) !TestManifest { | 217 | fn parse(arena: Allocator, bytes: []const u8) !TestManifest { |
| 218 | var it = std.mem.tokenize(u8, bytes, "\r\n"); | 218 | // The manifest is the last contiguous block of comments in the file |
| | 219 | // We scan for the beginning by searching backward for the first non-empty line that does not start with "//" |
| | 220 | var start: ?usize = null; |
| | 221 | var end: usize = bytes.len; |
| | 222 | if (bytes.len > 0) { |
| | 223 | var cursor: usize = bytes.len - 1; |
| | 224 | while (true) { |
| | 225 | // Move to beginning of line |
| | 226 | while (cursor > 0 and bytes[cursor - 1] != '\n') cursor -= 1; |
| | 227 | |
| | 228 | if (std.mem.startsWith(u8, bytes[cursor..], "//")) { |
| | 229 | start = cursor; // Contiguous comment line, include in manifest |
| | 230 | } else { |
| | 231 | if (start != null) break; // Encountered non-comment line, end of manifest |
| | 232 | |
| | 233 | // We ignore all-whitespace lines following the comment block, but anything else |
| | 234 | // means that there is no manifest present. |
| | 235 | if (std.mem.trim(u8, bytes[cursor..end], " \r\n\t").len == 0) { |
| | 236 | end = cursor; |
| | 237 | } else break; // If it's not whitespace, there is no manifest |
| | 238 | } |
| | 239 | |
| | 240 | // Move to previous line |
| | 241 | if (cursor != 0) cursor -= 1 else break; |
| | 242 | } |
| | 243 | } |
| | 244 | |
| | 245 | const actual_start = start orelse return error.MissingTestManifest; |
| | 246 | const manifest_bytes = bytes[actual_start..end]; |
| | 247 | |
| | 248 | var it = std.mem.tokenize(u8, manifest_bytes, "\r\n"); |
| 219 | | 249 | |
| 220 | // First line is the test type | 250 | // First line is the test type |
| 221 | const tt: Type = blk: { | 251 | const tt: Type = blk: { |
| ... | @@ -250,20 +280,29 @@ const TestManifest = struct { | ... | @@ -250,20 +280,29 @@ const TestManifest = struct { |
| 250 | } | 280 | } |
| 251 | | 281 | |
| 252 | // Finally, trailing is expected output | 282 | // Finally, trailing is expected output |
| 253 | manifest.trailing_bytes = bytes[it.index..]; | 283 | manifest.trailing_bytes = manifest_bytes[it.index..]; |
| 254 | | 284 | |
| 255 | return manifest; | 285 | return manifest; |
| 256 | } | 286 | } |
| 257 | | 287 | |
| 258 | fn getConfigValues( | 288 | fn getConfigForKey( |
| 259 | self: TestManifest, | 289 | self: TestManifest, |
| 260 | key: []const u8, | 290 | key: []const u8, |
| 261 | comptime T: type, | 291 | comptime T: type, |
| 262 | parse_fn: anytype, | 292 | parse_fn: fn ([]const u8) ?T, |
| 263 | ) ?ConfigValueIterator(T, @TypeOf(parse_fn)) { | 293 | ) ConfigValueIterator(T, @TypeOf(parse_fn)) { |
| 264 | const bytes = self.config_map.get(key) orelse return null; | 294 | const delimiter = ","; |
| | 295 | var inner: std.mem.SplitIterator(u8) = if (self.config_map.get(key)) |bytes| .{ |
| | 296 | .buffer = bytes, |
| | 297 | .delimiter = delimiter, |
| | 298 | .index = 0, |
| | 299 | } else .{ |
| | 300 | .buffer = undefined, |
| | 301 | .delimiter = delimiter, |
| | 302 | .index = null, |
| | 303 | }; |
| 265 | return ConfigValueIterator(T, @TypeOf(parse_fn)){ | 304 | return ConfigValueIterator(T, @TypeOf(parse_fn)){ |
| 266 | .inner = std.mem.split(u8, bytes, ","), | 305 | .inner = inner, |
| 267 | .parse_fn = parse_fn, | 306 | .parse_fn = parse_fn, |
| 268 | }; | 307 | }; |
| 269 | } | 308 | } |
| ... | @@ -958,93 +997,62 @@ pub const TestContext = struct { | ... | @@ -958,93 +997,62 @@ pub const TestContext = struct { |
| 958 | const max_file_size = 10 * 1024 * 1024; | 997 | const max_file_size = 10 * 1024 * 1024; |
| 959 | const src = try dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0); | 998 | const src = try dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0); |
| 960 | | 999 | |
| 961 | // The manifest is the last contiguous block of comments in the file | 1000 | // Parse the manifest |
| 962 | // We scan for the beginning by searching backward for the first non-empty line that does not start with "//" | 1001 | var manifest = try TestManifest.parse(ctx.arena, src); |
| 963 | var manifest_start: ?usize = null; | 1002 | const strategy = manifest.getConfigForKey("strategy", Strategy, Strategy.parse).next().?; |
| 964 | var manifest_end: usize = src.len; | 1003 | const backend = manifest.getConfigForKey("backend", Backend, Backend.parse).next().?; |
| 965 | if (src.len > 0) { | 1004 | |
| 966 | var cursor: usize = src.len - 1; | 1005 | switch (manifest.@"type") { |
| 967 | while (true) { | 1006 | .@"error" => { |
| 968 | // Move to beginning of line | 1007 | const case = opt_case orelse case: { |
| 969 | while (cursor > 0 and src[cursor - 1] != '\n') cursor -= 1; | 1008 | const case = try ctx.cases.addOne(); |
| 970 | | 1009 | case.* = .{ |
| 971 | if (std.mem.startsWith(u8, src[cursor..], "//")) { | 1010 | .name = "none", |
| 972 | manifest_start = cursor; // Contiguous comment line, include in manifest | 1011 | .target = .{}, |
| 973 | } else { | 1012 | .backend = backend, |
| 974 | if (manifest_start != null) break; // Encountered non-comment line, end of manifest | 1013 | .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator), |
| 975 | | 1014 | .is_test = false, |
| 976 | // We ignore all-whitespace lines following the comment block, but anything else | 1015 | .output_mode = .Obj, |
| 977 | // means that there is no manifest present. | 1016 | .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator), |
| 978 | if (std.mem.trim(u8, src[cursor..manifest_end], " \r\n\t").len == 0) { | | |
| 979 | manifest_end = cursor; | | |
| 980 | } else break; // If it's not whitespace, there is no manifest | | |
| 981 | } | | |
| 982 | | | |
| 983 | // Move to previous line | | |
| 984 | if (cursor != 0) cursor -= 1 else break; | | |
| 985 | } | | |
| 986 | } | | |
| 987 | | | |
| 988 | if (manifest_start) |start| { | | |
| 989 | // Parse the manifest | | |
| 990 | var mani = try TestManifest.parse(ctx.arena, src[start..manifest_end]); | | |
| 991 | const strategy = mani.getConfigValues("strategy", Strategy, Strategy.parse).?.next().?; | | |
| 992 | const backend = mani.getConfigValues("backend", Backend, Backend.parse).?.next().?; | | |
| 993 | | | |
| 994 | switch (mani.@"type") { | | |
| 995 | .@"error" => { | | |
| 996 | const case = opt_case orelse case: { | | |
| 997 | const case = try ctx.cases.addOne(); | | |
| 998 | case.* = .{ | | |
| 999 | .name = "none", | | |
| 1000 | .target = .{}, | | |
| 1001 | .backend = backend, | | |
| 1002 | .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator), | | |
| 1003 | .is_test = false, | | |
| 1004 | .output_mode = .Obj, | | |
| 1005 | .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator), | | |
| 1006 | }; | | |
| 1007 | opt_case = case; | | |
| 1008 | break :case case; | | |
| 1009 | }; | 1017 | }; |
| 1010 | const errors = try mani.trailingAlloc(ctx.arena); | 1018 | opt_case = case; |
| | 1019 | break :case case; |
| | 1020 | }; |
| | 1021 | const errors = try manifest.trailingAlloc(ctx.arena); |
| 1011 | | 1022 | |
| 1012 | switch (strategy) { | 1023 | switch (strategy) { |
| 1013 | .independent => { | 1024 | .independent => { |
| 1014 | case.addError(src, errors); | 1025 | case.addError(src, errors); |
| 1015 | }, | 1026 | }, |
| 1016 | .incremental => { | 1027 | .incremental => { |
| 1017 | case.addErrorNamed("update", src, errors); | 1028 | case.addErrorNamed("update", src, errors); |
| 1018 | }, | 1029 | }, |
| 1019 | } | 1030 | } |
| 1020 | }, | 1031 | }, |
| 1021 | .run => { | 1032 | .run => { |
| 1022 | const case = opt_case orelse case: { | 1033 | const case = opt_case orelse case: { |
| 1023 | const case = try ctx.cases.addOne(); | 1034 | const case = try ctx.cases.addOne(); |
| 1024 | case.* = .{ | 1035 | case.* = .{ |
| 1025 | .name = "none", | 1036 | .name = "none", |
| 1026 | .target = .{}, | 1037 | .target = .{}, |
| 1027 | .backend = backend, | 1038 | .backend = backend, |
| 1028 | .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator), | 1039 | .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator), |
| 1029 | .is_test = false, | 1040 | .is_test = false, |
| 1030 | .output_mode = .Exe, | 1041 | .output_mode = .Exe, |
| 1031 | .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator), | 1042 | .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator), |
| 1032 | }; | | |
| 1033 | opt_case = case; | | |
| 1034 | break :case case; | | |
| 1035 | }; | 1043 | }; |
| | 1044 | opt_case = case; |
| | 1045 | break :case case; |
| | 1046 | }; |
| 1036 | | 1047 | |
| 1037 | var output = std.ArrayList(u8).init(ctx.arena); | 1048 | var output = std.ArrayList(u8).init(ctx.arena); |
| 1038 | var trailing_it = mani.trailing(); | 1049 | var trailing_it = manifest.trailing(); |
| 1039 | while (trailing_it.next()) |line| { | 1050 | while (trailing_it.next()) |line| { |
| 1040 | try output.appendSlice(line); | 1051 | try output.appendSlice(line); |
| 1041 | } | 1052 | } |
| 1042 | case.addCompareOutput(src, output.toOwnedSlice()); | 1053 | case.addCompareOutput(src, output.toOwnedSlice()); |
| 1043 | }, | 1054 | }, |
| 1044 | .cli => @panic("TODO cli tests"), | 1055 | .cli => @panic("TODO cli tests"), |
| 1045 | } | | |
| 1046 | } else { | | |
| 1047 | return error.MissingManifest; | | |
| 1048 | } | 1056 | } |
| 1049 | } | 1057 | } |
| 1050 | } | 1058 | } |