3

I'm dealing with a lot of tests that all have the same structure as follows:

#[cfg(test)]
mod tests {
    #[test]
    fn test_sample() {
         // create file, optionally write something inside
         File::create(<FILE_NAME>).unwrap();

         // test stuff with file

         // delete file
         fs::remove_file(<FILE_NAME>).unwrap();
    }
}

My main concern is the sheer amount of duplicate code across all tests. I've tried using crates for creating a single global temp directory with the INIT.call_once() pattern shown here, but it doesn't work because I can't declare a static or const variable at the module level without also initializing it. Obviously it would work if I created a separate temp directory in each test, but I'd like to create only one and then inject it inside the tests like rstest does with fixtures.

Is it possible to achieve this kind of behaviour with temp directories? If not, I'd like to try the same thing with temp files, i.e. injecting them as fixtures. It should work, but I wanted to ask just in case. Or perhaps do you have other ideas for approaching this issue?

rdxdkr
  • 839
  • 1
  • 12
  • 22
  • what are you trying to actually test? does your code being tested actually need to deal with the files itself, or are you just using that as a form of data entry? if you just care about the data, you may be able to use [`include_str!`](https://doc.rust-lang.org/std/macro.include_str.html) to read a file at compile time and save to a `static`/`const` – Jeremy Meadows Jun 08 '22 at 19:56
  • "but it doesn't work because I can't declare a `static` or `const` variable at the module level without also initializing it" Are you referring to the `Once`? – Chayim Friedman Jun 08 '22 at 20:39
  • @JeremyMeadows yes, my code reads and writes a file in different ways, so I want to test what is actually happening. The current testing approach works, but I don't think it's the prettiest. – rdxdkr Jun 08 '22 at 21:13
  • @ChayimFriedman no, I was trying to declare a module level instance of a temp directory, and then create it with the `Once` pattern in order to use the same one for all tests. – rdxdkr Jun 08 '22 at 21:18
  • 1
    Does this answer your question? [How do I create a global, mutable singleton?](https://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton) – Chayim Friedman Jun 08 '22 at 21:22
  • @ChayimFriedman `lazy_static` and `once_cell` both work, but the temp directory doesn't actually get removed automatically at the end of the tests, even with no failures. Maybe it being `static` goes against the crate's ability to make it self deletable. – rdxdkr Jun 08 '22 at 22:23

1 Answers1

2

I've done something similar but with more files to be created, although it could get kinda messy but it's the best solution in my case.

It's basically using a wrapper function so that the test functions can get the path to the files/dirs created before running the actural tests:

//tests/common.rs
pub fn run<T>(test: T, clean_after: bool)
where
    T: FnOnce(&TestPaths) -> () + panic::UnwindSafe,
{
    let paths = init().unwrap();
    let res = panic::catch_unwind(|| {
        test(&paths);
    });
    if clean_after {
        clean(&paths).unwrap();
    }
    assert!(res.is_ok());
}

and in each test functions:

#[test]
fn tast1() {
    run(
        |paths| {
            // package metadata
            let p = paths.root.join("test1.toml");
            ...
        },
        false,
    );
}

And you'll have to write your own init() function to return the what ever paths created, and clear(&paths) to clears them ofc.