zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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 }