testing macros with side-effects using `trybuild`

Note: this is probably only of interest if you write (or want to write) Rust procedural macros.

I was recently writing a test for a procedural macro which looked like this:

#[test]
fn test_classes_to_file() {
    let t = trybuild::TestCases::new();
    t.pass("tests/write_file/pass.rs");
    let file = read_to_string(&format!(
        "{}/styles.css",
        std::env::var("CARGO_MANIFEST_DIR").unwrap()
    ))
    .unwrap();
    assert!(file.contains("{font-size:24px;font-family:sans-serif;}"));
}

It compiles a file which invokes a procedural macro. The procedural macro in question will write some data (some auto-generated CSS) into a file, and I wanted to make sure that it writes the correct CSS into the file. Unfortunately, in its current state the test fails (but not because the macro is performing incorrrectly!)

The way in which trybuild runs tests means that tests are added and then run when TestCases is dropped. I might question this API design were it not for the fact that most macros are just simple(ish) mappings of code from "A" to "B." That is a good way to write macros!!!

Anyway, in this case, the trick is to ensure that TestCases is dropped before checking that the file has been written to.

#[test]
fn test_classes_to_file() {
    let t = trybuild::TestCases::new();
    t.pass("tests/write_file/pass.rs");
        std::mem::drop(t);
    let file = read_to_string(&format!(
        "{}/styles.css",
        std::env::var("CARGO_MANIFEST_DIR").unwrap()
    ))
    .unwrap();
    assert!(file.contains("{font-size:24px;font-family:sans-serif;}"));
}

You'll only receive email when they publish something new.

More from Teymour Aldridge