Skip to content

Commit 06cbfaf

Browse files
test(hid): unit-test CancelState and CancelOnDrop
Covers the state machine introduced for cancel propagation: default state, signal/reset semantics, signal idempotency, notify wake-up of an awaiting task, and the CancelOnDrop RAII guard (fires on drop, skips on disarm). The integrated cancel/Drop invariants in hid_recv still need a hardware-in-the-loop test as listed in the PR plan; the virt backend does not exercise the spawn_blocking path.
1 parent 34def98 commit 06cbfaf

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

libwebauthn/src/transport/hid/channel.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,3 +718,72 @@ impl Ctap2AuthTokenStore for HidChannel<'_> {
718718
self.auth_token_data = None;
719719
}
720720
}
721+
722+
#[cfg(test)]
723+
mod tests {
724+
use super::{CancelOnDrop, CancelState};
725+
use std::sync::Arc;
726+
use std::time::Duration;
727+
728+
#[test]
729+
fn cancel_state_default_is_not_cancelled() {
730+
let state = CancelState::default();
731+
assert!(!state.is_cancelled());
732+
}
733+
734+
#[test]
735+
fn cancel_state_signal_sets_flag() {
736+
let state = CancelState::default();
737+
state.signal();
738+
assert!(state.is_cancelled());
739+
}
740+
741+
#[test]
742+
fn cancel_state_reset_clears_flag() {
743+
let state = CancelState::default();
744+
state.signal();
745+
state.reset();
746+
assert!(!state.is_cancelled());
747+
}
748+
749+
#[test]
750+
fn cancel_state_signal_is_idempotent() {
751+
let state = CancelState::default();
752+
state.signal();
753+
state.signal();
754+
assert!(state.is_cancelled());
755+
}
756+
757+
#[tokio::test]
758+
async fn cancel_state_signal_wakes_waiter() {
759+
let state = Arc::new(CancelState::default());
760+
let waiter = state.clone();
761+
let task = tokio::spawn(async move { waiter.notify.notified().await });
762+
// Yield so the waiter registers before we signal.
763+
tokio::task::yield_now().await;
764+
state.signal();
765+
tokio::time::timeout(Duration::from_secs(1), task)
766+
.await
767+
.expect("notify did not wake waiter within 1s")
768+
.expect("waiter task panicked");
769+
}
770+
771+
#[test]
772+
fn cancel_on_drop_signals_when_dropped() {
773+
let state = CancelState::default();
774+
{
775+
let _guard = CancelOnDrop::new(&state);
776+
}
777+
assert!(state.is_cancelled());
778+
}
779+
780+
#[test]
781+
fn cancel_on_drop_disarm_skips_signal() {
782+
let state = CancelState::default();
783+
{
784+
let mut guard = CancelOnDrop::new(&state);
785+
guard.disarm();
786+
}
787+
assert!(!state.is_cancelled());
788+
}
789+
}

0 commit comments

Comments
 (0)