Is there any good way to create helper functions in a test block?

Hi, guys. I need to create some function in a test block. And I don’t want to create them out of the test block(because they are functions only related to this test block).
When I try to create function in the test block, but soon I find it’s not supported.
So I change to encapsulate into a struct.
But then I got the problem below. What’s the reason.
And is there any good way to to achieve this goal?

test "test_cond" {
    const ProducerAndConsumer = struct {
        idx: usize = 0,
        queue: [Len]T = undefined,
        const Len: usize = 1024;
        const T = i32;
        // The line below’s throwing an error: use of undeclared identifier 'ProducerAndConsumer'
        fn can_produce(self: ProducerAndConsumer) bool {
            return self.idx < self.queue.len;
        }
        // fn produce() {}
        // ...
    };
    const v: ProducerAndCunsumer = .{};
    std.testing.expect(v.can_produce());
}

You can use self: @This().
And you can add an alias in the struct:
const ProducerAndCunsumer = @This().
or
const Self = @This().

(btw, the spelling is Consumer with an o)

2 Likes

Thanks, that is exactly what I wanted!

1 Like

@csmyx please read this:


Personally I think one of the better options is to introduce a local namespace like this:

const std = @import("std");

test "example" {
    const local = struct {
        const Bla = struct {
            foo: u32,

            fn doSomething(self: Bla) u32 {
                return self.foo + 5;
            }
        };
    };

    const b: local.Bla = .{ .foo = 5 };
    try std.testing.expectEqual(10, b.doSomething());
}

That way you don’t have to use @This() and only use that builtin where it is actually required, for example when writing generics.

2 Likes

Also note that your question applies equally to creating functions inside of functions. test "xxx" {} is just a special function.

@Sze 's answer is also worth considering, I have been thinking about doing that also.

2 Likes

Yeah, I also thought of this method. But this introduces yet another layer of namespace, which I just don’t like.

Another option is to define it outside the test and let the compiler take care of it via its lazy resolution:

const std = @import("std");

const Bla = struct {
    foo: u32,

    fn doSomething(self: Bla) u32 {
        return self.foo + 5;
    }
};

test "example" {
    const b: Bla = .{ .foo = 5 };
    try std.testing.expectEqual(10, b.doSomething());
}

If only your test uses Bla it should disappear for non test builds.
I guess the downside could be that editor tooling still shows the type when it is just used for the test and shouldn’t clutter the bigger namespace.

Yeah, that’s exactly why I don’t want to define it outside the test block. All 'cause no native closures support, lol!

Got it, thanks!

1 Like