Skip to main content

asupersync_conformance/
lib.rs

1//! Asupersync Conformance Test Suite
2//!
3// Allow type complexity for trait method return types - these are intentionally
4// verbose to be explicit about the async behavior and lifetimes
5#![allow(clippy::type_complexity)]
6//!
7//! This crate provides a conformance test suite for async runtime implementations.
8//! Tests are designed to verify that runtimes correctly implement the expected
9//! semantics for spawning, channels, I/O, synchronization, and cancellation.
10//!
11//! # Architecture
12//!
13//! The test suite is runtime-agnostic. Each runtime must implement the
14//! `RuntimeInterface` trait to provide the necessary primitives. Tests are
15//! written against this interface, allowing the same tests to validate
16//! different runtime implementations.
17//!
18//! # Test Categories
19//!
20//! - `Spawn`: Task spawning and join handles
21//! - `Channels`: MPSC, oneshot, broadcast, and watch channels
22//! - `IO`: File operations, TCP, and UDP networking
23//! - `Sync`: Mutex, RwLock, Semaphore, Barrier, OnceCell
24//! - `Time`: Sleep, timeout, interval
25//! - `Cancel`: Cancellation token and cooperative cancellation
26
27#![forbid(unsafe_code)]
28
29use serde::{Deserialize, Serialize};
30use std::fmt;
31use std::future::Future;
32use std::io::{self, SeekFrom};
33use std::net::SocketAddr;
34use std::path::Path;
35use std::pin::Pin;
36use std::time::Duration;
37
38pub mod bench;
39pub mod lean_coverage_matrix;
40pub mod lean_frontier;
41pub mod logging;
42pub mod report;
43pub mod runner;
44pub mod tests;
45pub mod traceability;
46
47pub use bench::{
48    BenchAllocSnapshot, BenchAllocStats, BenchCategory, BenchComparisonResult,
49    BenchComparisonSummary, BenchConfig, BenchOutput, BenchRunResult, BenchRunSummary, BenchRunner,
50    BenchThresholds, Benchmark, Comparison, ComparisonConfidence, RegressionCheck,
51    RegressionConfig, RegressionMetric, Stats, StatsError, default_benchmarks,
52    run_benchmark_comparison,
53};
54pub use lean_coverage_matrix::{
55    BlockerCode, CoverageBlocker, CoverageEvidence, CoverageRow, CoverageRowType, CoverageStatus,
56    LEAN_COVERAGE_SCHEMA_VERSION, LeanCoverageMatrix,
57};
58pub use lean_frontier::{
59    LEAN_FRONTIER_SCHEMA_VERSION, LeanDiagnosticSeverity, LeanFrontierBucket,
60    LeanFrontierDiagnostic, LeanFrontierReport, extract_frontier_report,
61};
62pub use logging::{
63    ConformanceTestLogger, LogCollector, LogConfig, LogEntry, LogLevel, TestEvent, TestEventKind,
64};
65pub use report::{render_console_summary, write_json_report};
66pub use runner::{
67    ComparisonResult, ComparisonStatus, ComparisonSummary, RunConfig, RunSummary, SingleRunResult,
68    SuiteResult, SuiteTestResult, TestRunner, compare_results, run_comparison,
69    run_conformance_suite,
70};
71pub use traceability::{
72    CiReport, CoverageStats, ScanWarning, SpecRequirement, TraceabilityEntry, TraceabilityMatrix,
73    TraceabilityMatrixBuilder, TraceabilityScan, TraceabilityScanError, requirements_from_entries,
74    scan_conformance_attributes,
75};
76
77// ============================================================================
78// Test Result Types
79// ============================================================================
80
81/// Result of a conformance test execution.
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct TestResult {
84    /// Whether the test passed.
85    pub passed: bool,
86    /// Optional failure message.
87    pub message: Option<String>,
88    /// Checkpoints recorded during test execution.
89    pub checkpoints: Vec<Checkpoint>,
90    /// Duration of test execution.
91    pub duration_ms: Option<u64>,
92}
93
94impl TestResult {
95    /// Create a passing test result.
96    pub fn passed() -> Self {
97        Self {
98            passed: true,
99            message: None,
100            checkpoints: Vec::new(),
101            duration_ms: None,
102        }
103    }
104
105    /// Create a failing test result with a message.
106    pub fn failed(message: impl Into<String>) -> Self {
107        Self {
108            passed: false,
109            message: Some(message.into()),
110            checkpoints: Vec::new(),
111            duration_ms: None,
112        }
113    }
114
115    /// Add a checkpoint to the result.
116    pub fn with_checkpoint(mut self, checkpoint: Checkpoint) -> Self {
117        self.checkpoints.push(checkpoint);
118        self
119    }
120
121    /// Set the duration.
122    pub fn with_duration(mut self, duration_ms: u64) -> Self {
123        self.duration_ms = Some(duration_ms);
124        self
125    }
126}
127
128/// A checkpoint recorded during test execution.
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130pub struct Checkpoint {
131    /// Name of the checkpoint.
132    pub name: String,
133    /// Data associated with the checkpoint.
134    pub data: serde_json::Value,
135}
136
137impl Checkpoint {
138    /// Create a new checkpoint.
139    pub fn new(name: impl Into<String>, data: serde_json::Value) -> Self {
140        Self {
141            name: name.into(),
142            data,
143        }
144    }
145}
146
147/// Helper function to record a checkpoint.
148pub fn checkpoint(name: &str, data: serde_json::Value) {
149    let _ = Checkpoint::new(name, data.clone());
150    crate::logging::record_checkpoint(name, data);
151}
152
153// ============================================================================
154// Test Categories
155// ============================================================================
156
157/// Categories of conformance tests.
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
159pub enum TestCategory {
160    /// Task spawning and join handles.
161    Spawn,
162    /// Channel primitives (MPSC, oneshot, broadcast, watch).
163    Channels,
164    /// I/O operations (file, TCP, UDP).
165    IO,
166    /// Synchronization primitives (Mutex, RwLock, etc.).
167    Sync,
168    /// Time-related operations (sleep, timeout).
169    Time,
170    /// Cancellation mechanisms.
171    Cancel,
172}
173
174impl fmt::Display for TestCategory {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        match self {
177            TestCategory::Spawn => write!(f, "spawn"),
178            TestCategory::Channels => write!(f, "channels"),
179            TestCategory::IO => write!(f, "io"),
180            TestCategory::Sync => write!(f, "sync"),
181            TestCategory::Time => write!(f, "time"),
182            TestCategory::Cancel => write!(f, "cancel"),
183        }
184    }
185}
186
187// ============================================================================
188// Test Metadata
189// ============================================================================
190
191/// Metadata for a conformance test.
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct TestMeta {
194    /// Unique identifier for the test.
195    pub id: String,
196    /// Human-readable name.
197    pub name: String,
198    /// Description of what the test validates.
199    pub description: String,
200    /// Category of the test.
201    pub category: TestCategory,
202    /// Tags for filtering.
203    pub tags: Vec<String>,
204    /// Expected behavior description.
205    pub expected: String,
206}
207
208// ============================================================================
209// Runtime Interface
210// ============================================================================
211
212/// Trait that async runtimes must implement to run conformance tests.
213///
214/// This trait provides the common primitives that tests require. Each method
215/// returns a concrete type that the runtime provides.
216pub trait RuntimeInterface: Sized {
217    // ---- Core Types ----
218    /// Join handle for spawned tasks.
219    type JoinHandle<T: Send + 'static>: Future<Output = T> + Send + 'static;
220
221    /// MPSC sender.
222    type MpscSender<T: Send + 'static>: MpscSender<T> + 'static;
223
224    /// MPSC receiver.
225    type MpscReceiver<T: Send + 'static>: MpscReceiver<T> + 'static;
226
227    /// Oneshot sender.
228    type OneshotSender<T: Send + 'static>: OneshotSender<T> + 'static;
229
230    /// Oneshot receiver.
231    type OneshotReceiver<T: Send + 'static>: Future<Output = Result<T, OneshotRecvError>>
232        + Send
233        + 'static;
234
235    /// Broadcast sender.
236    type BroadcastSender<T: Send + Clone + 'static>: BroadcastSender<T> + 'static;
237
238    /// Broadcast receiver.
239    type BroadcastReceiver<T: Send + Clone + 'static>: BroadcastReceiver<T> + 'static;
240
241    /// Watch sender.
242    type WatchSender<T: Send + Sync + 'static>: WatchSender<T> + 'static;
243
244    /// Watch receiver.
245    type WatchReceiver<T: Send + Sync + Clone + 'static>: WatchReceiver<T> + 'static;
246
247    /// Async file handle.
248    type File: AsyncFile + 'static;
249
250    /// TCP listener.
251    type TcpListener: TcpListener<Stream = Self::TcpStream> + 'static;
252
253    /// TCP stream.
254    type TcpStream: TcpStream + 'static;
255
256    /// UDP socket.
257    type UdpSocket: UdpSocket + 'static;
258
259    // ---- Spawn ----
260    /// Spawn an async task.
261    fn spawn<F>(&self, future: F) -> Self::JoinHandle<F::Output>
262    where
263        F: Future + Send + 'static,
264        F::Output: Send + 'static;
265
266    // ---- Block On ----
267    /// Block on a future until it completes.
268    fn block_on<F: Future>(&self, future: F) -> F::Output;
269
270    /// Snapshot allocation counters for benchmarking (if supported).
271    fn bench_alloc_snapshot(&self) -> Option<crate::bench::runner::BenchAllocSnapshot> {
272        None
273    }
274
275    // ---- Time ----
276    /// Sleep for a duration.
277    fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
278
279    /// Run a future with a timeout.
280    fn timeout<'a, F: Future + Send + 'a>(
281        &'a self,
282        duration: Duration,
283        future: F,
284    ) -> Pin<Box<dyn Future<Output = Result<F::Output, TimeoutError>> + Send + 'a>>
285    where
286        F::Output: Send;
287
288    // ---- Channels ----
289    /// Create an MPSC channel with the given capacity.
290    fn mpsc_channel<T: Send + 'static>(
291        &self,
292        capacity: usize,
293    ) -> (Self::MpscSender<T>, Self::MpscReceiver<T>);
294
295    /// Create a oneshot channel.
296    fn oneshot_channel<T: Send + 'static>(
297        &self,
298    ) -> (Self::OneshotSender<T>, Self::OneshotReceiver<T>);
299
300    /// Create a broadcast channel.
301    fn broadcast_channel<T: Send + Clone + 'static>(
302        &self,
303        capacity: usize,
304    ) -> (Self::BroadcastSender<T>, Self::BroadcastReceiver<T>);
305
306    /// Create a watch channel.
307    fn watch_channel<T: Send + Sync + Clone + 'static>(
308        &self,
309        initial: T,
310    ) -> (Self::WatchSender<T>, Self::WatchReceiver<T>);
311
312    // ---- File I/O ----
313    /// Create a file for writing.
314    fn file_create<'a>(
315        &'a self,
316        path: &'a Path,
317    ) -> Pin<Box<dyn Future<Output = io::Result<Self::File>> + Send + 'a>>;
318
319    /// Open a file for reading.
320    fn file_open<'a>(
321        &'a self,
322        path: &'a Path,
323    ) -> Pin<Box<dyn Future<Output = io::Result<Self::File>> + Send + 'a>>;
324
325    // ---- Network ----
326    /// Bind a TCP listener to an address.
327    fn tcp_listen<'a>(
328        &'a self,
329        addr: &'a str,
330    ) -> Pin<Box<dyn Future<Output = io::Result<Self::TcpListener>> + Send + 'a>>;
331
332    /// Connect to a TCP address.
333    fn tcp_connect<'a>(
334        &'a self,
335        addr: SocketAddr,
336    ) -> Pin<Box<dyn Future<Output = io::Result<Self::TcpStream>> + Send + 'a>>;
337
338    /// Bind a UDP socket to an address.
339    fn udp_bind<'a>(
340        &'a self,
341        addr: &'a str,
342    ) -> Pin<Box<dyn Future<Output = io::Result<Self::UdpSocket>> + Send + 'a>>;
343}
344
345// ============================================================================
346// Channel Traits
347// ============================================================================
348
349/// Error when receiving from a closed oneshot channel.
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
351pub struct OneshotRecvError;
352
353impl fmt::Display for OneshotRecvError {
354    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355        write!(f, "oneshot channel sender dropped")
356    }
357}
358
359impl std::error::Error for OneshotRecvError {}
360
361/// MPSC sender trait.
362pub trait MpscSender<T: Send>: Clone + Send + Sync {
363    /// Send a value, waiting if the channel is full.
364    fn send(&self, value: T) -> Pin<Box<dyn Future<Output = Result<(), T>> + Send + '_>>;
365}
366
367/// MPSC receiver trait.
368pub trait MpscReceiver<T: Send>: Send {
369    /// Receive a value, returning None if the channel is closed.
370    fn recv(&mut self) -> Pin<Box<dyn Future<Output = Option<T>> + Send + '_>>;
371}
372
373/// Oneshot sender trait.
374pub trait OneshotSender<T: Send>: Send {
375    /// Send a value. Can only be called once.
376    fn send(self, value: T) -> Result<(), T>;
377}
378
379/// Error when receiving from a closed broadcast channel.
380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
381pub enum BroadcastRecvError {
382    /// The receiver lagged too far behind.
383    Lagged(u64),
384    /// The sender was dropped.
385    Closed,
386}
387
388impl fmt::Display for BroadcastRecvError {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        match self {
391            Self::Lagged(n) => write!(f, "receiver lagged by {n} messages"),
392            Self::Closed => write!(f, "broadcast channel closed"),
393        }
394    }
395}
396
397impl std::error::Error for BroadcastRecvError {}
398
399/// Broadcast sender trait.
400pub trait BroadcastSender<T: Send + Clone>: Clone + Send + Sync {
401    /// Send a value to all receivers.
402    fn send(&self, value: T) -> Result<usize, T>;
403
404    /// Create a new receiver.
405    fn subscribe(&self) -> Box<dyn BroadcastReceiver<T>>;
406}
407
408/// Broadcast receiver trait.
409pub trait BroadcastReceiver<T: Send + Clone>: Send {
410    /// Receive a value.
411    fn recv(&mut self) -> Pin<Box<dyn Future<Output = Result<T, BroadcastRecvError>> + Send + '_>>;
412}
413
414/// Error when receiving from a closed watch channel.
415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
416pub struct WatchRecvError;
417
418impl fmt::Display for WatchRecvError {
419    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420        write!(f, "watch channel closed")
421    }
422}
423
424impl std::error::Error for WatchRecvError {}
425
426/// Watch sender trait.
427pub trait WatchSender<T: Send + Sync>: Send + Sync {
428    /// Send a new value.
429    fn send(&self, value: T) -> Result<(), T>;
430}
431
432/// Watch receiver trait.
433pub trait WatchReceiver<T: Send + Sync>: Clone + Send + Sync {
434    /// Wait for a change.
435    fn changed(&mut self) -> Pin<Box<dyn Future<Output = Result<(), WatchRecvError>> + Send + '_>>;
436
437    /// Get the current value.
438    fn borrow_and_clone(&self) -> T;
439}
440
441// ============================================================================
442// File I/O Traits
443// ============================================================================
444
445/// Async file trait.
446pub trait AsyncFile: Send {
447    /// Write all bytes to the file.
448    fn write_all<'a>(
449        &'a mut self,
450        buf: &'a [u8],
451    ) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + 'a>>;
452
453    /// Read to fill the buffer exactly.
454    fn read_exact<'a>(
455        &'a mut self,
456        buf: &'a mut [u8],
457    ) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + 'a>>;
458
459    /// Read all bytes into a vector.
460    fn read_to_end<'a>(
461        &'a mut self,
462        buf: &'a mut Vec<u8>,
463    ) -> Pin<Box<dyn Future<Output = io::Result<usize>> + Send + 'a>>;
464
465    /// Seek to a position.
466    fn seek<'a>(
467        &'a mut self,
468        pos: SeekFrom,
469    ) -> Pin<Box<dyn Future<Output = io::Result<u64>> + Send + 'a>>;
470
471    /// Sync all data to disk.
472    fn sync_all(&self) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + '_>>;
473
474    /// Shutdown the file (for sockets).
475    fn shutdown(&mut self) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + '_>>;
476}
477
478// ============================================================================
479// Network Traits
480// ============================================================================
481
482/// Timeout error.
483#[derive(Debug, Clone, Copy, PartialEq, Eq)]
484pub struct TimeoutError;
485
486impl fmt::Display for TimeoutError {
487    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488        write!(f, "operation timed out")
489    }
490}
491
492impl std::error::Error for TimeoutError {}
493
494/// TCP listener trait.
495pub trait TcpListener: Send {
496    /// The stream type returned by accept.
497    type Stream: TcpStream;
498
499    /// Get the local address.
500    fn local_addr(&self) -> io::Result<SocketAddr>;
501
502    /// Accept a connection.
503    fn accept(
504        &mut self,
505    ) -> Pin<Box<dyn Future<Output = io::Result<(Self::Stream, SocketAddr)>> + Send + '_>>;
506}
507
508/// TCP stream trait.
509pub trait TcpStream: Send {
510    /// Read into a buffer.
511    fn read<'a>(
512        &'a mut self,
513        buf: &'a mut [u8],
514    ) -> Pin<Box<dyn Future<Output = io::Result<usize>> + Send + 'a>>;
515
516    /// Read to fill the buffer exactly.
517    fn read_exact<'a>(
518        &'a mut self,
519        buf: &'a mut [u8],
520    ) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + 'a>>;
521
522    /// Write all bytes.
523    fn write_all<'a>(
524        &'a mut self,
525        buf: &'a [u8],
526    ) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + 'a>>;
527
528    /// Shutdown the stream.
529    fn shutdown(&mut self) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + '_>>;
530}
531
532/// UDP socket trait.
533pub trait UdpSocket: Send {
534    /// Get the local address.
535    fn local_addr(&self) -> io::Result<SocketAddr>;
536
537    /// Send to an address.
538    fn send_to<'a>(
539        &'a self,
540        buf: &'a [u8],
541        addr: SocketAddr,
542    ) -> Pin<Box<dyn Future<Output = io::Result<usize>> + Send + 'a>>;
543
544    /// Receive from any address.
545    fn recv_from<'a>(
546        &'a self,
547        buf: &'a mut [u8],
548    ) -> Pin<Box<dyn Future<Output = io::Result<(usize, SocketAddr)>> + Send + 'a>>;
549}
550
551// ============================================================================
552// Test Registration
553// ============================================================================
554
555/// A registered conformance test.
556pub struct ConformanceTest<RT: RuntimeInterface> {
557    /// Test metadata.
558    pub meta: TestMeta,
559    /// The test function.
560    pub test_fn: fn(&RT) -> TestResult,
561}
562
563impl<RT: RuntimeInterface> ConformanceTest<RT> {
564    /// Create a new conformance test.
565    pub const fn new(meta: TestMeta, test_fn: fn(&RT) -> TestResult) -> Self {
566        Self { meta, test_fn }
567    }
568
569    /// Run the test.
570    pub fn run(&self, runtime: &RT) -> TestResult {
571        (self.test_fn)(runtime)
572    }
573}
574
575/// Macro for defining conformance tests.
576///
577/// # Example
578///
579/// ```ignore
580/// conformance_test! {
581///     id: "io-001",
582///     name: "File write and read",
583///     description: "Write data to file, read it back",
584///     category: TestCategory::IO,
585///     tags: ["file", "basic"],
586///     expected: "Read data matches written data",
587///     test: |rt| {
588///         rt.block_on(async {
589///             // test implementation
590///             TestResult::passed()
591///         })
592///     }
593/// }
594/// ```
595#[macro_export]
596macro_rules! conformance_test {
597    (
598        id: $id:literal,
599        name: $name:literal,
600        description: $desc:literal,
601        category: $cat:expr,
602        tags: [$($tag:literal),* $(,)?],
603        expected: $expected:literal,
604        test: |$rt:ident| $body:expr
605    ) => {
606        {
607            fn test_fn<RT: $crate::RuntimeInterface>($rt: &RT) -> $crate::TestResult {
608                $body
609            }
610
611            $crate::ConformanceTest::new(
612                $crate::TestMeta {
613                    id: $id.to_string(),
614                    name: $name.to_string(),
615                    description: $desc.to_string(),
616                    category: $cat,
617                    tags: vec![$($tag.to_string()),*],
618                    expected: $expected.to_string(),
619                },
620                test_fn,
621            )
622        }
623    };
624}