Fix bevy_time tests occasionally failing on optimised Windows builds (#17349)

# Objective

Fix the `bevy_time` unit tests occasionally failing on optimised Windows
builds.

# Background

I noticed that the `bevy_time` unit tests would fail ~50% of the time
after enabling `opt-level=1` in config.toml, or adding `--release` to
cargo test.

```
> cargo test -p bevy_time --release

thread 'real::test::test_update' panicked at crates\bevy_time\src\real.rs:164:9:
assertion `left != right` failed
  left: Some(Instant { t: 9458.0756664s })
 right: Some(Instant { t: 9458.0756664s })
 ```

Disabling optimisations would fix the issue, as would switching from Windows to Linux.

The failing path is roughly:

```rust
let mut time = Time::<Real>::new(Instant::now());
time.update();
time.update();
assert_ne!(time.last_update(), time.first_update());
```

Which kinda boils down to:

```rust
let left = Instant::now();
let right = Instant::now();
assert_ne!(left, right);
```

So the failure seems legit, since there's no guarantee that `Instant::now()` increases between calls.

I suspect it only triggers with a combination of Windows + fast CPU + optimisations (Windows has a lower resolution clock than Linux/MacOS). That would explain why it doesn't fail on the Bevy Github CI (optimisations disabled, and I'm guessing the runner CPUs are clocked lower).

# Solution

Make sure `Instant::now()` has increased before calling `time.update()`.

I also considered:

1. Change the unit tests to accept `Instant:now()` not increasing.
    - In retrospect this is maybe the better change?
    - There's other unit tests that cover time increasing.
    - Could also add a deterministic test for zero delta updates.
    - I can switch the PR to this if desired.
2. Avoid any paths that hit `Instant::now()` in unit tests.
    - Arguably unit tests should always be deterministic.
    - But that would mean a bunch of paths aren't tested.

## Testing

`cargo test -p bevy_time --release`

## System Info

`os: "Windows 10 Pro", kernel: "19045", cpu: "AMD Ryzen 9 7900 12-Core Processor", core_count: "12", memory: "63.2 GiB"`

Also tested on same computer with Linux pop-os 6.9.3.

Co-authored-by: François Mockers <mockersf@gmail.com>
This commit is contained in:
Greeble 2025-01-14 00:30:07 +00:00 committed by GitHub
parent 141b7673ab
commit 83b2265673
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -139,6 +139,18 @@ impl Time<Real> {
mod test {
use super::*;
// Waits until Instant::now() has increased.
//
// ```
// let previous = Instant::now();
// wait();
// assert!(Instant::now() > previous);
// ```
fn wait() {
let start = Instant::now();
while Instant::now() <= start {}
}
#[test]
fn test_update() {
let startup = Instant::now();
@ -150,6 +162,7 @@ mod test {
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(time.elapsed(), Duration::ZERO);
wait();
time.update();
assert_ne!(time.first_update(), None);
@ -157,6 +170,7 @@ mod test {
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(time.elapsed(), Duration::ZERO);
wait();
time.update();
assert_ne!(time.first_update(), None);
@ -165,6 +179,7 @@ mod test {
assert_ne!(time.delta(), Duration::ZERO);
assert_eq!(time.elapsed(), time.delta());
wait();
let prev_elapsed = time.elapsed();
time.update();
@ -177,6 +192,7 @@ mod test {
let startup = Instant::now();
let mut time = Time::<Real>::new(startup);
wait();
let first_update = Instant::now();
time.update_with_instant(first_update);
@ -186,6 +202,7 @@ mod test {
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(time.elapsed(), Duration::ZERO);
wait();
let second_update = Instant::now();
time.update_with_instant(second_update);
@ -194,6 +211,7 @@ mod test {
assert_eq!(time.delta(), second_update - first_update);
assert_eq!(time.elapsed(), second_update - first_update);
wait();
let third_update = Instant::now();
time.update_with_instant(third_update);