How to use comptime values in table-driven tests?

I have an implementation which has the following function signature:

fn updateMatrix(alloc: std.mem.Allocator, comptime m: usize, comptime n: usize, grid: [m][n]u8) ![m][n]u8 {
    // create a copy of `grid`
    
    // modify the copy such that each 1 contains a distance to its nearest 0 (the 0 has to be 4-directionally
    // (up, down, left, rigth) adjacent)
    
    // return the updated copy
}

The 2D array grid consists of 0s and 1s only. The number of rows and columns in grid could be any positive value in the range [1, 100]

To test the implementation I have unit tests which look like the below (and run correctly):

test "the 1 at (2,1) is at distance 2 from the nearest zeroes, which are (1,0), (1,2), and (0,1)" {
    const grid = [3][3]u8{
        .{ 0, 0, 0 },
        .{ 0, 1, 0 },
        .{ 1, 1, 1 },
    };

    const expected = [3][3]u8{
        .{ 0, 0, 0 },
        .{ 0, 1, 0 },
        .{ 1, 2, 1 },
    };

    const result = try updateMatrix(testing.allocator, 3, 3, grid);
    try testing.expectEqualDeep(expected, result);
}


test "no changes in output - all 1s are already at their respective distance from the nearest 0" {
    const grid = [2][3]u8{
        .{1, 0, 1},
        .{0, 1, 0},
    };
    
    const expected = [2][3]u8{
        .{1, 0, 1},
        .{0, 1, 0},
    };
    
    const result = try updateMatrix(testing.allocator, 2, 3, grid);
    try testing.expectEqualDeep(expected, result);
}

For other programs, I have table-driven tests with the structure:

test declTest {
    const test_case = [_]struct{
        description: []const u8,
        input: []const u8,
        output: []const u8,
    }{
        .{
            .description = "example 1",
            .input = &[_]u8{1, 2, 3},
            .output = &[_]u8{4, 5, 6},
        },
        .{
            .description = "example 1",
            .input = &[_]u8{2, 3, 4},
            .output = &[_]u8{5, 6, 7},
        },
    };    
        
    for (test_case) |tc| {
        const got = try declTest(tc.input);
        const want = tc.output;
        
        std.debug.print("{s}\n", .{tc.description});
        try testing.expectEqual(want, got);
    }
}

but haven’t used this structure with comptime yet.

How can I convert the unit tests for updateMatrix to a table-driven test?

There shouldn’t be any issues as long as the things that need to be comptime are in fact comptime, which they will probably be.

Some relevant FYI: grabbing the len property on an array is always comptime as length is a part of the array’s type. The length of slices can be known at comptime, and creating a slice (a[start..end]) with a comptime known range actually creates a pointer to an array, meaning its length is comptime known.

Really anything in a const can be known at comptime.

1 Like

The only change needed is to make for an inline for.

Its more of a syntax related issue that I’m not able to figure out. For example:

test updateMatrix {
    const test_case = [_]struct {
        description: []const u8,
        grid: [_][_]u8,
        updated_grid: [_][_]u8,
    }{
        .{
            .description = "the 1 at (2,1) is at distance 2 from the nearest zeroes, which are (1,0), (1,2), and (0,1)",
            .grid = [3][3]u8{ .{ 0, 0, 0 }, .{ 0, 1, 0 }, .{ 1, 1, 1 } },
            .updated_grid = [3][3]u8{ .{ 0, 0, 0 }, .{ 0, 1, 0 }, .{ 1, 2, 1 } },
        },
        .{
            .description = "no changes in output - all 1s are already at their respective distance from the nearest 0",
            .grid = [2][3]u8{ .{ 1, 0, 1 }, .{ 0, 1, 0 } },
            .updated_grid = [2][3]u8{ .{ 1, 0, 1 }, .{ 0, 1, 0 } },
        },
    };

    inline for (test_case) |tc| {
        const got = try updateMatrix(testing.allocator, tc.grid.len, tc.grid[0].len, tc.grid);
        const want = tc.updated_grid;

        std.debug.print("{s}\n", .{});
        try testing.expectEqualDeep(want, got);
    }
}

gives

error: unable to infer array size
        grid: [_][_]u8,

I think it would be easier to instead create a function and have multiple calls to the function. So something like this:

fn testCase(
    allocator: std.mem.Allocator,
    description: []const u8,
    grid: anytype,
    updated_grid: anytype,
) !void {
    const got = try updateMatrix(allocator, grid.len, grid[0].len, grid);
    const want = updated_grid;

    std.debug.print("{s}\n", .{description});
    try testing.expectEqualDeep(want, got);
}

test updateMatrix {
    const a = std.testing.allocator;
    try testCase(
        a,
        "the 1 at (2,1) is at distance 2 from the nearest zeroes, which are (1,0), (1,2), and (0,1)",
        [3][3]u8{ .{ 0, 0, 0 }, .{ 0, 1, 0 }, .{ 1, 1, 1 } },
        [3][3]u8{ .{ 0, 0, 0 }, .{ 0, 1, 0 }, .{ 1, 2, 1 } },
    );
    try testCase(
        a,
        "no changes in output - all 1s are already at their respective distance from the nearest 0",
        [2][3]u8{ .{ 1, 0, 1 }, .{ 0, 1, 0 } },
        [2][3]u8{ .{ 1, 0, 1 }, .{ 0, 1, 0 } },
    );
}

fn updateMatrix(alloc: std.mem.Allocator, comptime m: usize, comptime n: usize, grid: [m][n]u8) ![m][n]u8 {
    _ = alloc;
    // create a copy of `grid`

    // modify the copy such that each 1 contains a distance to its nearest 0 (the 0 has to be 4-directionally
    // (up, down, left, rigth) adjacent)

    // return the updated copy
    return grid;
}

const std = @import("std");
const testing = std.testing;

1 Like

I think I read somewhere on Ziggit that anytype should be avoided / is discouraged.

Not really, some people just don’t like it in certain situations, but that is a whole other topic.

Basically they don’t like when it is used instead of providing a type that defines a specific interface, because then the caller doesn’t know what you are allowed to pass without looking at the code in the function and finding out what could be passed without causing a compile error.

But in your example here you actually want to pass different things (arrays of different sizes) and additionally it also is just a local helper function making it easier to write the test case.

1 Like