Optional hygiene in Rust Macro system

I have a trait that implements about 38 callbacks, like so:

trait Foo {
    fn handler_a(&self, ...);
    fn handler_b(&self, ...);
    // ...
}

I have a hard requirement to write and maintain unit tests that invokes these handlers individually in each test. E.g.,

#[test]
fn exercise_a() {
    // ... code that causes my_handler.handler_a() to be called.
}

#[test]
fn exercise_b() {
    // ... code that causes my_handler.handler_b() to be called.
}

99.999% of all this code is the same. The only thing that differs are the names.

How can I write a macro in Rust to automate this process? Something that lets me do this:

test_case!(a);
test_case!(b);
// ...

I desperately do not want to have to hand-write 38 nearly identical copies of a test function, even if they are only a handful of lines each.

Shelling out to a code-generating script is not feasible.

Thanks.

The paste crate lets you concatenate identifiers, which looks like most of what you need based on your sample code.

3 Likes

Also, note that a single macro can expand to multiple fn items; you should be able to shorten your invocation to something like this:

test_cases!{ a, b };
1 Like

I've had to do something very similar in other projects, so you might be able to take inspiration from real world examples.

Here's one place I use a macro to generate a large number of tests for parsing expressions in the OpenSCAD programming language:

Similar to you, my tests are almost all identical except for one part (the input string and parsing function in my case, but yours would be the callback method), so the macro definition is fairly straightforward plus/minus some extra bits I add to help debug failing tests.

One of the things I like about this macro is it preserves attributes. That means you can add #[ignore] above a test and Rust's test runner will skip it.

If this constraint is due to build environments and not being allowed to spawn other processes, it's useful to know that code generation doesn't require shelling out to a script. Using @matklad's self-modifying code trick and some inspiration from rust-analyzer, you can write a Rust test that will generate the code for you and automatically keep it up to date.

Here is how I use the pattern elsewhere in that codebase:

2 Likes

Wow, this is a lot to go through. :slight_smile: Thanks everyone for the tips!

1 Like