| ... | ... | @@ -215,7 +215,37 @@ const TestManifest = struct { |
| 215 | 215 | } |
| 216 | 216 | |
| 217 | 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 | 250 | // First line is the test type |
| 221 | 251 | const tt: Type = blk: { |
| ... | ... | @@ -250,20 +280,29 @@ const TestManifest = struct { |
| 250 | 280 | } |
| 251 | 281 | |
| 252 | 282 | // Finally, trailing is expected output |
| 253 | | manifest.trailing_bytes = bytes[it.index..]; |
| 283 | manifest.trailing_bytes = manifest_bytes[it.index..]; |
| 254 | 284 | |
| 255 | 285 | return manifest; |
| 256 | 286 | } |
| 257 | 287 | |
| 258 | | fn getConfigValues( |
| 288 | fn getConfigForKey( |
| 259 | 289 | self: TestManifest, |
| 260 | 290 | key: []const u8, |
| 261 | 291 | comptime T: type, |
| 262 | | parse_fn: anytype, |
| 263 | | ) ?ConfigValueIterator(T, @TypeOf(parse_fn)) { |
| 264 | | const bytes = self.config_map.get(key) orelse return null; |
| 292 | parse_fn: fn ([]const u8) ?T, |
| 293 | ) ConfigValueIterator(T, @TypeOf(parse_fn)) { |
| 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 | 304 | return ConfigValueIterator(T, @TypeOf(parse_fn)){ |
| 266 | | .inner = std.mem.split(u8, bytes, ","), |
| 305 | .inner = inner, |
| 267 | 306 | .parse_fn = parse_fn, |
| 268 | 307 | }; |
| 269 | 308 | } |
| ... | ... | @@ -958,93 +997,62 @@ pub const TestContext = struct { |
| 958 | 997 | const max_file_size = 10 * 1024 * 1024; |
| 959 | 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 |
| 962 | | // We scan for the beginning by searching backward for the first non-empty line that does not start with "//" |
| 963 | | var manifest_start: ?usize = null; |
| 964 | | var manifest_end: usize = src.len; |
| 965 | | if (src.len > 0) { |
| 966 | | var cursor: usize = src.len - 1; |
| 967 | | while (true) { |
| 968 | | // Move to beginning of line |
| 969 | | while (cursor > 0 and src[cursor - 1] != '\n') cursor -= 1; |
| 970 | | |
| 971 | | if (std.mem.startsWith(u8, src[cursor..], "//")) { |
| 972 | | manifest_start = cursor; // Contiguous comment line, include in manifest |
| 973 | | } else { |
| 974 | | if (manifest_start != null) break; // Encountered non-comment line, end of manifest |
| 975 | | |
| 976 | | // We ignore all-whitespace lines following the comment block, but anything else |
| 977 | | // means that there is no manifest present. |
| 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; |
| 1000 | // Parse the manifest |
| 1001 | var manifest = try TestManifest.parse(ctx.arena, src); |
| 1002 | const strategy = manifest.getConfigForKey("strategy", Strategy, Strategy.parse).next().?; |
| 1003 | const backend = manifest.getConfigForKey("backend", Backend, Backend.parse).next().?; |
| 1004 | |
| 1005 | switch (manifest.@"type") { |
| 1006 | .@"error" => { |
| 1007 | const case = opt_case orelse case: { |
| 1008 | const case = try ctx.cases.addOne(); |
| 1009 | case.* = .{ |
| 1010 | .name = "none", |
| 1011 | .target = .{}, |
| 1012 | .backend = backend, |
| 1013 | .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator), |
| 1014 | .is_test = false, |
| 1015 | .output_mode = .Obj, |
| 1016 | .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator), |
| 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) { |
| 1013 | | .independent => { |
| 1014 | | case.addError(src, errors); |
| 1015 | | }, |
| 1016 | | .incremental => { |
| 1017 | | case.addErrorNamed("update", src, errors); |
| 1018 | | }, |
| 1019 | | } |
| 1020 | | }, |
| 1021 | | .run => { |
| 1022 | | const case = opt_case orelse case: { |
| 1023 | | const case = try ctx.cases.addOne(); |
| 1024 | | case.* = .{ |
| 1025 | | .name = "none", |
| 1026 | | .target = .{}, |
| 1027 | | .backend = backend, |
| 1028 | | .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator), |
| 1029 | | .is_test = false, |
| 1030 | | .output_mode = .Exe, |
| 1031 | | .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator), |
| 1032 | | }; |
| 1033 | | opt_case = case; |
| 1034 | | break :case case; |
| 1023 | switch (strategy) { |
| 1024 | .independent => { |
| 1025 | case.addError(src, errors); |
| 1026 | }, |
| 1027 | .incremental => { |
| 1028 | case.addErrorNamed("update", src, errors); |
| 1029 | }, |
| 1030 | } |
| 1031 | }, |
| 1032 | .run => { |
| 1033 | const case = opt_case orelse case: { |
| 1034 | const case = try ctx.cases.addOne(); |
| 1035 | case.* = .{ |
| 1036 | .name = "none", |
| 1037 | .target = .{}, |
| 1038 | .backend = backend, |
| 1039 | .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator), |
| 1040 | .is_test = false, |
| 1041 | .output_mode = .Exe, |
| 1042 | .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator), |
| 1035 | 1043 | }; |
| 1044 | opt_case = case; |
| 1045 | break :case case; |
| 1046 | }; |
| 1036 | 1047 | |
| 1037 | | var output = std.ArrayList(u8).init(ctx.arena); |
| 1038 | | var trailing_it = mani.trailing(); |
| 1039 | | while (trailing_it.next()) |line| { |
| 1040 | | try output.appendSlice(line); |
| 1041 | | } |
| 1042 | | case.addCompareOutput(src, output.toOwnedSlice()); |
| 1043 | | }, |
| 1044 | | .cli => @panic("TODO cli tests"), |
| 1045 | | } |
| 1046 | | } else { |
| 1047 | | return error.MissingManifest; |
| 1048 | var output = std.ArrayList(u8).init(ctx.arena); |
| 1049 | var trailing_it = manifest.trailing(); |
| 1050 | while (trailing_it.next()) |line| { |
| 1051 | try output.appendSlice(line); |
| 1052 | } |
| 1053 | case.addCompareOutput(src, output.toOwnedSlice()); |
| 1054 | }, |
| 1055 | .cli => @panic("TODO cli tests"), |
| 1048 | 1056 | } |
| 1049 | 1057 | } |
| 1050 | 1058 | } |