#![cfg(feature = "wgpu")]
use std::sync::Arc;
use std::time::Duration;
use gpu_handle_types::{BackendKind, DeferredWgpuSlot, Error, SyncPoint, make_deferred_wgpu_sync_point};
#[allow(clippy::needless_update)]
fn try_open_wgpu_device() -> Option<(wgpu::Device, wgpu::Queue)> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..wgpu::InstanceDescriptor::new_without_display_handle()
});
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::None,
force_fallback_adapter: false,
compatible_surface: None,
apply_limit_buckets: false,
}))
.ok()?;
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("sync_deferred_wgpu.test.device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::default(),
trace: wgpu::Trace::Off,
experimental_features: wgpu::ExperimentalFeatures::disabled(),
..Default::default()
}))
.ok()?;
Some((device, queue))
}
fn submit_empty(device: &wgpu::Device, queue: &wgpu::Queue) -> wgpu::SubmissionIndex {
let encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("sync_deferred_wgpu.test.empty") });
queue.submit([encoder.finish()])
}
#[test]
fn slot_get_returns_none_before_set() {
let slot = DeferredWgpuSlot::new();
assert!(slot.get().is_none(), "fresh slot must be uncommitted");
}
#[test]
fn slot_double_set_rejects_second_call() {
let Some((device, queue)) = try_open_wgpu_device() else {
eprintln!("no wgpu adapter, skipping");
return;
};
let slot = DeferredWgpuSlot::new();
let first = submit_empty(&device, &queue);
let second = submit_empty(&device, &queue);
slot.set(first).expect("first set must succeed");
assert!(slot.get().is_some(), "committed slot must surface Some");
let err = slot.set(second).expect_err("second set must fail");
match err {
Error::InvalidArgument(_) => {}
other => panic!("expected InvalidArgument, got {other:?}"),
}
assert!(slot.get().is_some(), "rejected second set must not erase the first");
}
#[test]
fn sync_point_backend_is_wgpu() {
let Some((device, _queue)) = try_open_wgpu_device() else {
eprintln!("no wgpu adapter, skipping");
return;
};
let slot = Arc::new(DeferredWgpuSlot::new());
let sp = make_deferred_wgpu_sync_point(slot, device);
assert_eq!(sp.backend(), BackendKind::Wgpu);
}
#[test]
fn is_signaled_short_circuits_before_commit() {
let Some((device, _queue)) = try_open_wgpu_device() else {
eprintln!("no wgpu adapter, skipping");
return;
};
let slot = Arc::new(DeferredWgpuSlot::new());
let sp = make_deferred_wgpu_sync_point(slot, device);
let waiter = sp.waiter().expect("DeferredWgpu carries a waiter");
let _signaled = waiter.is_signaled().expect("is_signaled must not error");
}
#[test]
fn wait_after_commit_resolves_quickly() {
let Some((device, queue)) = try_open_wgpu_device() else {
eprintln!("no wgpu adapter, skipping");
return;
};
let slot = Arc::new(DeferredWgpuSlot::new());
let sp = make_deferred_wgpu_sync_point(slot.clone(), device.clone());
let idx = submit_empty(&device, &queue);
slot.set(idx).expect("commit");
sp.wait_with_timeout(Duration::from_secs(5)).expect("post-commit wait must resolve within 5 s");
}
#[test]
fn sync_point_clone_shares_committed_slot() {
let Some((device, queue)) = try_open_wgpu_device() else {
eprintln!("no wgpu adapter, skipping");
return;
};
let slot = Arc::new(DeferredWgpuSlot::new());
let sp_a = make_deferred_wgpu_sync_point(slot.clone(), device.clone());
let sp_b = sp_a.clone();
let idx = submit_empty(&device, &queue);
slot.set(idx).expect("commit through the original slot Arc");
sp_a.wait_with_timeout(Duration::from_secs(5)).expect("clone A sees the commit");
sp_b.wait_with_timeout(Duration::from_secs(5)).expect("clone B sees the commit through the shared Arc");
}
#[test]
fn sync_point_is_clone() {
fn _assert_clone<T: Clone>() {}
_assert_clone::<SyncPoint>();
}