1#![allow(clippy::type_complexity)]
6#![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#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct TestResult {
84 pub passed: bool,
86 pub message: Option<String>,
88 pub checkpoints: Vec<Checkpoint>,
90 pub duration_ms: Option<u64>,
92}
93
94impl TestResult {
95 pub fn passed() -> Self {
97 Self {
98 passed: true,
99 message: None,
100 checkpoints: Vec::new(),
101 duration_ms: None,
102 }
103 }
104
105 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 pub fn with_checkpoint(mut self, checkpoint: Checkpoint) -> Self {
117 self.checkpoints.push(checkpoint);
118 self
119 }
120
121 pub fn with_duration(mut self, duration_ms: u64) -> Self {
123 self.duration_ms = Some(duration_ms);
124 self
125 }
126}
127
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130pub struct Checkpoint {
131 pub name: String,
133 pub data: serde_json::Value,
135}
136
137impl Checkpoint {
138 pub fn new(name: impl Into<String>, data: serde_json::Value) -> Self {
140 Self {
141 name: name.into(),
142 data,
143 }
144 }
145}
146
147pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
159pub enum TestCategory {
160 Spawn,
162 Channels,
164 IO,
166 Sync,
168 Time,
170 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#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct TestMeta {
194 pub id: String,
196 pub name: String,
198 pub description: String,
200 pub category: TestCategory,
202 pub tags: Vec<String>,
204 pub expected: String,
206}
207
208pub trait RuntimeInterface: Sized {
217 type JoinHandle<T: Send + 'static>: Future<Output = T> + Send + 'static;
220
221 type MpscSender<T: Send + 'static>: MpscSender<T> + 'static;
223
224 type MpscReceiver<T: Send + 'static>: MpscReceiver<T> + 'static;
226
227 type OneshotSender<T: Send + 'static>: OneshotSender<T> + 'static;
229
230 type OneshotReceiver<T: Send + 'static>: Future<Output = Result<T, OneshotRecvError>>
232 + Send
233 + 'static;
234
235 type BroadcastSender<T: Send + Clone + 'static>: BroadcastSender<T> + 'static;
237
238 type BroadcastReceiver<T: Send + Clone + 'static>: BroadcastReceiver<T> + 'static;
240
241 type WatchSender<T: Send + Sync + 'static>: WatchSender<T> + 'static;
243
244 type WatchReceiver<T: Send + Sync + Clone + 'static>: WatchReceiver<T> + 'static;
246
247 type File: AsyncFile + 'static;
249
250 type TcpListener: TcpListener<Stream = Self::TcpStream> + 'static;
252
253 type TcpStream: TcpStream + 'static;
255
256 type UdpSocket: UdpSocket + 'static;
258
259 fn spawn<F>(&self, future: F) -> Self::JoinHandle<F::Output>
262 where
263 F: Future + Send + 'static,
264 F::Output: Send + 'static;
265
266 fn block_on<F: Future>(&self, future: F) -> F::Output;
269
270 fn bench_alloc_snapshot(&self) -> Option<crate::bench::runner::BenchAllocSnapshot> {
272 None
273 }
274
275 fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
278
279 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 fn mpsc_channel<T: Send + 'static>(
291 &self,
292 capacity: usize,
293 ) -> (Self::MpscSender<T>, Self::MpscReceiver<T>);
294
295 fn oneshot_channel<T: Send + 'static>(
297 &self,
298 ) -> (Self::OneshotSender<T>, Self::OneshotReceiver<T>);
299
300 fn broadcast_channel<T: Send + Clone + 'static>(
302 &self,
303 capacity: usize,
304 ) -> (Self::BroadcastSender<T>, Self::BroadcastReceiver<T>);
305
306 fn watch_channel<T: Send + Sync + Clone + 'static>(
308 &self,
309 initial: T,
310 ) -> (Self::WatchSender<T>, Self::WatchReceiver<T>);
311
312 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 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 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 fn tcp_connect<'a>(
334 &'a self,
335 addr: SocketAddr,
336 ) -> Pin<Box<dyn Future<Output = io::Result<Self::TcpStream>> + Send + 'a>>;
337
338 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#[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
361pub trait MpscSender<T: Send>: Clone + Send + Sync {
363 fn send(&self, value: T) -> Pin<Box<dyn Future<Output = Result<(), T>> + Send + '_>>;
365}
366
367pub trait MpscReceiver<T: Send>: Send {
369 fn recv(&mut self) -> Pin<Box<dyn Future<Output = Option<T>> + Send + '_>>;
371}
372
373pub trait OneshotSender<T: Send>: Send {
375 fn send(self, value: T) -> Result<(), T>;
377}
378
379#[derive(Debug, Clone, Copy, PartialEq, Eq)]
381pub enum BroadcastRecvError {
382 Lagged(u64),
384 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
399pub trait BroadcastSender<T: Send + Clone>: Clone + Send + Sync {
401 fn send(&self, value: T) -> Result<usize, T>;
403
404 fn subscribe(&self) -> Box<dyn BroadcastReceiver<T>>;
406}
407
408pub trait BroadcastReceiver<T: Send + Clone>: Send {
410 fn recv(&mut self) -> Pin<Box<dyn Future<Output = Result<T, BroadcastRecvError>> + Send + '_>>;
412}
413
414#[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
426pub trait WatchSender<T: Send + Sync>: Send + Sync {
428 fn send(&self, value: T) -> Result<(), T>;
430}
431
432pub trait WatchReceiver<T: Send + Sync>: Clone + Send + Sync {
434 fn changed(&mut self) -> Pin<Box<dyn Future<Output = Result<(), WatchRecvError>> + Send + '_>>;
436
437 fn borrow_and_clone(&self) -> T;
439}
440
441pub trait AsyncFile: Send {
447 fn write_all<'a>(
449 &'a mut self,
450 buf: &'a [u8],
451 ) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + 'a>>;
452
453 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 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 fn seek<'a>(
467 &'a mut self,
468 pos: SeekFrom,
469 ) -> Pin<Box<dyn Future<Output = io::Result<u64>> + Send + 'a>>;
470
471 fn sync_all(&self) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + '_>>;
473
474 fn shutdown(&mut self) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + '_>>;
476}
477
478#[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
494pub trait TcpListener: Send {
496 type Stream: TcpStream;
498
499 fn local_addr(&self) -> io::Result<SocketAddr>;
501
502 fn accept(
504 &mut self,
505 ) -> Pin<Box<dyn Future<Output = io::Result<(Self::Stream, SocketAddr)>> + Send + '_>>;
506}
507
508pub trait TcpStream: Send {
510 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 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 fn write_all<'a>(
524 &'a mut self,
525 buf: &'a [u8],
526 ) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + 'a>>;
527
528 fn shutdown(&mut self) -> Pin<Box<dyn Future<Output = io::Result<()>> + Send + '_>>;
530}
531
532pub trait UdpSocket: Send {
534 fn local_addr(&self) -> io::Result<SocketAddr>;
536
537 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 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
551pub struct ConformanceTest<RT: RuntimeInterface> {
557 pub meta: TestMeta,
559 pub test_fn: fn(&RT) -> TestResult,
561}
562
563impl<RT: RuntimeInterface> ConformanceTest<RT> {
564 pub const fn new(meta: TestMeta, test_fn: fn(&RT) -> TestResult) -> Self {
566 Self { meta, test_fn }
567 }
568
569 pub fn run(&self, runtime: &RT) -> TestResult {
571 (self.test_fn)(runtime)
572 }
573}
574
575#[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}