| 1 | const builtin = @import("builtin"); |
| 2 | |
| 3 | const std = @import("std.zig"); |
| 4 | const Io = std.Io; |
| 5 | const Environ = std.process.Environ; |
| 6 | const assert = std.debug.assert; |
| 7 | const math = std.math; |
| 8 | |
| 9 | /// Provides deterministic randomness in unit tests. |
| 10 | /// Initialized on startup. Read-only after that. |
| 11 | pub var random_seed: u32 = 0; |
| 12 | |
| 13 | pub const FailingAllocator = @import("testing/FailingAllocator.zig"); |
| 14 | pub const failing_allocator = failing_allocator_instance.allocator(); |
| 15 | var failing_allocator_instance = FailingAllocator.init(base_allocator_instance.allocator(), .{ |
| 16 | .fail_index = 0, |
| 17 | }); |
| 18 | var base_allocator_instance = std.heap.FixedBufferAllocator.init(""); |
| 19 | |
| 20 | pub var allocator_instance: std.heap.SafeAllocator = undefined; |
| 21 | pub const allocator = if (builtin.is_test) allocator_instance.allocator() else @compileError("not testing"); |
| 22 | |
| 23 | pub var io_instance: Io.Threaded = undefined; |
| 24 | pub const io = if (builtin.is_test) io_instance.io() else @compileError("not testing"); |
| 25 | |
| 26 | pub var environ: Environ = if (builtin.is_test) undefined else @compileError("not testing"); |
| 27 | |
| 28 | /// TODO https://github.com/ziglang/zig/issues/5738 |
| 29 | pub var log_level = std.log.Level.warn; |
| 30 | |
| 31 | // Disable printing in tests for simple backends. |
| 32 | pub const backend_can_print = switch (builtin.zig_backend) { |
| 33 | .stage2_aarch64, |
| 34 | .stage2_loongarch, |
| 35 | .stage2_powerpc, |
| 36 | .stage2_riscv64, |
| 37 | .stage2_spirv, |
| 38 | => false, |
| 39 | else => true, |
| 40 | }; |
| 41 | |
| 42 | /// Helper function for printing test failure information. |
| 43 | pub fn failPrint(comptime fmt: []const u8, args: anytype) void { |
| 44 | if (@inComptime()) { |
| 45 | @compileError(std.fmt.comptimePrint(fmt, args)); |
| 46 | } else if (backend_can_print) { |
| 47 | std.debug.print(fmt, args); |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | /// This function is intended to be used only in tests. When the two values are not |
| 52 | /// equal, prints diagnostics to stderr to show exactly how they are not equal, |
| 53 | /// then returns a test failure error. |
| 54 | /// `actual` and `expected` are coerced to a common type using peer type resolution. |
| 55 | pub inline fn expectEqual(expected: anytype, actual: anytype) !void { |
| 56 | const T = @TypeOf(expected, actual); |
| 57 | return expectEqualInner(T, expected, actual); |
| 58 | } |
| 59 | |
| 60 | fn expectEqualInner(comptime T: type, expected: T, actual: T) !void { |
| 61 | switch (@typeInfo(@TypeOf(actual))) { |
| 62 | .noreturn, |
| 63 | .@"opaque", |
| 64 | .spirv, |
| 65 | .frame, |
| 66 | .@"anyframe", |
| 67 | => @compileError("value of type " ++ @typeName(@TypeOf(actual)) ++ " encountered"), |
| 68 | |
| 69 | .undefined, |
| 70 | .null, |
| 71 | .void, |
| 72 | => return, |
| 73 | |
| 74 | .type => { |
| 75 | if (actual != expected) { |
| 76 | failPrint("expected type {s}, found type {s}\n", .{ @typeName(expected), @typeName(actual) }); |
| 77 | return error.TestExpectedEqual; |
| 78 | } |
| 79 | }, |
| 80 | |
| 81 | .bool, |
| 82 | .int, |
| 83 | .float, |
| 84 | .comptime_float, |
| 85 | .comptime_int, |
| 86 | .enum_literal, |
| 87 | .@"enum", |
| 88 | .@"fn", |
| 89 | .error_set, |
| 90 | => { |
| 91 | if (actual != expected) { |
| 92 | failPrint("expected {any}, found {any}\n", .{ expected, actual }); |
| 93 | return error.TestExpectedEqual; |
| 94 | } |
| 95 | }, |
| 96 | |
| 97 | .pointer => |pointer| { |
| 98 | switch (pointer.size) { |
| 99 | .one, .many, .c => { |
| 100 | if (actual != expected) { |
| 101 | failPrint("expected {*}, found {*}\n", .{ expected, actual }); |
| 102 | return error.TestExpectedEqual; |
| 103 | } |
| 104 | }, |
| 105 | .slice => { |
| 106 | if (actual.ptr != expected.ptr) { |
| 107 | failPrint("expected slice ptr {*}, found {*}\n", .{ expected.ptr, actual.ptr }); |
| 108 | return error.TestExpectedEqual; |
| 109 | } |
| 110 | if (actual.len != expected.len) { |
| 111 | failPrint("expected slice len {}, found {}\n", .{ expected.len, actual.len }); |
| 112 | return error.TestExpectedEqual; |
| 113 | } |
| 114 | }, |
| 115 | } |
| 116 | }, |
| 117 | |
| 118 | .array => |array| try expectEqualSlices(array.child, &expected, &actual), |
| 119 | |
| 120 | .vector => |info| { |
| 121 | const expect_array: [info.len]info.child = expected; |
| 122 | const actual_array: [info.len]info.child = actual; |
| 123 | try expectEqualSlices(info.child, &expect_array, &actual_array); |
| 124 | }, |
| 125 | |
| 126 | .@"struct" => |@"struct"| { |
| 127 | inline for (@"struct".field_names) |field_name| { |
| 128 | try expectEqual(@field(expected, field_name), @field(actual, field_name)); |
| 129 | } |
| 130 | }, |
| 131 | |
| 132 | .@"union" => |@"union"| if (@"union".backing_integer) |Int| { |
| 133 | try expectEqual(@as(Int, @bitCast(expected)), @as(Int, @bitCast(actual))); |
| 134 | } else switch (@"union".layout) { |
| 135 | .@"packed" => { |
| 136 | const Int = @Int(.unsigned, @bitSizeOf(T)); |
| 137 | try expectEqual(@as(Int, @bitCast(expected)), @as(Int, @bitCast(actual))); |
| 138 | }, |
| 139 | .@"extern" => { |
| 140 | const first_size = @bitSizeOf(@"union".field_types[0]); |
| 141 | inline for (@"union".field_types) |field_type| { |
| 142 | if (@bitSizeOf(field_type) != first_size) { |
| 143 | @compileError("Unable to compare extern unions with varying field sizes for type " ++ @typeName(T)); |
| 144 | } |
| 145 | } |
| 146 | const FieldInt = @Int(.unsigned, first_size); |
| 147 | const expected_field = @field(expected, @"union".field_names[0]); |
| 148 | const actual_field = @field(actual, @"union".field_names[0]); |
| 149 | return expectEqual( |
| 150 | @as(FieldInt, @bitCast(expected_field)), |
| 151 | @as(FieldInt, @bitCast(actual_field)), |
| 152 | ); |
| 153 | }, |
| 154 | .auto => { |
| 155 | const Tag = @"union".tag_type orelse @compileError("byteSwapAllFields expects packed, extern, or tagged union"); |
| 156 | |
| 157 | try expectEqual(@as(Tag, expected), @as(Tag, actual)); |
| 158 | switch (expected) { |
| 159 | inline else => |expected_payload, tag| { |
| 160 | const actual_payload = @field(actual, @tagName(tag)); |
| 161 | try expectEqual(expected_payload, actual_payload); |
| 162 | }, |
| 163 | } |
| 164 | }, |
| 165 | }, |
| 166 | |
| 167 | .optional => { |
| 168 | if (expected) |expected_payload| { |
| 169 | if (actual) |actual_payload| { |
| 170 | try expectEqual(expected_payload, actual_payload); |
| 171 | } else { |
| 172 | failPrint("expected {any}, found null\n", .{expected_payload}); |
| 173 | return error.TestExpectedEqual; |
| 174 | } |
| 175 | } else { |
| 176 | if (actual) |actual_payload| { |
| 177 | failPrint("expected null, found {any}\n", .{actual_payload}); |
| 178 | return error.TestExpectedEqual; |
| 179 | } |
| 180 | } |
| 181 | }, |
| 182 | |
| 183 | .error_union => { |
| 184 | if (expected) |expected_payload| { |
| 185 | if (actual) |actual_payload| { |
| 186 | try expectEqual(expected_payload, actual_payload); |
| 187 | } else |actual_err| { |
| 188 | failPrint("expected {any}, found {}\n", .{ expected_payload, actual_err }); |
| 189 | return error.TestExpectedEqual; |
| 190 | } |
| 191 | } else |expected_err| { |
| 192 | if (actual) |actual_payload| { |
| 193 | failPrint("expected {}, found {any}\n", .{ expected_err, actual_payload }); |
| 194 | return error.TestExpectedEqual; |
| 195 | } else |actual_err| { |
| 196 | try expectEqual(expected_err, actual_err); |
| 197 | } |
| 198 | } |
| 199 | }, |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | test "expectEqual union(enum)" { |
| 204 | const T = union(enum) { |
| 205 | a: i32, |
| 206 | b: f32, |
| 207 | }; |
| 208 | |
| 209 | const a10 = T{ .a = 10 }; |
| 210 | |
| 211 | try expectEqual(a10, a10); |
| 212 | } |
| 213 | |
| 214 | test "expectEqual union with comptime-only field" { |
| 215 | const U = union(enum) { |
| 216 | a: void, |
| 217 | b: void, |
| 218 | c: comptime_int, |
| 219 | }; |
| 220 | |
| 221 | try expectEqual(U{ .a = {} }, .a); |
| 222 | } |
| 223 | |
| 224 | test "expectEqual nested array" { |
| 225 | const a = [2][2]f32{ |
| 226 | [_]f32{ 1.0, 0.0 }, |
| 227 | [_]f32{ 0.0, 1.0 }, |
| 228 | }; |
| 229 | |
| 230 | const b = [2][2]f32{ |
| 231 | [_]f32{ 1.0, 0.0 }, |
| 232 | [_]f32{ 0.0, 1.0 }, |
| 233 | }; |
| 234 | |
| 235 | try expectEqual(a, b); |
| 236 | } |
| 237 | |
| 238 | test "expectEqual vector" { |
| 239 | const a: @Vector(4, u32) = @splat(4); |
| 240 | const b: @Vector(4, u32) = @splat(4); |
| 241 | |
| 242 | try expectEqual(a, b); |
| 243 | } |
| 244 | |
| 245 | test "expectEqual null" { |
| 246 | const a = .{null}; |
| 247 | const b = @Vector(1, ?*u8){null}; |
| 248 | |
| 249 | try expectEqual(a, b); |
| 250 | } |
| 251 | |
| 252 | /// This function is intended to be used only in tests. When the actual value is |
| 253 | /// not approximately equal to the expected value, prints diagnostics to stderr |
| 254 | /// to show exactly how they are not equal, then returns a test failure error. |
| 255 | /// See `math.approxEqAbs` for more information on the tolerance parameter. |
| 256 | /// The types must be floating-point. |
| 257 | /// `actual` and `expected` are coerced to a common type using peer type resolution. |
| 258 | pub inline fn expectApproxEqAbs(expected: anytype, actual: anytype, tolerance: anytype) !void { |
| 259 | const T = @TypeOf(expected, actual, tolerance); |
| 260 | return expectApproxEqAbsInner(T, expected, actual, tolerance); |
| 261 | } |
| 262 | |
| 263 | fn expectApproxEqAbsInner(comptime T: type, expected: T, actual: T, tolerance: T) !void { |
| 264 | switch (@typeInfo(T)) { |
| 265 | .float => if (!math.approxEqAbs(T, expected, actual, tolerance)) { |
| 266 | failPrint("actual {}, not within absolute tolerance {} of expected {}\n", .{ actual, tolerance, expected }); |
| 267 | return error.TestExpectedApproxEqAbs; |
| 268 | }, |
| 269 | |
| 270 | .comptime_float => @compileError("Cannot approximately compare two comptime_float values"), |
| 271 | |
| 272 | else => @compileError("Unable to compare non floating point values"), |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | test expectApproxEqAbs { |
| 277 | inline for ([_]type{ f16, f32, f64, f128 }) |T| { |
| 278 | const pos_x: T = 12.0; |
| 279 | const pos_y: T = 12.06; |
| 280 | const neg_x: T = -12.0; |
| 281 | const neg_y: T = -12.06; |
| 282 | |
| 283 | try expectApproxEqAbs(pos_x, pos_y, 0.1); |
| 284 | try expectApproxEqAbs(neg_x, neg_y, 0.1); |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | /// This function is intended to be used only in tests. When the actual value is |
| 289 | /// not approximately equal to the expected value, prints diagnostics to stderr |
| 290 | /// to show exactly how they are not equal, then returns a test failure error. |
| 291 | /// See `math.approxEqRel` for more information on the tolerance parameter. |
| 292 | /// The types must be floating-point. |
| 293 | /// `actual` and `expected` are coerced to a common type using peer type resolution. |
| 294 | pub inline fn expectApproxEqRel(expected: anytype, actual: anytype, tolerance: anytype) !void { |
| 295 | const T = @TypeOf(expected, actual, tolerance); |
| 296 | return expectApproxEqRelInner(T, expected, actual, tolerance); |
| 297 | } |
| 298 | |
| 299 | fn expectApproxEqRelInner(comptime T: type, expected: T, actual: T, tolerance: T) !void { |
| 300 | switch (@typeInfo(T)) { |
| 301 | .float => if (!math.approxEqRel(T, expected, actual, tolerance)) { |
| 302 | failPrint("actual {}, not within relative tolerance {} of expected {}\n", .{ actual, tolerance, expected }); |
| 303 | return error.TestExpectedApproxEqRel; |
| 304 | }, |
| 305 | |
| 306 | .comptime_float => @compileError("Cannot approximately compare two comptime_float values"), |
| 307 | |
| 308 | else => @compileError("Unable to compare non floating point values"), |
| 309 | } |
| 310 | } |
| 311 | |
| 312 | test expectApproxEqRel { |
| 313 | inline for ([_]type{ f16, f32, f64, f128 }) |T| { |
| 314 | const eps_value = comptime math.floatEps(T); |
| 315 | const sqrt_eps_value = comptime @sqrt(eps_value); |
| 316 | |
| 317 | const pos_x: T = 12.0; |
| 318 | const pos_y: T = pos_x + 2 * eps_value; |
| 319 | const neg_x: T = -12.0; |
| 320 | const neg_y: T = neg_x - 2 * eps_value; |
| 321 | |
| 322 | try expectApproxEqRel(pos_x, pos_y, sqrt_eps_value); |
| 323 | try expectApproxEqRel(neg_x, neg_y, sqrt_eps_value); |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | /// This function is intended to be used only in tests. When the two slices are |
| 328 | /// not equal, it prints diagnostics to stderr to show exactly how they are not |
| 329 | /// equal (with the differences highlighted in red), then returns a test |
| 330 | /// failure error. |
| 331 | pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void { |
| 332 | const diff_index: usize = diff_index: { |
| 333 | const shortest = @min(expected.len, actual.len); |
| 334 | var index: usize = 0; |
| 335 | while (index < shortest) : (index += 1) { |
| 336 | if (!std.meta.eql(actual[index], expected[index])) break :diff_index index; |
| 337 | } |
| 338 | break :diff_index if (expected.len == actual.len) return else shortest; |
| 339 | }; |
| 340 | if (!backend_can_print) return error.TestExpectedEqual; |
| 341 | // Intentionally using the debug Io instance rather than the testing Io instance. |
| 342 | const stderr = std.debug.lockStderr(&.{}); |
| 343 | defer std.debug.unlockStderr(); |
| 344 | const w = &stderr.file_writer.interface; |
| 345 | failEqualSlices(T, expected, actual, diff_index, w, stderr.terminal_mode) catch {}; |
| 346 | return error.TestExpectedEqual; |
| 347 | } |
| 348 | |
| 349 | fn failEqualSlices( |
| 350 | comptime T: type, |
| 351 | expected: []const T, |
| 352 | actual: []const T, |
| 353 | diff_index: usize, |
| 354 | w: *Io.Writer, |
| 355 | terminal_mode: Io.Terminal.Mode, |
| 356 | ) !void { |
| 357 | try w.print("slices differ. first difference occurs at index {d} (0x{X})\n", .{ diff_index, diff_index }); |
| 358 | |
| 359 | // TODO: Should this be configurable by the caller? |
| 360 | const max_lines: usize = 16; |
| 361 | const max_window_size: usize = if (T == u8) max_lines * 16 else max_lines; |
| 362 | |
| 363 | // Print a maximum of max_window_size items of each input, starting just before the |
| 364 | // first difference to give a bit of context. |
| 365 | var window_start: usize = 0; |
| 366 | if (@max(actual.len, expected.len) > max_window_size) { |
| 367 | const alignment = if (T == u8) 16 else 2; |
| 368 | window_start = std.mem.alignBackward(usize, diff_index - @min(diff_index, alignment), alignment); |
| 369 | } |
| 370 | const expected_window = expected[window_start..@min(expected.len, window_start + max_window_size)]; |
| 371 | const expected_truncated = window_start + expected_window.len < expected.len; |
| 372 | const actual_window = actual[window_start..@min(actual.len, window_start + max_window_size)]; |
| 373 | const actual_truncated = window_start + actual_window.len < actual.len; |
| 374 | |
| 375 | var differ = if (T == u8) BytesDiffer{ |
| 376 | .expected = expected_window, |
| 377 | .actual = actual_window, |
| 378 | .terminal_mode = terminal_mode, |
| 379 | } else SliceDiffer(T){ |
| 380 | .start_index = window_start, |
| 381 | .expected = expected_window, |
| 382 | .actual = actual_window, |
| 383 | .terminal_mode = terminal_mode, |
| 384 | }; |
| 385 | |
| 386 | // Print indexes as hex for slices of u8 since it's more likely to be binary data where |
| 387 | // that is usually useful. |
| 388 | const index_fmt = if (T == u8) "0x{X}" else "{}"; |
| 389 | |
| 390 | try w.print("\n============ expected this output: ============= len: {} (0x{X})\n\n", .{ expected.len, expected.len }); |
| 391 | if (window_start > 0) { |
| 392 | if (T == u8) { |
| 393 | try w.print("... truncated, start index: " ++ index_fmt ++ " ...\n", .{window_start}); |
| 394 | } else { |
| 395 | try w.print("... truncated ...\n", .{}); |
| 396 | } |
| 397 | } |
| 398 | differ.write(w) catch {}; |
| 399 | if (expected_truncated) { |
| 400 | const end_offset = window_start + expected_window.len; |
| 401 | const num_missing_items = expected.len - (window_start + expected_window.len); |
| 402 | if (T == u8) { |
| 403 | try w.print("... truncated, indexes [" ++ index_fmt ++ "..] not shown, remaining bytes: " ++ index_fmt ++ " ...\n", .{ end_offset, num_missing_items }); |
| 404 | } else { |
| 405 | try w.print("... truncated, remaining items: " ++ index_fmt ++ " ...\n", .{num_missing_items}); |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | // now reverse expected/actual and print again |
| 410 | differ.expected = actual_window; |
| 411 | differ.actual = expected_window; |
| 412 | try w.print("\n============= instead found this: ============== len: {} (0x{X})\n\n", .{ actual.len, actual.len }); |
| 413 | if (window_start > 0) { |
| 414 | if (T == u8) { |
| 415 | try w.print("... truncated, start index: " ++ index_fmt ++ " ...\n", .{window_start}); |
| 416 | } else { |
| 417 | try w.print("... truncated ...\n", .{}); |
| 418 | } |
| 419 | } |
| 420 | differ.write(w) catch {}; |
| 421 | if (actual_truncated) { |
| 422 | const end_offset = window_start + actual_window.len; |
| 423 | const num_missing_items = actual.len - (window_start + actual_window.len); |
| 424 | if (T == u8) { |
| 425 | try w.print("... truncated, indexes [" ++ index_fmt ++ "..] not shown, remaining bytes: " ++ index_fmt ++ " ...\n", .{ end_offset, num_missing_items }); |
| 426 | } else { |
| 427 | try w.print("... truncated, remaining items: " ++ index_fmt ++ " ...\n", .{num_missing_items}); |
| 428 | } |
| 429 | } |
| 430 | try w.print("\n================================================\n\n", .{}); |
| 431 | |
| 432 | return error.TestExpectedEqual; |
| 433 | } |
| 434 | |
| 435 | fn SliceDiffer(comptime T: type) type { |
| 436 | return struct { |
| 437 | start_index: usize, |
| 438 | expected: []const T, |
| 439 | actual: []const T, |
| 440 | terminal_mode: Io.Terminal.Mode, |
| 441 | |
| 442 | const Self = @This(); |
| 443 | |
| 444 | pub fn write(self: Self, writer: *Io.Writer) !void { |
| 445 | const t: Io.Terminal = .{ .writer = writer, .mode = self.terminal_mode }; |
| 446 | for (self.expected, 0..) |value, i| { |
| 447 | const full_index = self.start_index + i; |
| 448 | const diff = if (i < self.actual.len) !std.meta.eql(self.actual[i], value) else true; |
| 449 | if (diff) try t.setColor(.red); |
| 450 | if (@typeInfo(T) == .pointer) { |
| 451 | try writer.print("[{}]{*}: {any}\n", .{ full_index, value, value }); |
| 452 | } else { |
| 453 | try writer.print("[{}]: {any}\n", .{ full_index, value }); |
| 454 | } |
| 455 | if (diff) try t.setColor(.reset); |
| 456 | } |
| 457 | } |
| 458 | }; |
| 459 | } |
| 460 | |
| 461 | const BytesDiffer = struct { |
| 462 | expected: []const u8, |
| 463 | actual: []const u8, |
| 464 | terminal_mode: Io.Terminal.Mode, |
| 465 | |
| 466 | pub fn write(self: BytesDiffer, writer: *Io.Writer) !void { |
| 467 | var expected_iterator = std.mem.window(u8, self.expected, 16, 16); |
| 468 | var row: usize = 0; |
| 469 | while (expected_iterator.next()) |chunk| { |
| 470 | // to avoid having to calculate diffs twice per chunk |
| 471 | var diffs: std.bit_set.Integer(16) = .{ .mask = 0 }; |
| 472 | for (chunk, 0..) |byte, col| { |
| 473 | const absolute_byte_index = col + row * 16; |
| 474 | const diff = if (absolute_byte_index < self.actual.len) self.actual[absolute_byte_index] != byte else true; |
| 475 | if (diff) diffs.set(col); |
| 476 | try self.writeDiff(writer, "{X:0>2} ", .{byte}, diff); |
| 477 | if (col == 7) try writer.writeByte(' '); |
| 478 | } |
| 479 | try writer.writeByte(' '); |
| 480 | if (chunk.len < 16) { |
| 481 | var missing_columns = (16 - chunk.len) * 3; |
| 482 | if (chunk.len < 8) missing_columns += 1; |
| 483 | try writer.splatByteAll(' ', missing_columns); |
| 484 | } |
| 485 | for (chunk, 0..) |byte, col| { |
| 486 | const diff = diffs.isSet(col); |
| 487 | if (std.ascii.isPrint(byte)) { |
| 488 | try self.writeDiff(writer, "{c}", .{byte}, diff); |
| 489 | } else { |
| 490 | // TODO: remove this `if` when https://github.com/ziglang/zig/issues/7600 is fixed |
| 491 | if (self.terminal_mode == .windows_api) { |
| 492 | try self.writeDiff(writer, ".", .{}, diff); |
| 493 | continue; |
| 494 | } |
| 495 | |
| 496 | // Let's print some common control codes as graphical Unicode symbols. |
| 497 | // We don't want to do this for all control codes because most control codes apart from |
| 498 | // the ones that Zig has escape sequences for are likely not very useful to print as symbols. |
| 499 | switch (byte) { |
| 500 | '\n' => try self.writeDiff(writer, "␊", .{}, diff), |
| 501 | '\r' => try self.writeDiff(writer, "␍", .{}, diff), |
| 502 | '\t' => try self.writeDiff(writer, "␉", .{}, diff), |
| 503 | else => try self.writeDiff(writer, ".", .{}, diff), |
| 504 | } |
| 505 | } |
| 506 | } |
| 507 | try writer.writeByte('\n'); |
| 508 | row += 1; |
| 509 | } |
| 510 | } |
| 511 | |
| 512 | fn terminal(self: *const BytesDiffer, writer: *Io.Writer) Io.Terminal { |
| 513 | return .{ .writer = writer, .mode = self.terminal_mode }; |
| 514 | } |
| 515 | |
| 516 | fn writeDiff(self: BytesDiffer, writer: *Io.Writer, comptime fmt: []const u8, args: anytype, diff: bool) !void { |
| 517 | if (diff) try self.terminal(writer).setColor(.red); |
| 518 | try writer.print(fmt, args); |
| 519 | if (diff) try self.terminal(writer).setColor(.reset); |
| 520 | } |
| 521 | }; |
| 522 | |
| 523 | test expectEqualSlices { |
| 524 | try expectEqualSlices(u8, "foo\x00", "foo\x00"); |
| 525 | try expectEqualSlices(u16, &[_]u16{ 100, 200, 300, 400 }, &[_]u16{ 100, 200, 300, 400 }); |
| 526 | const E = enum { foo, bar }; |
| 527 | const S = struct { |
| 528 | v: E, |
| 529 | }; |
| 530 | try expectEqualSlices( |
| 531 | S, |
| 532 | &[_]S{ .{ .v = .foo }, .{ .v = .bar }, .{ .v = .foo }, .{ .v = .bar } }, |
| 533 | &[_]S{ .{ .v = .foo }, .{ .v = .bar }, .{ .v = .foo }, .{ .v = .bar } }, |
| 534 | ); |
| 535 | } |
| 536 | |
| 537 | /// This function is intended to be used only in tests. When the two slices or two arrays are not equal, |
| 538 | /// or their sentinel (if any) are not the same, it prints diagnostics to stderr to show exactly how |
| 539 | /// they are not equal (with the differences highlighted in red), then returns a test failure error. |
| 540 | /// It partially depends on `expectEquaSlices` for printing diagnostics. |
| 541 | pub fn expectEqualSentinel(comptime T: type, comptime sentinel: T, expected: [:sentinel]const T, actual: [:sentinel]const T) !void { |
| 542 | try expectEqualSlices(T, expected, actual); |
| 543 | |
| 544 | const expected_value_sentinel = blk: { |
| 545 | switch (@typeInfo(@TypeOf(expected))) { |
| 546 | .pointer => { |
| 547 | break :blk expected[expected.len]; |
| 548 | }, |
| 549 | .array => |array_info| { |
| 550 | const indexable_outside_of_bounds = @as([]const array_info.child, &expected); |
| 551 | break :blk indexable_outside_of_bounds[indexable_outside_of_bounds.len]; |
| 552 | }, |
| 553 | else => {}, |
| 554 | } |
| 555 | }; |
| 556 | |
| 557 | const actual_value_sentinel = blk: { |
| 558 | switch (@typeInfo(@TypeOf(actual))) { |
| 559 | .pointer => { |
| 560 | break :blk actual[actual.len]; |
| 561 | }, |
| 562 | .array => |array_info| { |
| 563 | const indexable_outside_of_bounds = @as([]const array_info.child, &actual); |
| 564 | break :blk indexable_outside_of_bounds[indexable_outside_of_bounds.len]; |
| 565 | }, |
| 566 | else => {}, |
| 567 | } |
| 568 | }; |
| 569 | |
| 570 | if (!std.meta.eql(sentinel, expected_value_sentinel)) { |
| 571 | failPrint("expectEqualSentinel: 'expected' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {}\n", .{ sentinel, expected_value_sentinel }); |
| 572 | return error.TestExpectedEqual; |
| 573 | } |
| 574 | |
| 575 | if (!std.meta.eql(sentinel, actual_value_sentinel)) { |
| 576 | failPrint("expectEqualSentinel: 'actual' sentinel in memory is different from its type sentinel. type sentinel {}, in memory sentinel {}\n", .{ sentinel, actual_value_sentinel }); |
| 577 | return error.TestExpectedEqual; |
| 578 | } |
| 579 | } |
| 580 | |
| 581 | /// This function is intended to be used only in tests. |
| 582 | /// When `ok` is false, returns a test failure error. |
| 583 | pub fn expect(ok: bool) !void { |
| 584 | if (!ok) return error.TestUnexpectedResult; |
| 585 | } |
| 586 | |
| 587 | pub const TmpDir = struct { |
| 588 | dir: Io.Dir, |
| 589 | parent_dir: Io.Dir, |
| 590 | sub_path: [sub_path_len]u8, |
| 591 | |
| 592 | const random_bytes_count = 12; |
| 593 | const sub_path_len = std.base64.url_safe.Encoder.calcSize(random_bytes_count); |
| 594 | |
| 595 | pub fn cleanup(self: *TmpDir) void { |
| 596 | self.dir.close(io); |
| 597 | self.parent_dir.deleteTree(io, &self.sub_path) catch {}; |
| 598 | self.parent_dir.close(io); |
| 599 | self.* = undefined; |
| 600 | } |
| 601 | }; |
| 602 | |
| 603 | pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir { |
| 604 | comptime assert(builtin.is_test); |
| 605 | var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; |
| 606 | io.random(&random_bytes); |
| 607 | var sub_path: [TmpDir.sub_path_len]u8 = undefined; |
| 608 | _ = std.base64.url_safe.Encoder.encode(&sub_path, &random_bytes); |
| 609 | |
| 610 | const cwd = Io.Dir.cwd(); |
| 611 | var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch |
| 612 | @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); |
| 613 | defer cache_dir.close(io); |
| 614 | const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch |
| 615 | @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); |
| 616 | const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch |
| 617 | @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); |
| 618 | |
| 619 | return .{ |
| 620 | .dir = dir, |
| 621 | .parent_dir = parent_dir, |
| 622 | .sub_path = sub_path, |
| 623 | }; |
| 624 | } |
| 625 | |
| 626 | /// This function is intended to be used only in tests. When `actual_error_union` is not |
| 627 | /// `expected_error`, it prints diagnostics to stderr, then returns a test failure error. |
| 628 | pub fn expectError(expected_error: anyerror, actual_error_union: anytype) !void { |
| 629 | if (actual_error_union) |actual_payload| { |
| 630 | failPrint("expected error.{s}, found {any}\n", .{ @errorName(expected_error), actual_payload }); |
| 631 | return error.TestExpectedError; |
| 632 | } else |actual_error| { |
| 633 | if (expected_error != actual_error) { |
| 634 | failPrint("expected error.{s}, found error.{s}\n", .{ |
| 635 | @errorName(expected_error), |
| 636 | @errorName(actual_error), |
| 637 | }); |
| 638 | return error.TestUnexpectedError; |
| 639 | } |
| 640 | } |
| 641 | } |
| 642 | |
| 643 | fn returnErrorUnion() !u8 { |
| 644 | return error.Expected; |
| 645 | } |
| 646 | |
| 647 | test expectError { |
| 648 | const actualErrorUnion = returnErrorUnion(); |
| 649 | try expectError(error.Expected, actualErrorUnion); |
| 650 | } |
| 651 | |
| 652 | /// This function is intended to be used only in tests. When the formatted result of the template |
| 653 | /// and its arguments does not equal the expected text, it prints diagnostics to stderr to show how |
| 654 | /// they are not equal, then returns an error. It depends on `expectEqualStrings` for printing |
| 655 | /// diagnostics. |
| 656 | pub fn expectFmt(expected: []const u8, comptime template: []const u8, args: anytype) !void { |
| 657 | if (@inComptime()) { |
| 658 | var buffer: [std.fmt.count(template, args)]u8 = undefined; |
| 659 | return expectEqualStrings(expected, try std.mem.print(&buffer, template, args)); |
| 660 | } |
| 661 | const actual = try std.fmt.allocPrint(allocator, template, args); |
| 662 | defer allocator.free(actual); |
| 663 | return expectEqualStrings(expected, actual); |
| 664 | } |
| 665 | |
| 666 | // This function is intended to be used only in test. When the two strings are not equal, |
| 667 | /// it prints diagnostics to stderr to show how they are not equal, then returns an error. |
| 668 | pub fn expectEqualStrings(expected: []const u8, actual: []const u8) !void { |
| 669 | if (std.mem.findDiff(u8, actual, expected)) |diff_index| { |
| 670 | if (@inComptime()) { |
| 671 | @compileError(std.fmt.comptimePrint("\nexpected:\n{s}\nfound:\n{s}\ndifference starts at index {d}", .{ |
| 672 | expected, actual, diff_index, |
| 673 | })); |
| 674 | } |
| 675 | failPrint("\n====== expected this output: =========\n", .{}); |
| 676 | failPrintWithVisibleNewlines(expected); |
| 677 | failPrint("\n======== instead found this: =========\n", .{}); |
| 678 | failPrintWithVisibleNewlines(actual); |
| 679 | failPrint("\n======================================\n", .{}); |
| 680 | |
| 681 | var diff_line_number: usize = 1; |
| 682 | for (expected[0..diff_index]) |value| { |
| 683 | if (value == '\n') diff_line_number += 1; |
| 684 | } |
| 685 | failPrint("First difference occurs on line {d}:\n", .{diff_line_number}); |
| 686 | |
| 687 | failPrint("expected:\n", .{}); |
| 688 | failPrintIndicatorLine(expected, diff_index); |
| 689 | |
| 690 | failPrint("found:\n", .{}); |
| 691 | failPrintIndicatorLine(actual, diff_index); |
| 692 | |
| 693 | return error.TestExpectedEqual; |
| 694 | } |
| 695 | } |
| 696 | |
| 697 | test expectEqualStrings { |
| 698 | try expectEqualStrings("foo", "foo"); |
| 699 | } |
| 700 | |
| 701 | /// This function is intended to be used only in test. When the start of `actual` and `expected_starts_with` |
| 702 | /// are not equal, it prints diagnostics to stderr to show how they are not equal, then returns an error. |
| 703 | pub fn expectStringStartsWith(actual: []const u8, expected_starts_with: []const u8) !void { |
| 704 | if (std.mem.startsWith(u8, actual, expected_starts_with)) |
| 705 | return; |
| 706 | |
| 707 | const shortened_actual = if (actual.len >= expected_starts_with.len) |
| 708 | actual[0..expected_starts_with.len] |
| 709 | else |
| 710 | actual; |
| 711 | |
| 712 | failPrint("\n====== expected to start with: =========\n", .{}); |
| 713 | failPrintWithVisibleNewlines(expected_starts_with); |
| 714 | failPrint("\n====== instead started with: ===========\n", .{}); |
| 715 | failPrintWithVisibleNewlines(shortened_actual); |
| 716 | failPrint("\n========= full output: ==============\n", .{}); |
| 717 | failPrintWithVisibleNewlines(actual); |
| 718 | failPrint("\n======================================\n", .{}); |
| 719 | |
| 720 | return error.TestExpectedStartsWith; |
| 721 | } |
| 722 | |
| 723 | test expectStringStartsWith { |
| 724 | try expectStringStartsWith("foobar", "foo"); |
| 725 | } |
| 726 | |
| 727 | /// This function is intended to be used only in test. When the end of `actual` and `expected_ends_with` |
| 728 | /// are not equal, it prints diagnostics to stderr to show how they are not equal, then returns an error. |
| 729 | pub fn expectStringEndsWith(actual: []const u8, expected_ends_with: []const u8) !void { |
| 730 | if (std.mem.endsWith(u8, actual, expected_ends_with)) |
| 731 | return; |
| 732 | |
| 733 | const shortened_actual = if (actual.len >= expected_ends_with.len) |
| 734 | actual[(actual.len - expected_ends_with.len)..] |
| 735 | else |
| 736 | actual; |
| 737 | |
| 738 | failPrint("\n====== expected to end with: =========\n", .{}); |
| 739 | failPrintWithVisibleNewlines(expected_ends_with); |
| 740 | failPrint("\n====== instead ended with: ===========\n", .{}); |
| 741 | failPrintWithVisibleNewlines(shortened_actual); |
| 742 | failPrint("\n========= full output: ==============\n", .{}); |
| 743 | failPrintWithVisibleNewlines(actual); |
| 744 | failPrint("\n======================================\n", .{}); |
| 745 | |
| 746 | return error.TestExpectedEndsWith; |
| 747 | } |
| 748 | |
| 749 | test expectStringEndsWith { |
| 750 | try expectStringEndsWith("foobar", "bar"); |
| 751 | } |
| 752 | |
| 753 | /// This function is intended to be used only in tests. When the two values are not |
| 754 | /// deeply equal, prints diagnostics to stderr to show exactly how they are not equal, |
| 755 | /// then returns a test failure error. |
| 756 | /// `actual` and `expected` are coerced to a common type using peer type resolution. |
| 757 | /// |
| 758 | /// Deeply equal is defined as follows: |
| 759 | /// Primitive types are deeply equal if they are equal using `==` operator. |
| 760 | /// Struct values are deeply equal if their corresponding fields are deeply equal. |
| 761 | /// Container types(like Array/Slice/Vector) deeply equal when their corresponding elements are deeply equal. |
| 762 | /// Pointer values are deeply equal if values they point to are deeply equal. |
| 763 | /// |
| 764 | /// Note: Self-referential structs are supported (e.g. things like std.SinglyLinkedList) |
| 765 | /// but may cause infinite recursion or stack overflow when a container has a pointer to itself. |
| 766 | pub inline fn expectEqualDeep(expected: anytype, actual: anytype) error{TestExpectedEqual}!void { |
| 767 | const T = @TypeOf(expected, actual); |
| 768 | return expectEqualDeepInner(T, expected, actual); |
| 769 | } |
| 770 | |
| 771 | fn expectEqualDeepInner(comptime T: type, expected: T, actual: T) error{TestExpectedEqual}!void { |
| 772 | switch (@typeInfo(@TypeOf(actual))) { |
| 773 | .noreturn, |
| 774 | .@"opaque", |
| 775 | .spirv, |
| 776 | .frame, |
| 777 | .@"anyframe", |
| 778 | => @compileError("value of type " ++ @typeName(@TypeOf(actual)) ++ " encountered"), |
| 779 | |
| 780 | .undefined, |
| 781 | .null, |
| 782 | .void, |
| 783 | => return, |
| 784 | |
| 785 | .type => { |
| 786 | if (actual != expected) { |
| 787 | failPrint("expected type {s}, found type {s}\n", .{ @typeName(expected), @typeName(actual) }); |
| 788 | return error.TestExpectedEqual; |
| 789 | } |
| 790 | }, |
| 791 | |
| 792 | .bool, |
| 793 | .int, |
| 794 | .float, |
| 795 | .comptime_float, |
| 796 | .comptime_int, |
| 797 | .enum_literal, |
| 798 | .@"enum", |
| 799 | .@"fn", |
| 800 | .error_set, |
| 801 | => { |
| 802 | if (actual != expected) { |
| 803 | failPrint("expected {any}, found {any}\n", .{ expected, actual }); |
| 804 | return error.TestExpectedEqual; |
| 805 | } |
| 806 | }, |
| 807 | |
| 808 | .pointer => |pointer| { |
| 809 | switch (pointer.size) { |
| 810 | // We have no idea what is behind those pointers, so the best we can do is `==` check. |
| 811 | .c, .many => { |
| 812 | if (actual != expected) { |
| 813 | failPrint("expected {*}, found {*}\n", .{ expected, actual }); |
| 814 | return error.TestExpectedEqual; |
| 815 | } |
| 816 | }, |
| 817 | .one => { |
| 818 | // Length of those pointers are runtime value, so the best we can do is `==` check. |
| 819 | switch (@typeInfo(pointer.child)) { |
| 820 | .@"fn", .@"opaque" => { |
| 821 | if (actual != expected) { |
| 822 | failPrint("expected {*}, found {*}\n", .{ expected, actual }); |
| 823 | return error.TestExpectedEqual; |
| 824 | } |
| 825 | }, |
| 826 | else => try expectEqualDeep(expected.*, actual.*), |
| 827 | } |
| 828 | }, |
| 829 | .slice => { |
| 830 | if (expected.len != actual.len) { |
| 831 | failPrint("Slice len not the same, expected {d}, found {d}\n", .{ expected.len, actual.len }); |
| 832 | return error.TestExpectedEqual; |
| 833 | } |
| 834 | var i: usize = 0; |
| 835 | while (i < expected.len) : (i += 1) { |
| 836 | expectEqualDeep(expected[i], actual[i]) catch |e| { |
| 837 | failPrint("index {d} incorrect. expected {any}, found {any}\n", .{ |
| 838 | i, expected[i], actual[i], |
| 839 | }); |
| 840 | return e; |
| 841 | }; |
| 842 | } |
| 843 | }, |
| 844 | } |
| 845 | }, |
| 846 | |
| 847 | .array => { |
| 848 | if (expected.len != actual.len) { |
| 849 | failPrint("Array len not the same, expected {d}, found {d}\n", .{ expected.len, actual.len }); |
| 850 | return error.TestExpectedEqual; |
| 851 | } |
| 852 | var i: usize = 0; |
| 853 | while (i < expected.len) : (i += 1) { |
| 854 | expectEqualDeep(expected[i], actual[i]) catch |e| { |
| 855 | failPrint("index {d} incorrect. expected {any}, found {any}\n", .{ |
| 856 | i, expected[i], actual[i], |
| 857 | }); |
| 858 | return e; |
| 859 | }; |
| 860 | } |
| 861 | }, |
| 862 | |
| 863 | .vector => |info| { |
| 864 | if (info.len != @typeInfo(@TypeOf(actual)).vector.len) { |
| 865 | failPrint("Vector len not the same, expected {d}, found {d}\n", .{ info.len, @typeInfo(@TypeOf(actual)).vector.len }); |
| 866 | return error.TestExpectedEqual; |
| 867 | } |
| 868 | inline for (0..info.len) |i| { |
| 869 | expectEqualDeep(expected[i], actual[i]) catch |e| { |
| 870 | failPrint("index {d} incorrect. expected {any}, found {any}\n", .{ |
| 871 | i, expected[i], actual[i], |
| 872 | }); |
| 873 | return e; |
| 874 | }; |
| 875 | } |
| 876 | }, |
| 877 | |
| 878 | .@"struct" => |structType| { |
| 879 | inline for (structType.field_names) |field_name| { |
| 880 | expectEqualDeep(@field(expected, field_name), @field(actual, field_name)) catch |e| { |
| 881 | failPrint("Field {s} incorrect. expected {any}, found {any}\n", .{ field_name, @field(expected, field_name), @field(actual, field_name) }); |
| 882 | return e; |
| 883 | }; |
| 884 | } |
| 885 | }, |
| 886 | |
| 887 | .@"union" => |union_info| { |
| 888 | if (union_info.tag_type == null) { |
| 889 | @compileError("Unable to compare untagged union values for type " ++ @typeName(@TypeOf(actual))); |
| 890 | } |
| 891 | |
| 892 | const Tag = std.meta.Tag(@TypeOf(expected)); |
| 893 | |
| 894 | const expectedTag = @as(Tag, expected); |
| 895 | const actualTag = @as(Tag, actual); |
| 896 | |
| 897 | try expectEqual(expectedTag, actualTag); |
| 898 | |
| 899 | // we only reach this switch if the tags are equal |
| 900 | switch (expected) { |
| 901 | inline else => |val, tag| { |
| 902 | try expectEqualDeep(val, @field(actual, @tagName(tag))); |
| 903 | }, |
| 904 | } |
| 905 | }, |
| 906 | |
| 907 | .optional => { |
| 908 | if (expected) |expected_payload| { |
| 909 | if (actual) |actual_payload| { |
| 910 | try expectEqualDeep(expected_payload, actual_payload); |
| 911 | } else { |
| 912 | failPrint("expected {any}, found null\n", .{expected_payload}); |
| 913 | return error.TestExpectedEqual; |
| 914 | } |
| 915 | } else { |
| 916 | if (actual) |actual_payload| { |
| 917 | failPrint("expected null, found {any}\n", .{actual_payload}); |
| 918 | return error.TestExpectedEqual; |
| 919 | } |
| 920 | } |
| 921 | }, |
| 922 | |
| 923 | .error_union => { |
| 924 | if (expected) |expected_payload| { |
| 925 | if (actual) |actual_payload| { |
| 926 | try expectEqualDeep(expected_payload, actual_payload); |
| 927 | } else |actual_err| { |
| 928 | failPrint("expected {any}, found {any}\n", .{ expected_payload, actual_err }); |
| 929 | return error.TestExpectedEqual; |
| 930 | } |
| 931 | } else |expected_err| { |
| 932 | if (actual) |actual_payload| { |
| 933 | failPrint("expected {any}, found {any}\n", .{ expected_err, actual_payload }); |
| 934 | return error.TestExpectedEqual; |
| 935 | } else |actual_err| { |
| 936 | try expectEqualDeep(expected_err, actual_err); |
| 937 | } |
| 938 | } |
| 939 | }, |
| 940 | } |
| 941 | } |
| 942 | |
| 943 | test "expectEqualDeep primitive type" { |
| 944 | try expectEqualDeep(1, 1); |
| 945 | try expectEqualDeep(true, true); |
| 946 | try expectEqualDeep(1.5, 1.5); |
| 947 | try expectEqualDeep(u8, u8); |
| 948 | try expectEqualDeep(error.Bad, error.Bad); |
| 949 | |
| 950 | // optional |
| 951 | { |
| 952 | const foo: ?u32 = 1; |
| 953 | const bar: ?u32 = 1; |
| 954 | try expectEqualDeep(foo, bar); |
| 955 | try expectEqualDeep(?u32, ?u32); |
| 956 | } |
| 957 | // function type |
| 958 | { |
| 959 | const fnType = struct { |
| 960 | fn foo() void { |
| 961 | unreachable; |
| 962 | } |
| 963 | }.foo; |
| 964 | try expectEqualDeep(fnType, fnType); |
| 965 | } |
| 966 | // enum with formatter |
| 967 | { |
| 968 | const TestEnum = enum { |
| 969 | a, |
| 970 | b, |
| 971 | |
| 972 | pub fn format(self: @This(), writer: *Io.Writer) !void { |
| 973 | try writer.writeAll(@tagName(self)); |
| 974 | } |
| 975 | }; |
| 976 | try expectEqualDeep(TestEnum.b, TestEnum.b); |
| 977 | } |
| 978 | } |
| 979 | |
| 980 | test "expectEqualDeep pointer" { |
| 981 | try comptime expectEqualDeep(&1, &1); |
| 982 | try expectEqualDeep(&@as(u32, 1), &@as(u32, 1)); |
| 983 | } |
| 984 | |
| 985 | test "expectEqualDeep composite type" { |
| 986 | try expectEqualDeep("abc", "abc"); |
| 987 | const s1: []const u8 = "abc"; |
| 988 | const s2 = "abcd"; |
| 989 | const s3: []const u8 = s2[0..3]; |
| 990 | try expectEqualDeep(s1, s3); |
| 991 | |
| 992 | const TestStruct = struct { s: []const u8 }; |
| 993 | try expectEqualDeep(TestStruct{ .s = "abc" }, TestStruct{ .s = "abc" }); |
| 994 | try expectEqualDeep([_][]const u8{ "a", "b", "c" }, [_][]const u8{ "a", "b", "c" }); |
| 995 | |
| 996 | // vector |
| 997 | try expectEqualDeep(@as(@Vector(4, u32), @splat(4)), @as(@Vector(4, u32), @splat(4))); |
| 998 | |
| 999 | // nested array |
| 1000 | { |
| 1001 | const a = [2][2]f32{ |
| 1002 | [_]f32{ 1.0, 0.0 }, |
| 1003 | [_]f32{ 0.0, 1.0 }, |
| 1004 | }; |
| 1005 | |
| 1006 | const b = [2][2]f32{ |
| 1007 | [_]f32{ 1.0, 0.0 }, |
| 1008 | [_]f32{ 0.0, 1.0 }, |
| 1009 | }; |
| 1010 | |
| 1011 | try expectEqualDeep(a, b); |
| 1012 | try expectEqualDeep(&a, &b); |
| 1013 | } |
| 1014 | |
| 1015 | // inferred union |
| 1016 | const TestStruct2 = struct { |
| 1017 | const A = union(enum) { b: B, c: C }; |
| 1018 | const B = struct {}; |
| 1019 | const C = struct { a: *const A }; |
| 1020 | }; |
| 1021 | |
| 1022 | const union1 = TestStruct2.A{ .b = .{} }; |
| 1023 | try expectEqualDeep( |
| 1024 | TestStruct2.A{ .c = .{ .a = &union1 } }, |
| 1025 | TestStruct2.A{ .c = .{ .a = &union1 } }, |
| 1026 | ); |
| 1027 | } |
| 1028 | |
| 1029 | /// Helper function for printing test failure information. |
| 1030 | pub fn failPrintIndicatorLine(source: []const u8, indicator_index: usize) void { |
| 1031 | const line_begin_index = if (std.mem.findScalarLast(u8, source[0..indicator_index], '\n')) |line_begin| |
| 1032 | line_begin + 1 |
| 1033 | else |
| 1034 | 0; |
| 1035 | const line_end_index = if (std.mem.findScalar(u8, source[indicator_index..], '\n')) |line_end| |
| 1036 | (indicator_index + line_end) |
| 1037 | else |
| 1038 | source.len; |
| 1039 | |
| 1040 | failPrintLine(source[line_begin_index..line_end_index]); |
| 1041 | for (line_begin_index..indicator_index) |_| |
| 1042 | failPrint(" ", .{}); |
| 1043 | if (indicator_index >= source.len) |
| 1044 | failPrint("^ (end of string)\n", .{}) |
| 1045 | else |
| 1046 | failPrint("^ ('\\x{x:0>2}')\n", .{source[indicator_index]}); |
| 1047 | } |
| 1048 | |
| 1049 | /// Helper function for printing test failure information. |
| 1050 | pub fn failPrintWithVisibleNewlines(source: []const u8) void { |
| 1051 | var i: usize = 0; |
| 1052 | while (std.mem.findScalar(u8, source[i..], '\n')) |nl| : (i += nl + 1) { |
| 1053 | failPrintLine(source[i..][0..nl]); |
| 1054 | } |
| 1055 | failPrint("{s}␃\n", .{source[i..]}); // End of Text symbol (ETX) |
| 1056 | } |
| 1057 | |
| 1058 | /// Helper function for printing test failure information. |
| 1059 | pub fn failPrintLine(line: []const u8) void { |
| 1060 | if (line.len != 0) switch (line[line.len - 1]) { |
| 1061 | ' ', '\t' => return failPrint("{s}⏎\n", .{line}), // Return symbol |
| 1062 | else => {}, |
| 1063 | }; |
| 1064 | failPrint("{s}\n", .{line}); |
| 1065 | } |
| 1066 | |
| 1067 | /// Exhaustively check that allocation failures within `test_fn` are handled without |
| 1068 | /// introducing memory leaks. If used with the `testing.allocator` as the `backing_allocator`, |
| 1069 | /// it will also be able to detect double frees, etc (when runtime safety is enabled). |
| 1070 | /// |
| 1071 | /// The provided `test_fn` must have a `std.mem.Allocator` as its first argument, |
| 1072 | /// and must have a return type of `!void`. Any extra arguments of `test_fn` can |
| 1073 | /// be provided via the `extra_args` tuple. |
| 1074 | /// |
| 1075 | /// Any relevant state shared between runs of `test_fn` *must* be reset within `test_fn`. |
| 1076 | /// |
| 1077 | /// The strategy employed is to: |
| 1078 | /// - Run the test function once to get the total number of allocations. |
| 1079 | /// - Then, iterate and run the function X more times, incrementing |
| 1080 | /// the failing index each iteration (where X is the total number of |
| 1081 | /// allocations determined previously) |
| 1082 | /// |
| 1083 | /// Expects that `test_fn` has a deterministic number of memory allocations: |
| 1084 | /// - If an allocation was made to fail during a run of `test_fn`, but `test_fn` |
| 1085 | /// didn't return `error.OutOfMemory`, then `error.SwallowedOutOfMemoryError` |
| 1086 | /// is returned from `checkAllAllocationFailures`. You may want to ignore this |
| 1087 | /// depending on whether or not the code you're testing includes some strategies |
| 1088 | /// for recovering from `error.OutOfMemory`. |
| 1089 | /// - If a run of `test_fn` with an expected allocation failure executes without |
| 1090 | /// an allocation failure being induced, then `error.NondeterministicMemoryUsage` |
| 1091 | /// is returned. This error means that there are allocation points that won't be |
| 1092 | /// tested by the strategy this function employs (that is, there are sometimes more |
| 1093 | /// points of allocation than the initial run of `test_fn` detects). |
| 1094 | /// |
| 1095 | /// --- |
| 1096 | /// |
| 1097 | /// Here's an example using a simple test case that will cause a leak when the |
| 1098 | /// allocation of `bar` fails (but will pass normally): |
| 1099 | /// |
| 1100 | /// ```zig |
| 1101 | /// test { |
| 1102 | /// const length: usize = 10; |
| 1103 | /// const allocator = std.testing.allocator; |
| 1104 | /// var foo = try allocator.alloc(u8, length); |
| 1105 | /// var bar = try allocator.alloc(u8, length); |
| 1106 | /// |
| 1107 | /// allocator.free(foo); |
| 1108 | /// allocator.free(bar); |
| 1109 | /// } |
| 1110 | /// ``` |
| 1111 | /// |
| 1112 | /// The test case can be converted to something that this function can use by |
| 1113 | /// doing: |
| 1114 | /// |
| 1115 | /// ```zig |
| 1116 | /// fn testImpl(allocator: std.mem.Allocator, length: usize) !void { |
| 1117 | /// var foo = try allocator.alloc(u8, length); |
| 1118 | /// var bar = try allocator.alloc(u8, length); |
| 1119 | /// |
| 1120 | /// allocator.free(foo); |
| 1121 | /// allocator.free(bar); |
| 1122 | /// } |
| 1123 | /// |
| 1124 | /// test { |
| 1125 | /// const length: usize = 10; |
| 1126 | /// const allocator = std.testing.allocator; |
| 1127 | /// try std.testing.checkAllAllocationFailures(allocator, testImpl, .{length}); |
| 1128 | /// } |
| 1129 | /// ``` |
| 1130 | /// |
| 1131 | /// Running this test will show that `foo` is leaked when the allocation of |
| 1132 | /// `bar` fails. The simplest fix, in this case, would be to use defer like so: |
| 1133 | /// |
| 1134 | /// ```zig |
| 1135 | /// fn testImpl(allocator: std.mem.Allocator, length: usize) !void { |
| 1136 | /// var foo = try allocator.alloc(u8, length); |
| 1137 | /// defer allocator.free(foo); |
| 1138 | /// var bar = try allocator.alloc(u8, length); |
| 1139 | /// defer allocator.free(bar); |
| 1140 | /// } |
| 1141 | /// ``` |
| 1142 | pub fn checkAllAllocationFailures( |
| 1143 | backing_allocator: std.mem.Allocator, |
| 1144 | comptime test_fn: anytype, |
| 1145 | extra_args: CheckAllAllocationFailuresExtraArgs(@TypeOf(test_fn)), |
| 1146 | ) !void { |
| 1147 | // Try it once with unlimited memory, make sure it works |
| 1148 | const needed_alloc_count = x: { |
| 1149 | var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{}); |
| 1150 | |
| 1151 | try @call(.auto, test_fn, .{failing_allocator_inst.allocator()} ++ extra_args); |
| 1152 | break :x failing_allocator_inst.alloc_index; |
| 1153 | }; |
| 1154 | |
| 1155 | for (0..needed_alloc_count) |fail_index| { |
| 1156 | var failing_allocator_inst = std.testing.FailingAllocator.init(backing_allocator, .{ |
| 1157 | .fail_index = fail_index, |
| 1158 | }); |
| 1159 | |
| 1160 | if (@call(.auto, test_fn, .{failing_allocator_inst.allocator()} ++ extra_args)) |_| { |
| 1161 | if (failing_allocator_inst.has_induced_failure) { |
| 1162 | return error.SwallowedOutOfMemoryError; |
| 1163 | } else { |
| 1164 | return error.NondeterministicMemoryUsage; |
| 1165 | } |
| 1166 | } else |err| switch (err) { |
| 1167 | error.OutOfMemory => { |
| 1168 | if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) { |
| 1169 | failPrint( |
| 1170 | "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}", |
| 1171 | .{ |
| 1172 | fail_index, |
| 1173 | needed_alloc_count, |
| 1174 | failing_allocator_inst.allocated_bytes, |
| 1175 | failing_allocator_inst.freed_bytes, |
| 1176 | failing_allocator_inst.allocations, |
| 1177 | failing_allocator_inst.deallocations, |
| 1178 | std.debug.FormatStackTrace{ |
| 1179 | .stack_trace = failing_allocator_inst.getStackTrace(), |
| 1180 | }, |
| 1181 | }, |
| 1182 | ); |
| 1183 | return error.MemoryLeakDetected; |
| 1184 | } |
| 1185 | }, |
| 1186 | else => |e| return e, |
| 1187 | } |
| 1188 | } |
| 1189 | } |
| 1190 | |
| 1191 | fn CheckAllAllocationFailuresExtraArgs(comptime TestFn: type) type { |
| 1192 | switch (@typeInfo(@typeInfo(TestFn).@"fn".return_type.?)) { |
| 1193 | .error_union => |info| { |
| 1194 | if (info.payload != void) { |
| 1195 | @compileError("Return type must be !void"); |
| 1196 | } |
| 1197 | }, |
| 1198 | else => @compileError("Return type must be !void"), |
| 1199 | } |
| 1200 | |
| 1201 | const ArgsTuple = std.meta.ArgsTuple(TestFn); |
| 1202 | |
| 1203 | const field_types = @typeInfo(ArgsTuple).@"struct".field_types; |
| 1204 | if (field_types.len == 0 or field_types[0] != std.mem.Allocator) { |
| 1205 | @compileError("The provided function must have an " ++ @typeName(std.mem.Allocator) ++ " as its first argument"); |
| 1206 | } |
| 1207 | |
| 1208 | var extra_args: [field_types.len - 1]type = undefined; |
| 1209 | for (&extra_args, field_types[1..]) |*arg, field_type| { |
| 1210 | arg.* = field_type; |
| 1211 | } |
| 1212 | |
| 1213 | return @Tuple(&extra_args); |
| 1214 | } |
| 1215 | |
| 1216 | test "checkAllAllocationFailures provide result type to 'extra_args' argument" { |
| 1217 | try checkAllAllocationFailures( |
| 1218 | std.testing.allocator, |
| 1219 | struct { |
| 1220 | fn f(ally: std.mem.Allocator, params: struct { |
| 1221 | foo_len: u32, |
| 1222 | bar_len: u32, |
| 1223 | }) !void { |
| 1224 | const foo = try ally.alloc(u8, params.foo_len); |
| 1225 | defer ally.free(foo); |
| 1226 | const bar = try ally.alloc(u8, params.bar_len); |
| 1227 | defer ally.free(bar); |
| 1228 | } |
| 1229 | }.f, |
| 1230 | .{ |
| 1231 | .{ |
| 1232 | .foo_len = 3, |
| 1233 | .bar_len = 5, |
| 1234 | }, |
| 1235 | }, |
| 1236 | ); |
| 1237 | } |
| 1238 | |
| 1239 | /// Given a type, references all the declarations inside, so that the semantic analyzer sees them. |
| 1240 | pub fn refAllDecls(comptime T: type) void { |
| 1241 | if (!builtin.is_test) return; |
| 1242 | inline for (comptime std.meta.declarations(T)) |decl_name| { |
| 1243 | _ = &@field(T, decl_name); |
| 1244 | } |
| 1245 | } |
| 1246 | |
| 1247 | pub const Smith = @import("testing/Smith.zig"); |
| 1248 | |
| 1249 | pub const FuzzInputOptions = struct { |
| 1250 | corpus: []const []const u8 = &.{}, |
| 1251 | }; |
| 1252 | |
| 1253 | /// Inline to avoid coverage instrumentation. |
| 1254 | pub inline fn fuzz( |
| 1255 | context: anytype, |
| 1256 | comptime testOne: fn (context: @TypeOf(context), smith: *Smith) anyerror!void, |
| 1257 | options: FuzzInputOptions, |
| 1258 | ) anyerror!void { |
| 1259 | return @import("root").fuzz(context, testOne, options); |
| 1260 | } |
| 1261 | |
| 1262 | /// A `Io.Reader` that writes a predetermined list of buffers during `stream`. |
| 1263 | pub const Reader = struct { |
| 1264 | calls: []const Call, |
| 1265 | interface: Io.Reader, |
| 1266 | next_call_index: usize, |
| 1267 | next_offset: usize, |
| 1268 | /// Further reduces how many bytes are written in each `stream` call. |
| 1269 | artificial_limit: Io.Limit = .unlimited, |
| 1270 | |
| 1271 | pub const Call = struct { |
| 1272 | buffer: []const u8, |
| 1273 | }; |
| 1274 | |
| 1275 | pub fn init(buffer: []u8, calls: []const Call) Reader { |
| 1276 | return .{ |
| 1277 | .next_call_index = 0, |
| 1278 | .next_offset = 0, |
| 1279 | .interface = .{ |
| 1280 | .vtable = &.{ .stream = stream }, |
| 1281 | .buffer = buffer, |
| 1282 | .seek = 0, |
| 1283 | .end = 0, |
| 1284 | }, |
| 1285 | .calls = calls, |
| 1286 | }; |
| 1287 | } |
| 1288 | |
| 1289 | fn stream(io_r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { |
| 1290 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); |
| 1291 | if (r.calls.len - r.next_call_index == 0) return error.EndOfStream; |
| 1292 | const call = r.calls[r.next_call_index]; |
| 1293 | const buffer = r.artificial_limit.sliceConst(limit.sliceConst(call.buffer[r.next_offset..])); |
| 1294 | const n = try w.write(buffer); |
| 1295 | r.next_offset += n; |
| 1296 | if (call.buffer.len - r.next_offset == 0) { |
| 1297 | r.next_call_index += 1; |
| 1298 | r.next_offset = 0; |
| 1299 | } |
| 1300 | return n; |
| 1301 | } |
| 1302 | }; |
| 1303 | |
| 1304 | /// A `Io.Reader` that gets its data from another `Io.Reader`, and always |
| 1305 | /// writes to its own buffer (and returns 0) during `stream` and `readVec`. |
| 1306 | pub const ReaderIndirect = struct { |
| 1307 | in: *Io.Reader, |
| 1308 | interface: Io.Reader, |
| 1309 | |
| 1310 | pub fn init(in: *Io.Reader, buffer: []u8) ReaderIndirect { |
| 1311 | return .{ |
| 1312 | .in = in, |
| 1313 | .interface = .{ |
| 1314 | .vtable = &.{ |
| 1315 | .stream = stream, |
| 1316 | .readVec = readVec, |
| 1317 | }, |
| 1318 | .buffer = buffer, |
| 1319 | .seek = 0, |
| 1320 | .end = 0, |
| 1321 | }, |
| 1322 | }; |
| 1323 | } |
| 1324 | |
| 1325 | fn readVec(r: *Io.Reader, _: [][]u8) Io.Reader.Error!usize { |
| 1326 | try streamInner(r); |
| 1327 | return 0; |
| 1328 | } |
| 1329 | |
| 1330 | fn stream(r: *Io.Reader, _: *Io.Writer, _: Io.Limit) Io.Reader.StreamError!usize { |
| 1331 | try streamInner(r); |
| 1332 | return 0; |
| 1333 | } |
| 1334 | |
| 1335 | fn streamInner(r: *Io.Reader) Io.Reader.Error!void { |
| 1336 | const r_indirect: *ReaderIndirect = @alignCast(@fieldParentPtr("interface", r)); |
| 1337 | |
| 1338 | // If there's no room remaining in the buffer at all, make room. |
| 1339 | if (r.buffer.len == r.end) { |
| 1340 | try r.rebase(r.buffer.len); |
| 1341 | } |
| 1342 | |
| 1343 | var writer: Io.Writer = .{ |
| 1344 | .buffer = r.buffer, |
| 1345 | .end = r.end, |
| 1346 | .vtable = &.{ |
| 1347 | .drain = Io.Writer.unreachableDrain, |
| 1348 | .rebase = Io.Writer.unreachableRebase, |
| 1349 | }, |
| 1350 | }; |
| 1351 | defer r.end = writer.end; |
| 1352 | |
| 1353 | r_indirect.in.streamExact(&writer, r.buffer.len - r.end) catch |err| switch (err) { |
| 1354 | // Only forward EndOfStream if no new bytes were written to the buffer |
| 1355 | error.EndOfStream => |e| if (r.end == writer.end) { |
| 1356 | return e; |
| 1357 | }, |
| 1358 | error.WriteFailed => unreachable, |
| 1359 | else => |e| return e, |
| 1360 | }; |
| 1361 | } |
| 1362 | }; |
| 1363 | |
| 1364 | /// A `Io.Writer` that writes its data to another `Io.Writer`, and only |
| 1365 | /// writes new data to its own buffer during `drain`. |
| 1366 | pub const WriterIndirect = struct { |
| 1367 | out: *Io.Writer, |
| 1368 | interface: Io.Writer, |
| 1369 | |
| 1370 | pub fn init(out: *Io.Writer, buffer: []u8) WriterIndirect { |
| 1371 | return .{ |
| 1372 | .out = out, |
| 1373 | .interface = .{ |
| 1374 | .vtable = &.{ |
| 1375 | .drain = drain, |
| 1376 | }, |
| 1377 | .buffer = buffer, |
| 1378 | .end = 0, |
| 1379 | }, |
| 1380 | }; |
| 1381 | } |
| 1382 | |
| 1383 | fn drain(w: *Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { |
| 1384 | const w_indirect: *WriterIndirect = @alignCast(@fieldParentPtr("interface", w)); |
| 1385 | |
| 1386 | // Write all data in the buffer to `out` |
| 1387 | try w_indirect.out.writeAll(w.buffer[0..w.end]); |
| 1388 | w.end = 0; |
| 1389 | |
| 1390 | // Refill buffer using data |
| 1391 | { |
| 1392 | const end_before_fill = w.end; |
| 1393 | for (data[0 .. data.len - 1]) |bytes| { |
| 1394 | const dest = w.buffer[w.end..]; |
| 1395 | const len = @min(bytes.len, dest.len); |
| 1396 | @memcpy(dest[0..len], bytes[0..len]); |
| 1397 | w.end += len; |
| 1398 | } |
| 1399 | const pattern = data[data.len - 1]; |
| 1400 | switch (pattern.len) { |
| 1401 | 0 => {}, |
| 1402 | 1 => { |
| 1403 | const len = @min(w.buffer[w.end..].len, splat); |
| 1404 | @memset(w.buffer[w.end..][0..len], pattern[0]); |
| 1405 | w.end += len; |
| 1406 | }, |
| 1407 | else => { |
| 1408 | const dest = w.buffer[w.end..]; |
| 1409 | for (0..splat) |i| { |
| 1410 | const start_i = i * pattern.len; |
| 1411 | if (start_i >= dest.len) break; |
| 1412 | const remaining = dest[start_i..]; |
| 1413 | const len = @min(pattern.len, remaining.len); |
| 1414 | @memcpy(remaining[0..len], pattern[0..len]); |
| 1415 | w.end += len; |
| 1416 | } |
| 1417 | }, |
| 1418 | } |
| 1419 | |
| 1420 | return w.end - end_before_fill; |
| 1421 | } |
| 1422 | } |
| 1423 | }; |
| 1424 | |
| 1425 | test { |
| 1426 | _ = &Smith; |
| 1427 | } |