blob 0f6cefb7 (10882B) - Raw
1 const std = @import("std.zig"); 2 const warn = std.debug.warn; 3 4 pub const LeakCountAllocator = @import("testing/leak_count_allocator.zig").LeakCountAllocator; 5 pub const FailingAllocator = @import("testing/failing_allocator.zig").FailingAllocator; 6 7 /// This should only be used in temporary test programs. 8 pub const allocator = &allocator_instance.allocator; 9 pub var allocator_instance = LeakCountAllocator.init(&base_allocator_instance.allocator); 10 11 pub const failing_allocator = &failing_allocator_instance.allocator; 12 pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_instance.allocator, 0); 13 14 pub var base_allocator_instance = std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]); 15 var allocator_mem: [2 * 1024 * 1024]u8 = undefined; 16 17 /// This function is intended to be used only in tests. It prints diagnostics to stderr 18 /// and then aborts when actual_error_union is not expected_error. 19 pub fn expectError(expected_error: anyerror, actual_error_union: var) void { 20 if (actual_error_union) |actual_payload| { 21 std.debug.panic("expected error.{}, found {}", .{ @errorName(expected_error), actual_payload }); 22 } else |actual_error| { 23 if (expected_error != actual_error) { 24 std.debug.panic("expected error.{}, found error.{}", .{ 25 @errorName(expected_error), 26 @errorName(actual_error), 27 }); 28 } 29 } 30 } 31 32 /// This function is intended to be used only in tests. When the two values are not 33 /// equal, prints diagnostics to stderr to show exactly how they are not equal, 34 /// then aborts. 35 /// The types must match exactly. 36 pub fn expectEqual(expected: var, actual: @TypeOf(expected)) void { 37 switch (@typeInfo(@TypeOf(actual))) { 38 .NoReturn, 39 .BoundFn, 40 .Opaque, 41 .Frame, 42 .AnyFrame, 43 => @compileError("value of type " ++ @typeName(@TypeOf(actual)) ++ " encountered"), 44 45 .Undefined, 46 .Null, 47 .Void, 48 => return, 49 50 .Type, 51 .Bool, 52 .Int, 53 .Float, 54 .ComptimeFloat, 55 .ComptimeInt, 56 .EnumLiteral, 57 .Enum, 58 .Fn, 59 .ErrorSet, 60 => { 61 if (actual != expected) { 62 std.debug.panic("expected {}, found {}", .{ expected, actual }); 63 } 64 }, 65 66 .Pointer => |pointer| { 67 switch (pointer.size) { 68 .One, .Many, .C => { 69 if (actual != expected) { 70 std.debug.panic("expected {*}, found {*}", .{ expected, actual }); 71 } 72 }, 73 .Slice => { 74 if (actual.ptr != expected.ptr) { 75 std.debug.panic("expected slice ptr {}, found {}", .{ expected.ptr, actual.ptr }); 76 } 77 if (actual.len != expected.len) { 78 std.debug.panic("expected slice len {}, found {}", .{ expected.len, actual.len }); 79 } 80 }, 81 } 82 }, 83 84 .Array => |array| expectEqualSlices(array.child, &expected, &actual), 85 86 .Vector => |vectorType| { 87 var i: usize = 0; 88 while (i < vectorType.len) : (i += 1) { 89 if (!std.meta.eql(expected[i], actual[i])) { 90 std.debug.panic("index {} incorrect. expected {}, found {}", .{ i, expected[i], actual[i] }); 91 } 92 } 93 }, 94 95 .Struct => |structType| { 96 inline for (structType.fields) |field| { 97 expectEqual(@field(expected, field.name), @field(actual, field.name)); 98 } 99 }, 100 101 .Union => |union_info| { 102 if (union_info.tag_type == null) { 103 @compileError("Unable to compare untagged union values"); 104 } 105 106 const TagType = @TagType(@TypeOf(expected)); 107 108 const expectedTag = @as(TagType, expected); 109 const actualTag = @as(TagType, actual); 110 111 expectEqual(expectedTag, actualTag); 112 113 // we only reach this loop if the tags are equal 114 inline for (std.meta.fields(@TypeOf(actual))) |fld| { 115 if (std.mem.eql(u8, fld.name, @tagName(actualTag))) { 116 expectEqual(@field(expected, fld.name), @field(actual, fld.name)); 117 return; 118 } 119 } 120 121 // we iterate over *all* union fields 122 // => we should never get here as the loop above is 123 // including all possible values. 124 unreachable; 125 }, 126 127 .Optional => { 128 if (expected) |expected_payload| { 129 if (actual) |actual_payload| { 130 expectEqual(expected_payload, actual_payload); 131 } else { 132 std.debug.panic("expected {}, found null", .{expected_payload}); 133 } 134 } else { 135 if (actual) |actual_payload| { 136 std.debug.panic("expected null, found {}", .{actual_payload}); 137 } 138 } 139 }, 140 141 .ErrorUnion => { 142 if (expected) |expected_payload| { 143 if (actual) |actual_payload| { 144 expectEqual(expected_payload, actual_payload); 145 } else |actual_err| { 146 std.debug.panic("expected {}, found {}", .{ expected_payload, actual_err }); 147 } 148 } else |expected_err| { 149 if (actual) |actual_payload| { 150 std.debug.panic("expected {}, found {}", .{ expected_err, actual_payload }); 151 } else |actual_err| { 152 expectEqual(expected_err, actual_err); 153 } 154 } 155 }, 156 } 157 } 158 159 test "expectEqual.union(enum)" { 160 const T = union(enum) { 161 a: i32, 162 b: f32, 163 }; 164 165 const a10 = T{ .a = 10 }; 166 const a20 = T{ .a = 20 }; 167 168 expectEqual(a10, a10); 169 } 170 171 /// This function is intended to be used only in tests. When the two slices are not 172 /// equal, prints diagnostics to stderr to show exactly how they are not equal, 173 /// then aborts. 174 pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) void { 175 // TODO better printing of the difference 176 // If the arrays are small enough we could print the whole thing 177 // If the child type is u8 and no weird bytes, we could print it as strings 178 // Even for the length difference, it would be useful to see the values of the slices probably. 179 if (expected.len != actual.len) { 180 std.debug.panic("slice lengths differ. expected {}, found {}", .{ expected.len, actual.len }); 181 } 182 var i: usize = 0; 183 while (i < expected.len) : (i += 1) { 184 if (!std.meta.eql(expected[i], actual[i])) { 185 std.debug.panic("index {} incorrect. expected {}, found {}", .{ i, expected[i], actual[i] }); 186 } 187 } 188 } 189 190 /// This function is intended to be used only in tests. When `ok` is false, the test fails. 191 /// A message is printed to stderr and then abort is called. 192 pub fn expect(ok: bool) void { 193 if (!ok) @panic("test failure"); 194 } 195 196 pub const TmpDir = struct { 197 dir: std.fs.Dir, 198 parent_dir: std.fs.Dir, 199 sub_path: [sub_path_len]u8, 200 201 const random_bytes_count = 12; 202 const sub_path_len = std.base64.Base64Encoder.calcSize(random_bytes_count); 203 204 pub fn cleanup(self: *TmpDir) void { 205 self.dir.close(); 206 self.parent_dir.deleteTree(&self.sub_path) catch {}; 207 self.parent_dir.close(); 208 self.* = undefined; 209 } 210 }; 211 212 pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir { 213 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; 214 std.crypto.randomBytes(&random_bytes) catch 215 @panic("unable to make tmp dir for testing: unable to get random bytes"); 216 var sub_path: [TmpDir.sub_path_len]u8 = undefined; 217 std.fs.base64_encoder.encode(&sub_path, &random_bytes); 218 219 var cache_dir = std.fs.cwd().makeOpenPath("zig-cache", .{}) catch 220 @panic("unable to make tmp dir for testing: unable to make and open zig-cache dir"); 221 defer cache_dir.close(); 222 var parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch 223 @panic("unable to make tmp dir for testing: unable to make and open zig-cache/tmp dir"); 224 var dir = parent_dir.makeOpenPath(&sub_path, opts) catch 225 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); 226 227 return .{ 228 .dir = dir, 229 .parent_dir = parent_dir, 230 .sub_path = sub_path, 231 }; 232 } 233 234 test "expectEqual nested array" { 235 const a = [2][2]f32{ 236 [_]f32{ 1.0, 0.0 }, 237 [_]f32{ 0.0, 1.0 }, 238 }; 239 240 const b = [2][2]f32{ 241 [_]f32{ 1.0, 0.0 }, 242 [_]f32{ 0.0, 1.0 }, 243 }; 244 245 expectEqual(a, b); 246 } 247 248 test "expectEqual vector" { 249 var a = @splat(4, @as(u32, 4)); 250 var b = @splat(4, @as(u32, 4)); 251 252 expectEqual(a, b); 253 } 254 255 pub fn expectEqualStrings(expected: []const u8, actual: []const u8) void { 256 if (std.mem.indexOfDiff(u8, actual, expected)) |diff_index| { 257 warn("\n====== expected this output: =========\n", .{}); 258 printWithVisibleNewlines(expected); 259 warn("\n======== instead found this: =========\n", .{}); 260 printWithVisibleNewlines(actual); 261 warn("\n======================================\n", .{}); 262 263 var diff_line_number: usize = 1; 264 for (expected[0..diff_index]) |value| { 265 if (value == '\n') diff_line_number += 1; 266 } 267 warn("First difference occurs on line {}:\n", .{diff_line_number}); 268 269 warn("expected:\n", .{}); 270 printIndicatorLine(expected, diff_index); 271 272 warn("found:\n", .{}); 273 printIndicatorLine(actual, diff_index); 274 275 @panic("test failure"); 276 } 277 } 278 279 fn printIndicatorLine(source: []const u8, indicator_index: usize) void { 280 const line_begin_index = if (std.mem.lastIndexOfScalar(u8, source[0..indicator_index], '\n')) |line_begin| 281 line_begin + 1 282 else 283 0; 284 const line_end_index = if (std.mem.indexOfScalar(u8, source[indicator_index..], '\n')) |line_end| 285 (indicator_index + line_end) 286 else 287 source.len; 288 289 printLine(source[line_begin_index..line_end_index]); 290 { 291 var i: usize = line_begin_index; 292 while (i < indicator_index) : (i += 1) 293 warn(" ", .{}); 294 } 295 warn("^\n", .{}); 296 } 297 298 fn printWithVisibleNewlines(source: []const u8) void { 299 var i: usize = 0; 300 while (std.mem.indexOf(u8, source[i..], "\n")) |nl| : (i += nl + 1) { 301 printLine(source[i .. i + nl]); 302 } 303 warn("{}␃\n", .{source[i..]}); // End of Text symbol (ETX) 304 } 305 306 fn printLine(line: []const u8) void { 307 switch (line[line.len - 1]) { 308 ' ', '\t' => warn("{}⏎\n", .{line}), // Carriage return symbol, 309 else => warn("{}\n", .{line}), 310 } 311 } 312 313 test "" { 314 expectEqualStrings("foo", "foo"); 315 }