-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Possible leakage using rstest or tokio::test #14
Comments
I have not tested it with rstest, but I can see how it could be problematic. With https://docs.rs/tokio/1.37.0/tokio/time/fn.advance.html and notably: |
As for |
I've elated to just putting the reset in every test that uses the mock. I was really hoping i could just make it a fixture, but it def works better when i put it in the test body. |
I don't actually see |
I see. you are correct. Originally this crate didn't have a mockable SystemTime but a user requested that it was added recently. Without knowing more context, the only thing I can suggest is to either make tests that use I don't have a good solution for this. the rust std (which I try to emulate) implementations use globals from an external source. My One idea would be to move away from global state, e.g. threading in a A while back, cargo test (the frontend for I don't know what I could change on my end to help with your problem, but I can offer suggestions/help. I understand how tokio does time pretty well and I'm rather familiar with One could try |
If you can give a few simple examples of where SystemTime gets into invalid state, I can perhaps point out where you should reset the clock. One idea would be to have a simple barrier (or using a semaphore) w/ a drop guard to either block the test thread until the unit-of-work is done, or ensure things have a re-initialized clock (back to some epoch) before they run and that they reset when they are finished. And another would be a bigger change that'll require passing around a time source/context to things that use time. Like how one would unit test a RNG where you can seed it with a stack-local value (This is that |
After further investigation, it seems that I just needed to adjust "where" I reset the clock. I had originally created an rstest |
Just FYI. I'm still having a great deal of issues with concurrent tests and I've decided I have to lock any test that uses MockClock since its not thread safe, at least not with #[rstest] and #[tokio::test]. I really wanted it to work with #[rstest] out of the box because #[fixtures] that required time could just reset the clock when used. But I'm going to have to build extra infrastructure around it to lock it for each test. |
I could perhaps support an api like that. But acquiring a global mutex in multiple tests could lead to interesting side effects. (ordering) I would suggest using a DI-style to things that use a clock so you can pass in frame-local clocks to the things that use it. I know its not a drop-in but it'd give you the flexibility of when things are constructed and used. a theory of how the design could look: trait Clock {
fn now(&self) -> Instant;
}
struct RealClock {} // TODO
impl Clock for RealClock {
fn now(&self) -> Instant { Instant::now() }
}
struct MockClock {} // TODO
impl Clock for MockClock {
fn now(&self) -> Instant {
// swap out Instant with a mocked one, like this crate does
}
}
struct MyThing;
impl MyThing {
fn something(&self, clock: &impl Clock) {
let now = clock.now();
}
}
#[test]
fn foo() {
let clock = MockClock::new();
let thing = MyThing;
thing.something(&clock);
}
fn real() {
let clock = RealClock::new();
let thing = MyThing;
thing.something(&clock);
} A new crate could probably be designed to support this. Instead of global state, it'd use monomorphization to provide stack-local timing. On a side note, this is one of the reasons rust makes global state (and singletons) difficult because it always leads to hard to reason about states in multi-threaded code. |
I'm basing this pattern on crates like and using this for a deterministic rng: (rand uses a trait for trait implementation. things impl RngCore and there is a blanket impl for the main Rng trait: But notably, the time implementation generally would be rather simple. In tests it'd be a monotonic counter, like this crate does. So the core trait (or blanket trait) wouldn't really be needed. |
As reported by #15 people have a use for thread-local sources. You may want to try it to see if it solves your problem: (it moves the types into a @dr-kernel to describe when one would use a I've basically duplicated all of the types, where one set will use a mutex and the other a thread local cell. Even though using a thread local source will work, Tokio also has a thread pool when using the multi-thread runtime. So ideally you'd configure the test to use the single threaded runtime so the source isn't created for the moved context when a task moves threads. By default Tokio uses a single thread executor https://docs.rs/tokio/latest/tokio/attr.test.html#current-thread-runtime but one can change the flavor. To help with your problem, I would suggest using the new thread local System time, resetting the clock before or after each test and ensuring the Tokio executor is single threaded (current_thread) I'm sorry this has been so much trouble for you, but this swapping out types and using a singleton to mimic the OS source will always be hacky :( If the above doesn't resolve the issue and If you could provide a simple demo test application that has the sometimes faulty logic I can maybe figure out a more tailored to you solution. (Using retest, Tokio, SystemTime) |
And to expand on some patterns, I could probably provide a guard type that resets the clock after the test runs. Something like #[test]
fn foo() {
let _guard = MockClock::reset_guard();
// Stuff
} I haven't thought about the implications of resetting both the Instant source and SystemTime source. Maybe 3 functions: reset_instant, reset_system_time and reset (both) (Of course names are provisional, better ones would be chosen once the behavior is decided upon) The guard would also allow scoped states, naturally |
I just checked the libtest source in rustc and this is incorrect. They aren't using a thread pool any more. So, under the current behavior a thread local won't be reused. But one should not rely on internal details. The prior advice should still be considered |
Greetings,
Love this crate, thanks for this work. I've been using it and noticed odd errors with my tests failing then passing on re-runs of the tests. I'm wondering if there's some leakage of the mutex clock between tests due to these wrappers? Have you tested with either of these testing architectures?
Appreciate your thoughts!
The text was updated successfully, but these errors were encountered: