2

A subset of my tests need a resource that is only allowed to be created once. This resource also has to be dropped at some point (not before the last test needing it finished).

Currently I use a static OnceCell:

struct MyResource {}

impl Drop for MyResource {
    fn drop(&mut self) {
        println!("Dropped MyResource");
    }
}

impl MyResource {
    fn new() -> MyResource {
        println!("Created MyResource");
        MyResource {}
    }
}

fn main() {
    let _my_resource = MyResource::new();
    println!("Hello, world!");
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokio::sync::OnceCell;

    static GET_RESOURCE: OnceCell<MyResource> = OnceCell::const_new();

    async fn init_my_resource() {
        GET_RESOURCE.get_or_init(|| async {
            MyResource::new()
        }).await;
    }

    #[tokio::test]
    async fn some_test() {
        init_my_resource().await;
        assert_eq!("some assert", "some assert");
    }
}

This works fine except that it never drops the resource.

Is there a better way to do this that also drops the resource?

The answer to How to run setup code before any tests run in Rust? does not answer this question as it only answers how to initialize something once, but not how to drop something after the tests ran.

Herohtar
  • 5,347
  • 4
  • 31
  • 41
apbr
  • 21
  • 4
  • I can't seem to find an associated function of `OnceCell` called `const_new`. There is `new`, which is also `const`, so that's maybe what you meant? Also, I can't get to compile your code because it complains about `OnceCell<_>` not being `Sync`. – jthulhu Apr 21 '22 at 10:13
  • @BlackBeans Sorry, I forgot to add a use statement. I'm using tokio's variant of OnceCell. – apbr Apr 21 '22 at 10:41
  • Incidentally, the question you link to also provides the solution to your question. The suggested crate, `ctor`, also provides a similar functionality but for running finish code after all your tests are run. – jthulhu Apr 21 '22 at 11:15
  • Thanks that solves it. :) – apbr Apr 21 '22 at 11:18
  • 1
    If that solves it, I'll just add it as an answer for future readers :) – jthulhu Apr 21 '22 at 11:23
  • The one time I used a crate built on top of Tokio, I decided to stay clear of *Tokio* as it is simply too invasive and monopolizes otherwise unrelated code with its "voodoo". Not even `main()` remains unaffected, nor as it shows here, unit tests. Its like a virus, spreading the infection and it is hard to keep it in a dedicated part of a larger project. As such, I consider it terrible design. – BitTickler Apr 21 '22 at 11:28

2 Answers2

1

The crate ctor provides the dtor macro, which allows you to manually drop the resources once all your tests are done.

jthulhu
  • 7,223
  • 2
  • 16
  • 33
  • As noted in my answer I think the solution with static_init is better as it doesn't need unsafe. (At least when directly using it without extra precautions) But thank you anyways for this proposal. – apbr Apr 21 '22 at 11:46
0

The most straight-forward solution I found is offered by the crate static_init:

struct MyResource {}

impl Drop for MyResource {
    fn drop(&mut self) {
        println!("Dropped my rescource");
    }
}

impl MyResource {
    fn new() -> MyResource {
        println!("Created MyResource");
        MyResource {}
    }
}

fn main() {
    let _my_resource = MyResource::new();
    println!("Hello, world!");
}

#[cfg(test)]
mod tests {
    use super::*;
    use static_init::dynamic;

    #[dynamic(drop)]
    static mut RES: MyResource = MyResource::new();

    #[tokio::test]
    async fn some_test() {
        println!("Running test");
        assert_eq!("some assert", "some assert");
    }
}

Another proposed solution was using ctor and dtor of the crate ctor. Though using this with a plain static mut RES: MyResource results in unsafe code as we need to modify a mutable static.

apbr
  • 21
  • 4