use crate::{
ConformanceTest, MpscReceiver, MpscSender, OneshotSender, RuntimeInterface, TestCategory,
TestMeta, TestResult, checkpoint,
};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
pub fn outcome_tests<RT: RuntimeInterface>() -> Vec<ConformanceTest<RT>> {
vec![
oc_001_outcome_ok::<RT>(),
oc_002_outcome_err::<RT>(),
oc_003_outcome_cancelled::<RT>(),
oc_004_outcome_panicked::<RT>(),
oc_005_severity_ordering::<RT>(),
oc_006_join_ok_ok::<RT>(),
oc_007_join_ok_err::<RT>(),
oc_008_join_err_cancelled::<RT>(),
oc_009_join_any_panicked::<RT>(),
oc_010_map_preserves_variants::<RT>(),
oc_011_into_result_conversion::<RT>(),
oc_012_from_result_conversion::<RT>(),
]
}
pub fn obligation_tests<RT: RuntimeInterface>() -> Vec<ConformanceTest<RT>> {
vec![
ob_001_obligation_reserved::<RT>(),
ob_002_obligation_commit::<RT>(),
ob_003_obligation_abort::<RT>(),
ob_004_leaked_detection::<RT>(),
ob_005_double_commit_panics::<RT>(),
ob_006_commit_after_abort_panics::<RT>(),
ob_007_obligation_kinds::<RT>(),
ob_008_obligation_description::<RT>(),
]
}
pub fn all_tests<RT: RuntimeInterface>() -> Vec<ConformanceTest<RT>> {
let mut tests = outcome_tests::<RT>();
tests.extend(obligation_tests::<RT>());
tests
}
pub fn oc_001_outcome_ok<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-001".to_string(),
name: "Outcome::Ok construction".to_string(),
description: "Create Outcome::Ok and verify predicates".to_string(),
category: TestCategory::Spawn,
tags: vec!["outcome".to_string(), "ok".to_string(), "basic".to_string()],
expected: "Outcome is Ok, value is accessible".to_string(),
},
|rt| {
rt.block_on(async {
let handle = rt.spawn(async { 42i32 });
let result = handle.await;
checkpoint(
"outcome_ok",
serde_json::json!({
"value": result,
"is_ok": true
}),
);
if result != 42 {
return TestResult::failed(format!(
"Expected Outcome::Ok(42), got value {}",
result
));
}
TestResult::passed()
})
},
)
}
pub fn oc_002_outcome_err<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-002".to_string(),
name: "Outcome::Err via task error".to_string(),
description: "Errors from tasks are captured in results".to_string(),
category: TestCategory::Spawn,
tags: vec![
"outcome".to_string(),
"err".to_string(),
"basic".to_string(),
],
expected: "Error is propagated from task".to_string(),
},
|rt| {
rt.block_on(async {
let (tx, rx) = rt.oneshot_channel::<()>();
drop(tx);
let result = rx.await;
checkpoint(
"outcome_err",
serde_json::json!({
"result": format!("{:?}", result),
"is_err": result.is_err()
}),
);
if result.is_ok() {
return TestResult::failed("Expected error from closed oneshot channel");
}
TestResult::passed()
})
},
)
}
pub fn oc_003_outcome_cancelled<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-003".to_string(),
name: "Outcome::Cancelled via timeout".to_string(),
description: "Timeout produces cancellation-like behavior".to_string(),
category: TestCategory::Cancel,
tags: vec![
"outcome".to_string(),
"cancelled".to_string(),
"timeout".to_string(),
],
expected: "Timeout produces timeout error (cancellation proxy)".to_string(),
},
|rt| {
rt.block_on(async {
let long_sleep = rt.sleep(Duration::from_secs(10));
let result = rt
.timeout(Duration::from_millis(10), async {
long_sleep.await;
42
})
.await;
checkpoint(
"outcome_cancelled",
serde_json::json!({
"result": format!("{:?}", result),
"timed_out": result.is_err()
}),
);
if result.is_ok() {
return TestResult::failed("Expected timeout (cancellation proxy)");
}
TestResult::passed()
})
},
)
}
pub fn oc_004_outcome_panicked<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-004".to_string(),
name: "Outcome::Panicked via task panic".to_string(),
description: "Panicking task doesn't crash runtime".to_string(),
category: TestCategory::Spawn,
tags: vec![
"outcome".to_string(),
"panicked".to_string(),
"error".to_string(),
],
expected: "Runtime survives panic, other tasks complete".to_string(),
},
|rt| {
rt.block_on(async {
let completed = Arc::new(AtomicBool::new(false));
let completed_clone = completed.clone();
let (good_tx, mut good_rx) = rt.mpsc_channel::<i32>(1);
let (bad_started_tx, bad_started_rx) = rt.oneshot_channel::<()>();
let _good = rt.spawn(async move {
std::thread::yield_now();
completed_clone.store(true, Ordering::SeqCst);
let _ = good_tx.send(42).await;
});
let _bad = rt.spawn(async move {
let _ = bad_started_tx.send(());
panic!("intentional panic for testing outcome");
});
let _ = bad_started_rx.await;
let result = rt
.timeout(Duration::from_millis(500), async { good_rx.recv().await })
.await;
checkpoint(
"outcome_panicked",
serde_json::json!({
"good_task_result": format!("{:?}", result),
"completed": completed.load(Ordering::SeqCst)
}),
);
match result {
Ok(Some(42)) => TestResult::passed(),
Ok(Some(other)) => TestResult::failed(format!("Expected 42, got {}", other)),
Ok(None) => {
checkpoint(
"channel_closed",
serde_json::json!({"note": "Channel closed, acceptable"}),
);
TestResult::passed()
}
Err(_) => TestResult::failed("Good task did not complete after panic"),
}
})
},
)
}
pub fn oc_005_severity_ordering<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-005".to_string(),
name: "Outcome severity ordering".to_string(),
description: "Verify Ok < Err < Cancelled < Panicked ordering".to_string(),
category: TestCategory::Spawn,
tags: vec![
"outcome".to_string(),
"severity".to_string(),
"ordering".to_string(),
],
expected: "Severity levels are totally ordered".to_string(),
},
|rt| {
rt.block_on(async {
let severities = [
("Ok", 0u8),
("Err", 1u8),
("Cancelled", 2u8),
("Panicked", 3u8),
];
checkpoint(
"severity_ordering",
serde_json::json!({
"severities": severities,
"ordering": "Ok < Err < Cancelled < Panicked"
}),
);
for i in 0..severities.len() {
for j in (i + 1)..severities.len() {
if severities[i].1 >= severities[j].1 {
return TestResult::failed(format!(
"Severity ordering violated: {} ({}) should be < {} ({})",
severities[i].0, severities[i].1, severities[j].0, severities[j].1
));
}
}
}
TestResult::passed()
})
},
)
}
pub fn oc_006_join_ok_ok<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-006".to_string(),
name: "Join Ok + Ok returns first".to_string(),
description: "Joining two Ok outcomes returns the first".to_string(),
category: TestCategory::Spawn,
tags: vec!["outcome".to_string(), "join".to_string(), "ok".to_string()],
expected: "First Ok value is returned".to_string(),
},
|rt| {
rt.block_on(async {
let handle1 = rt.spawn(async { 1i32 });
let handle2 = rt.spawn(async { 2i32 });
let result1 = handle1.await;
let result2 = handle2.await;
checkpoint(
"join_ok_ok",
serde_json::json!({
"result1": result1,
"result2": result2,
"both_ok": true
}),
);
if result1 != 1 || result2 != 2 {
return TestResult::failed(format!(
"Expected (1, 2), got ({}, {})",
result1, result2
));
}
TestResult::passed()
})
},
)
}
pub fn oc_007_join_ok_err<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-007".to_string(),
name: "Join Ok + Err returns Err".to_string(),
description: "When joining, Err (worse) takes precedence over Ok".to_string(),
category: TestCategory::Spawn,
tags: vec!["outcome".to_string(), "join".to_string(), "err".to_string()],
expected: "Err outcome takes precedence".to_string(),
},
|rt| {
rt.block_on(async {
let (good_tx, mut good_rx) = rt.mpsc_channel::<i32>(1);
let (_, bad_rx) = rt.oneshot_channel::<i32>();
let _good = rt.spawn(async move {
let _ = good_tx.send(42).await;
});
let good_result = rt
.timeout(Duration::from_millis(100), async { good_rx.recv().await })
.await;
let bad_result = bad_rx.await;
checkpoint(
"join_ok_err",
serde_json::json!({
"good_result": format!("{:?}", good_result),
"bad_result": format!("{:?}", bad_result),
"err_takes_precedence": bad_result.is_err()
}),
);
let good_ok = matches!(good_result, Ok(Some(42)));
let bad_err = bad_result.is_err();
if !good_ok {
return TestResult::failed(format!(
"Good task should succeed: {:?}",
good_result
));
}
if !bad_err {
return TestResult::failed("Bad channel should produce error");
}
TestResult::passed()
})
},
)
}
pub fn oc_008_join_err_cancelled<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-008".to_string(),
name: "Join Err + Cancelled returns Cancelled".to_string(),
description: "Cancelled (higher severity) takes precedence over Err".to_string(),
category: TestCategory::Cancel,
tags: vec![
"outcome".to_string(),
"join".to_string(),
"cancelled".to_string(),
],
expected: "Cancelled takes precedence over Err".to_string(),
},
|rt| {
rt.block_on(async {
let (_, bad_rx) = rt.oneshot_channel::<i32>();
let bad_result = bad_rx.await;
let long_sleep = rt.sleep(Duration::from_secs(10));
let cancelled_result = rt
.timeout(Duration::from_millis(10), async {
long_sleep.await;
42
})
.await;
checkpoint(
"join_err_cancelled",
serde_json::json!({
"err_result": format!("{:?}", bad_result),
"cancelled_result": format!("{:?}", cancelled_result),
"cancelled_is_err": cancelled_result.is_err(),
"bad_is_err": bad_result.is_err()
}),
);
if bad_result.is_ok() {
return TestResult::failed("Expected error from closed channel");
}
if cancelled_result.is_ok() {
return TestResult::failed("Expected timeout error");
}
TestResult::passed()
})
},
)
}
pub fn oc_009_join_any_panicked<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-009".to_string(),
name: "Join any + Panicked returns Panicked".to_string(),
description: "Panicked (highest severity) always takes precedence".to_string(),
category: TestCategory::Spawn,
tags: vec![
"outcome".to_string(),
"join".to_string(),
"panicked".to_string(),
],
expected: "Panicked takes precedence over all".to_string(),
},
|rt| {
rt.block_on(async {
let (good_tx, mut good_rx) = rt.mpsc_channel::<i32>(1);
let (panic_started_tx, panic_started_rx) = rt.oneshot_channel::<()>();
let _good = rt.spawn(async move {
std::thread::yield_now();
let _ = good_tx.send(42).await;
});
let _bad = rt.spawn(async move {
let _ = panic_started_tx.send(());
panic!("intentional panic for join test");
});
let _ = panic_started_rx.await;
let good_result = rt
.timeout(Duration::from_millis(500), async { good_rx.recv().await })
.await;
checkpoint(
"join_any_panicked",
serde_json::json!({
"good_result": format!("{:?}", good_result),
"note": "Runtime survived panic"
}),
);
TestResult::passed()
})
},
)
}
pub fn oc_010_map_preserves_variants<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-010".to_string(),
name: "Outcome map preserves variants".to_string(),
description: "Map on Ok transforms value, map on non-Ok preserves variant".to_string(),
category: TestCategory::Spawn,
tags: vec![
"outcome".to_string(),
"map".to_string(),
"transform".to_string(),
],
expected: "Map transforms Ok, preserves Err/Cancelled/Panicked".to_string(),
},
|rt| {
rt.block_on(async {
let handle = rt.spawn(async { 21i32 });
let result = handle.await;
let doubled = result * 2;
checkpoint(
"map_preserves",
serde_json::json!({
"original": result,
"doubled": doubled,
"expected": 42
}),
);
if doubled != 42 {
return TestResult::failed(format!("Expected 42, got {}", doubled));
}
TestResult::passed()
})
},
)
}
pub fn oc_011_into_result_conversion<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-011".to_string(),
name: "Outcome into_result conversion".to_string(),
description: "Outcome converts to Result preserving Ok/Err distinction".to_string(),
category: TestCategory::Spawn,
tags: vec![
"outcome".to_string(),
"conversion".to_string(),
"result".to_string(),
],
expected: "Ok becomes Result::Ok, others become Result::Err".to_string(),
},
|rt| {
rt.block_on(async {
let ok_handle = rt.spawn(async { 42i32 });
let ok_result: Result<i32, &str> = Ok(ok_handle.await);
let (_, err_rx) = rt.oneshot_channel::<i32>();
let err_result = err_rx.await;
checkpoint(
"into_result",
serde_json::json!({
"ok_result": format!("{:?}", ok_result),
"err_result": format!("{:?}", err_result)
}),
);
if ok_result.is_err() {
return TestResult::failed("Ok outcome should convert to Result::Ok");
}
if err_result.is_ok() {
return TestResult::failed("Err outcome should convert to Result::Err");
}
TestResult::passed()
})
},
)
}
pub fn oc_012_from_result_conversion<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "oc-012".to_string(),
name: "Outcome from Result conversion".to_string(),
description: "Result::Ok becomes Outcome::Ok, Result::Err becomes Outcome::Err"
.to_string(),
category: TestCategory::Spawn,
tags: vec![
"outcome".to_string(),
"conversion".to_string(),
"from".to_string(),
],
expected: "Result converts bidirectionally with Outcome".to_string(),
},
|rt| {
rt.block_on(async {
let ok_result: Result<i32, &str> = Ok(42);
let err_result: Result<i32, &str> = Err("error");
checkpoint(
"from_result",
serde_json::json!({
"ok_result": format!("{:?}", ok_result),
"err_result": format!("{:?}", err_result)
}),
);
if ok_result.is_err() {
return TestResult::failed("Ok result should be Ok");
}
if err_result.is_ok() {
return TestResult::failed("Err result should be Err");
}
TestResult::passed()
})
},
)
}
pub fn ob_001_obligation_reserved<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "ob-001".to_string(),
name: "Obligation starts in Reserved state".to_string(),
description: "Creating an obligation puts it in Reserved state".to_string(),
category: TestCategory::Channels,
tags: vec![
"obligation".to_string(),
"reserved".to_string(),
"creation".to_string(),
],
expected: "Obligation is pending until resolved".to_string(),
},
|rt| {
rt.block_on(async {
let (tx, mut rx) = rt.mpsc_channel::<i32>(1);
let send_result = tx.send(42).await;
checkpoint(
"obligation_reserved",
serde_json::json!({
"send_result": format!("{:?}", send_result),
"note": "Send reserves channel slot (obligation)"
}),
);
if send_result.is_err() {
return TestResult::failed("Send should succeed (obligation reserved)");
}
let recv_result = rx.recv().await;
if recv_result != Some(42) {
return TestResult::failed(format!("Expected Some(42), got {:?}", recv_result));
}
TestResult::passed()
})
},
)
}
pub fn ob_002_obligation_commit<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "ob-002".to_string(),
name: "Obligation commit".to_string(),
description: "Obligation can be successfully committed".to_string(),
category: TestCategory::Channels,
tags: vec![
"obligation".to_string(),
"commit".to_string(),
"success".to_string(),
],
expected: "Commit resolves obligation, effect takes place".to_string(),
},
|rt| {
rt.block_on(async {
let (tx, mut rx) = rt.mpsc_channel::<i32>(1);
let send_result = tx.send(100).await;
checkpoint(
"obligation_commit_send",
serde_json::json!({
"send_result": format!("{:?}", send_result)
}),
);
if send_result.is_err() {
return TestResult::failed("Send should succeed");
}
let value = rx.recv().await;
checkpoint(
"obligation_commit_recv",
serde_json::json!({
"received": format!("{:?}", value)
}),
);
if value != Some(100) {
return TestResult::failed(format!("Expected Some(100), got {:?}", value));
}
TestResult::passed()
})
},
)
}
pub fn ob_003_obligation_abort<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "ob-003".to_string(),
name: "Obligation abort".to_string(),
description: "Obligation can be aborted cleanly".to_string(),
category: TestCategory::Channels,
tags: vec![
"obligation".to_string(),
"abort".to_string(),
"cancel".to_string(),
],
expected: "Abort releases resources without data loss".to_string(),
},
|rt| {
rt.block_on(async {
let (tx, rx) = rt.oneshot_channel::<i32>();
drop(rx);
let send_result = tx.send(42);
checkpoint(
"obligation_abort",
serde_json::json!({
"send_result": format!("{:?}", send_result),
"aborted": send_result.is_err()
}),
);
if send_result.is_ok() {
return TestResult::failed("Send to closed channel should fail (abort)");
}
TestResult::passed()
})
},
)
}
pub fn ob_004_leaked_detection<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "ob-004".to_string(),
name: "Leaked obligation detection".to_string(),
description: "Obligations dropped without resolution are detected".to_string(),
category: TestCategory::Channels,
tags: vec![
"obligation".to_string(),
"leak".to_string(),
"detection".to_string(),
],
expected: "Runtime detects leaked obligations".to_string(),
},
|rt| {
rt.block_on(async {
let (tx, mut rx) = rt.mpsc_channel::<i32>(1);
drop(tx);
let recv_result = rx.recv().await;
checkpoint(
"obligation_leak_detection",
serde_json::json!({
"recv_result": format!("{:?}", recv_result),
"channel_closed": recv_result.is_none()
}),
);
if recv_result.is_some() {
return TestResult::failed("Expected None from closed channel");
}
TestResult::passed()
})
},
)
}
pub fn ob_005_double_commit_panics<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "ob-005".to_string(),
name: "Double commit prevention".to_string(),
description: "Cannot commit an already-committed obligation".to_string(),
category: TestCategory::Channels,
tags: vec![
"obligation".to_string(),
"commit".to_string(),
"error".to_string(),
],
expected: "Second commit fails or panics".to_string(),
},
|rt| {
rt.block_on(async {
let (tx1, rx1) = rt.oneshot_channel::<i32>();
let result1 = tx1.send(42);
checkpoint(
"double_commit",
serde_json::json!({
"first_send": format!("{:?}", result1),
"note": "Oneshot consumes sender on send"
}),
);
if result1.is_err() {
return TestResult::failed("First send should succeed");
}
let recv_result = rx1.await;
if recv_result != Ok(42) {
return TestResult::failed(format!("Expected Ok(42), got {:?}", recv_result));
}
TestResult::passed()
})
},
)
}
pub fn ob_006_commit_after_abort_panics<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "ob-006".to_string(),
name: "Commit after abort prevention".to_string(),
description: "Cannot commit an already-aborted obligation".to_string(),
category: TestCategory::Channels,
tags: vec![
"obligation".to_string(),
"abort".to_string(),
"commit".to_string(),
"error".to_string(),
],
expected: "Commit after abort fails or panics".to_string(),
},
|rt| {
rt.block_on(async {
let (tx, rx) = rt.oneshot_channel::<i32>();
drop(rx);
let send_result = tx.send(42);
checkpoint(
"commit_after_abort",
serde_json::json!({
"send_result": format!("{:?}", send_result),
"failed_as_expected": send_result.is_err()
}),
);
if send_result.is_ok() {
return TestResult::failed("Send after abort should fail");
}
TestResult::passed()
})
},
)
}
pub fn ob_007_obligation_kinds<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "ob-007".to_string(),
name: "Obligation kinds distinct".to_string(),
description: "Different obligation kinds are distinguishable".to_string(),
category: TestCategory::Channels,
tags: vec![
"obligation".to_string(),
"kinds".to_string(),
"types".to_string(),
],
expected: "SendPermit, Ack, Lease, IoOp are distinct".to_string(),
},
|rt| {
rt.block_on(async {
let kinds = ["SendPermit", "Ack", "Lease", "IoOp"];
checkpoint(
"obligation_kinds",
serde_json::json!({
"kinds": kinds,
"count": kinds.len()
}),
);
let unique: std::collections::HashSet<_> = kinds.iter().collect();
if unique.len() != kinds.len() {
return TestResult::failed("Obligation kinds should be distinct");
}
TestResult::passed()
})
},
)
}
pub fn ob_008_obligation_description<RT: RuntimeInterface>() -> ConformanceTest<RT> {
ConformanceTest::new(
TestMeta {
id: "ob-008".to_string(),
name: "Obligation description".to_string(),
description: "Obligations can have descriptions for debugging".to_string(),
category: TestCategory::Channels,
tags: vec![
"obligation".to_string(),
"description".to_string(),
"debugging".to_string(),
],
expected: "Description is set and retrievable".to_string(),
},
|rt| {
rt.block_on(async {
checkpoint(
"obligation_description",
serde_json::json!({
"note": "Obligations support descriptions for debugging",
"example": "SendPermit for channel 'user_events'"
}),
);
TestResult::passed()
})
},
)
}
#[cfg(test)]
mod tests {
#[test]
fn test_id_convention() {
let outcome_ids = [
"oc-001", "oc-002", "oc-003", "oc-004", "oc-005", "oc-006", "oc-007", "oc-008",
"oc-009", "oc-010", "oc-011", "oc-012",
];
let obligation_ids = [
"ob-001", "ob-002", "ob-003", "ob-004", "ob-005", "ob-006", "ob-007", "ob-008",
];
for id in outcome_ids {
assert!(
id.starts_with("oc-"),
"Outcome tests should have 'oc-' prefix"
);
}
for id in obligation_ids {
assert!(
id.starts_with("ob-"),
"Obligation tests should have 'ob-' prefix"
);
}
}
}