flybrain/services/flysim/crates/fly-session/tests/common/mod.rs
acamilo 52ad46ecb0 refactor(session): build the session slice on the shared fly-session-types crate
CONTRACT-01 landed its crate, so the local stand-in module goes away and the domain
scalars, method payloads, their validation, the canonical digests and the trace format
all come from the contract. `src/types.rs` is a facade over that crate plus the few
things a coordinator needs that are not part of the type contract: a session-side
DomainError, the synthetic composition's schema and event-id derivations, and the
coordinator-local ControllerIntent, PortBinding and AgentOutcome that never cross the
bus.

Consequences worth naming:

- Payloads are read and written through DomainType::from_json / to_json instead of
  serde derives, so a misspelled required field fails where the contract says it
  should. serde, sha2 and ryu-js leave this crate's dependencies with them.
- The step-v1 section 8 trace is the contract's TransitionTrace, with behaviour and
  operational metadata already separated; the dispatch-order comparison now runs over
  the contract's own behaviour encoding.
- Two things the migration found. IN_PROGRESS is raised strictly before any mutation,
  so its certainty is "none", not "unknown"; the local module had it wrong.
  WorldObservation::validate_against checks the views a result carries but does not
  require every declared view to be present, so requiring them is made explicit in the
  coordinator's phase C check, where step-v1 puts it.
- The lost-Advance-result injection now waits for the worker to report the operation
  before abandoning the call, so the case it injects really is a loss after dispatch
  rather than a cancellation before it.

Gates: cargo test -p fly-session (61 tests, both transports), cargo clippy
--all-targets clean, and the runnable example produces the same behaviour trace over
both transports.
2026-09-22 12:30:41 +00:00

96 lines
2.5 KiB
Rust

//! Shared test fixture: the synthetic session on a temporary store, over either transport.
#![allow(dead_code)]
use std::time::Duration;
use fly_session::harness::{HarnessConfig, SessionHarness, Via};
use fly_session::types::*;
pub const WAIT: Duration = Duration::from_secs(20);
/// Generates one test per transport from an `async fn name(via: Via)`.
#[macro_export]
macro_rules! both_transports {
($($name:ident),* $(,)?) => {
mod in_memory {
$(
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn $name() {
super::$name($crate::common::via_memory()).await
}
)*
}
mod unix_socket {
$(
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn $name() {
super::$name($crate::common::via_unix()).await
}
)*
}
};
}
pub fn via_memory() -> Via {
Via::Memory
}
pub fn via_unix() -> Via {
Via::Unix
}
/// A started session plus the temporary directory its store lives in.
pub struct Fixture {
pub dir: tempfile::TempDir,
pub harness: SessionHarness,
}
impl Fixture {
pub async fn shutdown(self) {
let Fixture { dir, harness } = self;
harness.shutdown().await;
drop(dir);
}
}
pub async fn fixture(via: Via, config: HarnessConfig) -> Fixture {
let dir = tempfile::tempdir().expect("a temporary directory");
let harness = SessionHarness::start(via, dir.path(), config)
.await
.expect("the synthetic session starts");
Fixture { dir, harness }
}
/// The default two-agent composition: 60 Hz world, 1 ms model tick.
pub async fn default_fixture(via: Via) -> Fixture {
fixture(via, HarnessConfig::default()).await
}
pub fn fly_a() -> Id {
id("fly-a")
}
pub fn fly_b() -> Id {
id("fly-b")
}
/// The index of an audit entry, or a panic naming what was missing.
pub fn at(audit: &[String], what: &str) -> usize {
audit
.iter()
.position(|entry| entry == what)
.unwrap_or_else(|| panic!("the audit has no {what:?}: {audit:?}"))
}
pub fn count(audit: &[String], what: &str) -> usize {
audit.iter().filter(|entry| *entry == what).count()
}
/// Fails the test rather than hanging, so a missed reply is a failure and not a stuck job.
pub async fn within<T>(what: &str, f: impl std::future::Future<Output = T>) -> T {
match tokio::time::timeout(WAIT, f).await {
Ok(v) => v,
Err(_) => panic!("{what}: timed out"),
}
}