# Deterministic runtime testing Use the deterministic runtime for async protocol tests. It makes scheduling, time, failure injection, and state recovery reproducible. Use `commonware_utils::test_rng()` for test data; for independent streams use `TestRng::new(seed)`. ## Basic async test ```rust #[test] fn test_async_behavior() { let runner = deterministic::Runner::seeded(42); runner.start(|context| async move { let handle = context.child("worker").spawn(|context| async move { context.sleep(Duration::from_secs(1)).await; }); context.sleep(Duration::from_millis(100)).await; select! { result = handle => { /* handle result */ }, _ = context.sleep(Duration::from_secs(5)) => panic!("timeout"), } }); } ``` Label actors with `context.child("role")`. Use a seeded runner for repeatability and a timeout when testing a bounded operation: ```rust let cfg = deterministic::Config::new() .with_seed(seed) .with_timeout(Some(Duration::from_secs(30))); let runner = deterministic::Runner::new(cfg); ``` ## Recovery Use `start_and_recover` to exercise unclean shutdown and restart paths: ```rust let mut checkpoint = None; loop { let runner = if let Some(checkpoint) = checkpoint.take() { deterministic::Runner::from(checkpoint) } else { deterministic::Runner::timed(Duration::from_secs(30)) }; let (complete, next_checkpoint) = runner.start_and_recover(f); if complete { break; } checkpoint = Some(next_checkpoint); } ``` ## Verification checklist - Check determinism with `context.auditor().state()` when relevant. - Monitor progress with supervisors or metrics rather than time alone. - For shutdown, assert the task-prefix count becomes non-zero before shutdown and zero afterward. - Run a scenario twice with the same seed when its state is meant to be deterministic. - Include recovery cases when the changed component has those boundaries.