Reputation: 4012
I'm try to compose a generic solution to provide fixtures for unit testing Rust code. I have come up with a macro, which allows the user to define setup and teardown methods. Here is my solution so far:
struct FooTestFixture {
pub name : String
}
impl FooTestFixture {
fn setup() -> FooTestFixture {
FooTestFixture { name: String::from("Initialised") }
}
}
fn teardown(fixture : &mut FooTestFixture) {
fixture.name = "".to_string();
}
macro_rules! unit_test {
($name:ident $fixt:ident $expr:expr) => (
#[test]
fn $name() {
let mut $fixt : FooTestFixture = FooTestFixture::setup();
$expr;
teardown(&mut $fixt);
}
)
}
unit_test! (heap_foo_fixture_should_be_initialised_using_macro f {
assert_eq!(f.name, "Initialised");
});
This works. The only problem is, that the macro unit_test is not generic, and is bound to the fixture name FooTestFixture. This means that each test module needs to redefine this macro for every test fixture, which is not ideal. What I'd like to be able to do is to also introduce a type variable and use that type in the macro expansion. Delving more into macros I have found that there is a 'ty' item, that represents a type, and I thought I could do this ...
macro_rules! unit_test {
($name:ident $fixt:ident $ftype:ty $expr:expr) => (
#[test]
fn $name() {
let mut $fixt : $ftype = $ftype::setup();
$expr;
teardown(&mut $fixt);
}
)
}
unit_test! (heap_foo_fixture_should_be_initialised_using_macro FooTestFixture f {
assert_eq!(f.name, "Initialised");
});
However, this doesn't work and results in the following error:
src\tests\heap_fixture_with_new.rs:48:40: 48:50 error:
$ftype:ty
is followed by$expr:expr
, which is not allowed forty
fragments src\tests\heap_fixture_with_new.rs:48 ($name:ident $fixt:ident $ftype:ty $expr:expr) => (
As you can see, in the macro definition, I have replaced references to FooTestFixture with $ftype.
Is what I'm trying to achieve possible? It's almost like I'd like the macro to be generic, allowing you to pass in a type, to be used inside the macro definition.
Upvotes: 5
Views: 3544
Reputation: 523154
A ty
cannot be directly followed by an expr
. It must be followed by a specific set of tokens:
=>
,
=
|
;
:
>
[
{
as
where
Similar restriction exists after an expr
, stmt
, path
and pat
. This was introduced in RFC 550 to future-proof potential change in Rust syntax.
To fix it you need to change your macro's pattern, e.g.
macro_rules! unit_test {
($name:ident $fixt:ident<$ftype:ty> $expr:expr) => (
// ^ ^ followed by '>' is OK
unit_test! (test_name fixture_name<FooTestFixture> f {
// ^ ^
Upvotes: 4
Reputation: 4012
Well I realised I didn't need ty
after all. I can just specify the type as an ident
parameter so the following does work:
macro_rules! unit_test {
($name:ident $fixt:ident $ftype:ident $expr:expr) => (
#[test]
fn $name() {
let mut $fixt = $ftype::setup();
$expr;
teardown(&mut $fixt);
}
)
}
unit_test! (foo_fixture_should_be_initialised_using_generic_macro f FooTestFixture {
assert_eq!(f.name, "Initialised");
});
Upvotes: 3