test.zig (8750B)
1 const std = @import("std"); 2 const parse = @import("AstGen.zig").parse; 3 const Ast = @import("Ast.zig"); 4 const PADDING = @import("padded_str.zig").PADDING; 5 6 const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator(.{}); 7 const ArenaAllocator = std.heap.ArenaAllocator; 8 9 fn readFile(gpa: std.mem.Allocator, path: []const u8) !std.ArrayList(u8) { 10 var input_instance: std.ArrayList(u8) = .init(gpa); 11 errdefer input_instance.deinit(); 12 const input_file = try std.fs.cwd().openFile(path, .{}); 13 defer input_file.close(); 14 const file_size = (try input_file.stat()).size; 15 try input_instance.ensureTotalCapacity(file_size + PADDING + 1); 16 try input_file.reader().readAllArrayList(&input_instance, std.math.maxInt(u32) - PADDING - 1); 17 return input_instance; 18 } 19 20 fn testForCrashes(gpa: std.mem.Allocator, input: []const u8) !void { 21 const padded_input = try std.mem.concat(gpa, u8, &.{ input, "\n" ** (PADDING + 1) }); 22 defer gpa.free(padded_input); 23 24 const ast = try parse(gpa, null, padded_input); 25 defer ast.deinit(gpa); 26 27 var output_instance: std.ArrayList(u8) = .init(gpa); 28 defer output_instance.deinit(); 29 try ast.renderAst(output_instance.writer(), padded_input); 30 } 31 32 fn testSnapshot(gpa: std.mem.Allocator, path: []const u8) !void { 33 return testSnapshotImpl(gpa, path); 34 // std.testing.checkAllAllocationFailures( 35 // gpa, 36 // testSnapshotImpl, 37 // .{path}, 38 // ) catch |err| if (err != error.SwallowedOutOfMemoryError) 39 // return err; 40 } 41 fn testSnapshotImpl(gpa: std.mem.Allocator, path: []const u8) !void { 42 const snapshot_path = try std.mem.concat(gpa, u8, &.{ path, ".ast" }); 43 defer gpa.free(snapshot_path); 44 const wrong_path = try std.mem.concat(gpa, u8, &.{ path, ".wrong.ast" }); 45 defer gpa.free(wrong_path); 46 47 var input_instance = try readFile(gpa, path); 48 defer input_instance.deinit(); 49 try input_instance.appendNTimes('\n', PADDING + 1); 50 51 const ast = try parse(gpa, null, input_instance.items); 52 defer ast.deinit(gpa); 53 // const ast2 = try parse3(gpa, gpa, input_instance.items); 54 // defer ast2.deinit(gpa); 55 56 var output_instance: std.ArrayList(u8) = .init(gpa); 57 defer output_instance.deinit(); 58 try ast.renderAst(output_instance.writer(), input_instance.items); 59 60 var snapshot_instance: std.ArrayList(u8) = readFile(gpa, snapshot_path) catch |err| { 61 const wrong_file = try std.fs.cwd().createFile(wrong_path, .{}); 62 defer wrong_file.close(); 63 try wrong_file.writer().writeAll(output_instance.items); 64 std.debug.print( 65 \\Snapshot could not be loaded for {[path]s} due to error {[err]}. 66 \\ 67 \\Generated output written to {[wrong_path]s}. 68 \\ 69 \\If generated output looks good: 70 \\ 71 \\ mv {[wrong_path]s} {[snapshot_path]s} 72 \\ 73 \\ 74 , .{ .path = path, .wrong_path = wrong_path, .snapshot_path = snapshot_path, .err = err }); 75 return err; 76 }; 77 defer snapshot_instance.deinit(); 78 79 if (!std.mem.eql(u8, snapshot_instance.items, output_instance.items)) { 80 const wrong_file = try std.fs.cwd().createFile(wrong_path, .{}); 81 defer wrong_file.close(); 82 try wrong_file.writer().writeAll(output_instance.items); 83 std.debug.print( 84 \\Snapshot test for {[path]s} failed. 85 \\ 86 \\Generated output written to {[wrong_path]s}. 87 \\If changes are expected, copy {[wrong_path]s} to {[snapshot_path]s}. 88 \\ 89 \\Diff changes with your favourite diff viewer: 90 \\ 91 \\ diff {[snapshot_path]s} {[wrong_path]s} 92 \\ git diff --no-index -- {[snapshot_path]s} {[wrong_path]s} 93 \\ 94 \\If changes are expected: 95 \\ 96 \\ mv {[wrong_path]s} {[snapshot_path]s} 97 \\ 98 \\ 99 , .{ .path = path, .wrong_path = wrong_path, .snapshot_path = snapshot_path }); 100 return error.TestFailed; 101 } 102 } 103 104 test "empty" { 105 try testSnapshot(std.testing.allocator, "src/test/empty.my"); 106 } 107 108 test "Happy path paragraph" { 109 try testSnapshot(std.testing.allocator, "src/test/paragraph.my"); 110 } 111 112 test "Happy path headings" { 113 try testSnapshot(std.testing.allocator, "src/test/heading.my"); 114 } 115 116 test "Happy path quote" { 117 try testSnapshot(std.testing.allocator, "src/test/quote.my"); 118 } 119 120 test "Happy path list" { 121 try testSnapshot(std.testing.allocator, "src/test/list.my"); 122 } 123 124 test "Happy path list elaboration" { 125 try testSnapshot(std.testing.allocator, "src/test/elaboration.my"); 126 } 127 128 test "Thematic break" { 129 try testSnapshot(std.testing.allocator, "src/test/thematic_break.my"); 130 } 131 132 test "Sample" { 133 try testSnapshot(std.testing.allocator, "src/test/sample.my"); 134 } 135 136 test "fuzz" { 137 var arena_instance: std.heap.ArenaAllocator = .init(std.testing.allocator); 138 defer arena_instance.deinit(); 139 const arena = arena_instance.allocator(); 140 try std.testing.fuzz(std.testing.allocator, testForCrashes, .{ 141 .corpus = &.{ 142 // (try readFile(arena, "src/test/empty.my")).items, 143 (try readFile(arena, "src/test/paragraph.my")).items, 144 (try readFile(arena, "src/test/heading.my")).items, 145 (try readFile(arena, "src/test/quote.my")).items, 146 (try readFile(arena, "src/test/list.my")).items, 147 (try readFile(arena, "src/test/elaboration.my")).items, 148 (try readFile(arena, "src/test/thematic_break.my")).items, 149 (try readFile(arena, "src/test/sample.my")).items, 150 }, 151 }); 152 } 153 154 test "Super long line" { 155 const input = try std.testing.allocator.create([(1 << 24) * 4 + PADDING + 1]u8); 156 defer std.testing.allocator.destroy(input); 157 @memset(input[0 .. (1 << 24) * 4], 'a'); 158 @memset(input[(1 << 24) * 4 ..], '\n'); 159 var arena: ArenaAllocator = .init(std.testing.allocator); 160 defer arena.deinit(); 161 const ast = try parse(std.testing.allocator, arena.allocator(), input); 162 try std.testing.expectEqual(7, ast.nodes.len); 163 try std.testing.expectEqual(0, ast.errors.len); 164 try std.testing.expectEqual(.document, ast.nodes[0].tag); 165 try std.testing.expectEqual(0, ast.nodes[0].off); 166 try std.testing.expectEqual(1, ast.nodes[0].numChildren()); 167 try std.testing.expectEqual(.paragraph, ast.nodes[1].tag); 168 try std.testing.expectEqual(0, ast.nodes[1].off); 169 try std.testing.expectEqual(5, ast.nodes[1].numChildren()); 170 try std.testing.expectEqual(.text, ast.nodes[2].tag); 171 try std.testing.expectEqual(0, ast.nodes[2].off); 172 try std.testing.expectEqual(16777215, ast.nodes[2].len()); 173 try std.testing.expectEqual(.text, ast.nodes[3].tag); 174 try std.testing.expectEqual(16777215, ast.nodes[3].off); 175 try std.testing.expectEqual(16777215, ast.nodes[3].len()); 176 try std.testing.expectEqual(.text, ast.nodes[4].tag); 177 try std.testing.expectEqual(33554430, ast.nodes[4].off); 178 try std.testing.expectEqual(16777215, ast.nodes[4].len()); 179 try std.testing.expectEqual(.text, ast.nodes[5].tag); 180 try std.testing.expectEqual(50331645, ast.nodes[5].off); 181 try std.testing.expectEqual(16777215, ast.nodes[5].len()); 182 try std.testing.expectEqual(.text, ast.nodes[6].tag); 183 try std.testing.expectEqual(67108860, ast.nodes[6].off); 184 try std.testing.expectEqual(4, ast.nodes[6].len()); 185 } 186 187 test "Many short lines" { 188 const input = try std.testing.allocator.create([(1 << 25) - 4 + PADDING]u8); 189 defer std.testing.allocator.destroy(input); 190 @memset(@as(*[(1 << 24) - 2][2]u8, @ptrCast(input)), "a\n"[0..2].*); 191 @memset(input[(1 << 25) - 4 ..], '\n'); 192 193 var arena: ArenaAllocator = .init(std.testing.allocator); 194 defer arena.deinit(); 195 const ast = try parse(std.testing.allocator, arena.allocator(), input); 196 197 try std.testing.expectEqual(1 << 24, ast.nodes.len); 198 try std.testing.expectEqual(0, ast.errors.len); 199 try std.testing.expectEqual(.document, ast.nodes[0].tag); 200 try std.testing.expectEqual(0, ast.nodes[0].off); 201 try std.testing.expectEqual(1, ast.nodes[0].numChildren()); 202 try std.testing.expectEqual(.paragraph, ast.nodes[1].tag); 203 try std.testing.expectEqual(0, ast.nodes[1].off); 204 try std.testing.expectEqual((1 << 24) - 2, ast.nodes[1].numChildren()); 205 try std.testing.expectEqual(.text, ast.nodes[2].tag); 206 try std.testing.expectEqual(0, ast.nodes[2].off); 207 try std.testing.expectEqual(1, ast.nodes[2].len()); 208 for (1..(1 << 24) - 2) |i| { 209 try std.testing.expectEqual(.space_text, ast.nodes[i + 2].tag); 210 try std.testing.expectEqual(i * 2, ast.nodes[i + 2].off); 211 try std.testing.expectEqual(1, ast.nodes[i + 2].len()); 212 } 213 }