Skip to main content

oxigdal_gpu/
device_lost_test.rs

1//! CPU-only unit tests for GPU device-lost recovery machinery.
2//!
3//! These tests exercise the `Arc<AtomicBool>` flag mechanism and the
4//! `GpuError::DeviceLost` variant without requiring a real GPU device.
5//! They are designed to always pass on any host (including CI without a GPU).
6
7#[cfg(test)]
8mod tests {
9    use crate::error::GpuError;
10    use std::sync::{
11        Arc,
12        atomic::{AtomicBool, Ordering},
13    };
14
15    /// Verifies that a freshly-created `AtomicBool` flag starts as `false`.
16    ///
17    /// This mirrors the initial state of `GpuContext::device_lost` before any
18    /// device-lost callback fires.
19    #[test]
20    fn test_device_lost_flag_default_is_false() {
21        let flag = Arc::new(AtomicBool::new(false));
22        assert!(!flag.load(Ordering::SeqCst));
23    }
24
25    /// Verifies that the flag can be atomically set to `true`.
26    ///
27    /// This replicates what the wgpu device-lost callback does internally when
28    /// the GPU hardware is reset or removed.
29    #[test]
30    fn test_device_lost_flag_set_to_true() {
31        let flag = Arc::new(AtomicBool::new(false));
32        flag.store(true, Ordering::SeqCst);
33        assert!(flag.load(Ordering::SeqCst));
34    }
35
36    /// Verifies that `Arc::clone` shares ownership of the same underlying bool.
37    ///
38    /// The `GpuContext` registers a clone of `device_lost` with the wgpu
39    /// callback closure.  This test confirms that a write through the clone is
40    /// visible through the original, which is the invariant that makes the
41    /// callback mechanism correct.
42    #[test]
43    fn test_device_lost_flag_clone_shares_state() {
44        let flag = Arc::new(AtomicBool::new(false));
45        let cloned = Arc::clone(&flag);
46        // Simulate callback writing through its cloned handle.
47        cloned.store(true, Ordering::SeqCst);
48        // The original — held by GpuContext — must observe the update.
49        assert!(flag.load(Ordering::SeqCst));
50    }
51
52    /// Verifies that `GpuError::device_lost` constructs the `DeviceLost` variant.
53    ///
54    /// `check_device_lost` returns `Err(GpuError::device_lost(...))` when the
55    /// flag is set.  This test ensures the constructor produces the expected
56    /// enum variant independently of any GPU context.
57    #[test]
58    fn test_check_device_lost_returns_err_when_flag_set() {
59        let err = GpuError::device_lost("hardware reset");
60        assert!(matches!(err, GpuError::DeviceLost { .. }));
61    }
62
63    /// Verifies that the `Display` impl for `GpuError::DeviceLost` includes the
64    /// reason string in its output.
65    ///
66    /// The formatted error message is what end-users and log subscribers see.
67    /// Ensuring the reason is not silently dropped is a basic sanity check.
68    #[test]
69    fn test_device_lost_error_formats_as_string() {
70        let err = GpuError::device_lost("TDR timeout");
71        let s = format!("{err}");
72        // The formatted message must be non-empty and contain the reason.
73        assert!(!s.is_empty());
74        assert!(s.contains("TDR timeout"), "expected reason in '{s}'");
75    }
76}