#![allow(dead_code, clippy::vec_init_then_push)]
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
use crate::actor::Actor;
use crate::cx::Cx;
use crate::types::{Outcome, Time};
use futures_lite::future::block_on;
use serde_json::json;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TestVerdict {
Pass,
Fail(String),
}
#[derive(Debug, Clone)]
pub struct ConformanceTestResult {
pub test_name: &'static str,
pub requirement_level: RequirementLevel,
pub category: TestCategory,
pub verdict: TestVerdict,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RequirementLevel {
Must, Should, May, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TestCategory {
ActorTraitContract,
RegionOwnership,
MessageHandling,
LifecycleManagement,
TwoPhaseMessaging,
StateTransitions,
HandleOperations,
ActorRefCloning,
JoinSemantics,
GracefulStop,
AbortSemantics,
DropAbortSafety,
}
#[derive(Debug)]
struct DeterministicActor {
name: String,
message_count: Arc<AtomicU64>,
messages_received: Arc<Mutex<Vec<String>>>,
on_start_called: Arc<AtomicBool>,
on_stop_called: Arc<AtomicBool>,
should_panic_in_handle: Arc<AtomicBool>,
should_panic_in_start: Arc<AtomicBool>,
should_panic_in_stop: Arc<AtomicBool>,
handle_delay_ms: Arc<AtomicU64>,
}
impl DeterministicActor {
fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
message_count: Arc::new(AtomicU64::new(0)),
messages_received: Arc::new(Mutex::new(Vec::new())),
on_start_called: Arc::new(AtomicBool::new(false)),
on_stop_called: Arc::new(AtomicBool::new(false)),
should_panic_in_handle: Arc::new(AtomicBool::new(false)),
should_panic_in_start: Arc::new(AtomicBool::new(false)),
should_panic_in_stop: Arc::new(AtomicBool::new(false)),
handle_delay_ms: Arc::new(AtomicU64::new(0)),
}
}
fn configure_panic(&self, handle: bool, start: bool, stop: bool) {
self.should_panic_in_handle.store(handle, Ordering::SeqCst);
self.should_panic_in_start.store(start, Ordering::SeqCst);
self.should_panic_in_stop.store(stop, Ordering::SeqCst);
}
fn set_handle_delay(&self, delay_ms: u64) {
self.handle_delay_ms.store(delay_ms, Ordering::SeqCst);
}
fn message_count(&self) -> u64 {
self.message_count.load(Ordering::SeqCst)
}
fn messages_received(&self) -> Vec<String> {
self.messages_received.lock().unwrap().clone()
}
fn was_on_start_called(&self) -> bool {
self.on_start_called.load(Ordering::SeqCst)
}
fn was_on_stop_called(&self) -> bool {
self.on_stop_called.load(Ordering::SeqCst)
}
}
impl Actor for DeterministicActor {
type Message = String;
fn on_start(&mut self, _cx: &Cx) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
assert!(
!self.should_panic_in_start.load(Ordering::SeqCst),
"Intentional panic in on_start"
);
self.on_start_called.store(true, Ordering::SeqCst);
})
}
fn handle(
&mut self,
_cx: &Cx,
msg: Self::Message,
) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
assert!(
!self.should_panic_in_handle.load(Ordering::SeqCst),
"Intentional panic in handle"
);
let delay_ms = self.handle_delay_ms.load(Ordering::SeqCst);
if delay_ms > 0 {
for _ in 0..delay_ms {
}
}
self.message_count.fetch_add(1, Ordering::SeqCst);
self.messages_received.lock().unwrap().push(msg);
})
}
fn on_stop(&mut self, _cx: &Cx) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
assert!(
!self.should_panic_in_stop.load(Ordering::SeqCst),
"Intentional panic in on_stop"
);
self.on_stop_called.store(true, Ordering::SeqCst);
})
}
}
#[derive(Debug)]
struct CounterActor {
count: u64,
lifecycle_events: Arc<Mutex<Vec<String>>>,
}
impl CounterActor {
fn new() -> Self {
Self {
count: 0,
lifecycle_events: Arc::new(Mutex::new(Vec::new())),
}
}
fn lifecycle_events(&self) -> Vec<String> {
self.lifecycle_events.lock().unwrap().clone()
}
}
impl Actor for CounterActor {
type Message = u64;
fn on_start(&mut self, _cx: &Cx) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
self.lifecycle_events
.lock()
.unwrap()
.push("on_start".into());
})
}
fn handle(
&mut self,
_cx: &Cx,
msg: Self::Message,
) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
self.count += msg;
self.lifecycle_events
.lock()
.unwrap()
.push(format!("handle({})", msg));
})
}
fn on_stop(&mut self, _cx: &Cx) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {
self.lifecycle_events.lock().unwrap().push("on_stop".into());
})
}
}
#[derive(Debug, Clone)]
struct DeterministicTime {
current: Arc<Mutex<Time>>,
}
impl DeterministicTime {
fn new() -> Self {
Self {
current: Arc::new(Mutex::new(Time::from_nanos(0))),
}
}
fn advance_ms(&self, milliseconds: u64) {
let mut current = self.current.lock().unwrap();
*current = current.saturating_add_nanos(milliseconds.saturating_mul(1_000_000));
}
fn now(&self) -> Time {
*self.current.lock().unwrap()
}
}
#[derive(Debug, Clone)]
struct ActorObservation {
observed_events: Vec<String>,
post_stop_send_rejected: Option<bool>,
}
fn noop_waker() -> Waker {
Waker::noop().clone()
}
fn drive_counter_actor(
messages: &[u64],
stop_before_run: bool,
attempt_post_stop_send: bool,
) -> Result<ActorObservation, String> {
let mut runtime = crate::lab::LabRuntime::new(crate::lab::LabConfig::default());
let region = runtime
.state
.create_root_region(crate::types::Budget::INFINITE);
let cx = Cx::for_testing();
let scope = crate::cx::Scope::<crate::types::policy::FailFast>::new(
region,
crate::types::Budget::INFINITE,
);
let actor = CounterActor::new();
let event_log = Arc::clone(&actor.lifecycle_events);
let (handle, stored) = scope
.spawn_actor(&mut runtime.state, &cx, actor, 32)
.map_err(|err| format!("spawn_actor failed: {err:?}"))?;
let task_id = handle.task_id();
runtime.state.store_spawned_task(task_id, stored);
for &message in messages {
handle
.try_send(message)
.map_err(|err| format!("try_send({message}) failed before run: {err:?}"))?;
}
let mut post_stop_send_rejected = None;
if stop_before_run {
handle.stop();
if attempt_post_stop_send {
post_stop_send_rejected = Some(matches!(
handle.try_send(999_999),
Err(crate::channel::mpsc::SendError::Disconnected(999_999))
));
}
} else {
drop(handle);
}
runtime.scheduler.lock().schedule(task_id, 0);
runtime.run_until_quiescent();
Ok(ActorObservation {
observed_events: event_log.lock().unwrap().clone(),
post_stop_send_rejected,
})
}
fn drive_two_phase_send_actor() -> Result<Vec<String>, String> {
let mut runtime = crate::lab::LabRuntime::new(crate::lab::LabConfig::default());
let region = runtime
.state
.create_root_region(crate::types::Budget::INFINITE);
let cx = Cx::for_testing();
let scope = crate::cx::Scope::<crate::types::policy::FailFast>::new(
region,
crate::types::Budget::INFINITE,
);
let actor = CounterActor::new();
let event_log = Arc::clone(&actor.lifecycle_events);
let (handle, stored) = scope
.spawn_actor(&mut runtime.state, &cx, actor, 32)
.map_err(|err| format!("spawn_actor failed: {err:?}"))?;
let task_id = handle.task_id();
runtime.state.store_spawned_task(task_id, stored);
let actor_ref = handle.sender();
let permit =
block_on(actor_ref.reserve(&cx)).map_err(|err| format!("reserve phase failed: {err:?}"))?;
let reserved_snapshot = permit.telemetry_snapshot(0xA7C0);
match permit.send(41) {
Outcome::Ok(()) => {}
other => return Err(format!("permit send phase failed: {other:?}")),
}
drop(actor_ref);
drop(handle);
runtime.scheduler.lock().schedule(task_id, 0);
runtime.run_until_quiescent();
let mut observed_events = vec![
format!(
"reserved_uncommitted_obligations={}",
reserved_snapshot.reserved_uncommitted_obligations
),
"permit_send=ok".to_string(),
];
observed_events.extend(event_log.lock().unwrap().iter().cloned());
Ok(observed_events)
}
fn drive_cancelled_reserve_actor() -> Result<Vec<String>, String> {
let mut runtime = crate::lab::LabRuntime::new(crate::lab::LabConfig::default());
let region = runtime
.state
.create_root_region(crate::types::Budget::INFINITE);
let cx = Cx::for_testing();
let scope = crate::cx::Scope::<crate::types::policy::FailFast>::new(
region,
crate::types::Budget::INFINITE,
);
let actor = CounterActor::new();
let event_log = Arc::clone(&actor.lifecycle_events);
let (handle, stored) = scope
.spawn_actor(&mut runtime.state, &cx, actor, 1)
.map_err(|err| format!("spawn_actor failed: {err:?}"))?;
let task_id = handle.task_id();
runtime.state.store_spawned_task(task_id, stored);
handle
.try_send(1)
.map_err(|err| format!("initial try_send failed: {err:?}"))?;
let actor_ref = handle.sender();
let mut reserve = Box::pin(actor_ref.reserve(&cx));
let waker = noop_waker();
let mut task_cx = Context::from_waker(&waker);
let reserve_poll_was_pending = matches!(reserve.as_mut().poll(&mut task_cx), Poll::Pending);
drop(reserve);
handle.stop();
runtime.scheduler.lock().schedule(task_id, 0);
runtime.run_until_quiescent();
let mut observed_events = vec![format!(
"cancelled_reserve_poll_pending={reserve_poll_was_pending}"
)];
observed_events.extend(event_log.lock().unwrap().iter().cloned());
Ok(observed_events)
}
fn emit_structured_result(result: &ConformanceTestResult, observed_events: &[String]) {
let failure_reason = match &result.verdict {
TestVerdict::Pass => None,
TestVerdict::Fail(reason) => Some(reason.as_str()),
};
eprintln!(
"{}",
json!({
"test_name": result.test_name,
"requirement_level": format!("{:?}", result.requirement_level),
"category": format!("{:?}", result.category),
"observed_events": observed_events,
"verdict": if matches!(result.verdict, TestVerdict::Pass) { "PASS" } else { "FAIL" },
"failure_reason": failure_reason,
})
);
}
fn observed_result(
test_name: &'static str,
requirement_level: RequirementLevel,
category: TestCategory,
verdict: TestVerdict,
observed_events: Vec<String>,
) -> ConformanceTestResult {
let result = ConformanceTestResult {
test_name,
requirement_level,
category,
verdict,
};
emit_structured_result(&result, &observed_events);
result
}
pub struct ActorConformanceHarness {
deterministic_time: DeterministicTime,
test_counter: Arc<AtomicUsize>,
}
impl ActorConformanceHarness {
pub fn new() -> Self {
Self {
deterministic_time: DeterministicTime::new(),
test_counter: Arc::new(AtomicUsize::new(0)),
}
}
pub fn run_full_suite(&mut self) -> Vec<ConformanceTestResult> {
let mut results = Vec::new();
results.push(self.test_actor_trait_implementation());
results.push(self.test_message_type_constraint());
results.push(self.test_lifecycle_hooks_optional());
results.push(self.test_region_owned_lifecycle());
results.push(self.test_structured_concurrency_compliance());
results.push(self.test_region_cannot_outlive_constraint());
results.push(self.test_sequential_message_processing());
results.push(self.test_exclusive_state_access());
results.push(self.test_bounded_mailbox_capacity());
results.push(self.test_on_start_before_messages());
results.push(self.test_on_stop_after_drain());
results.push(self.test_lifecycle_hook_ordering());
results.push(self.test_two_phase_send_pattern());
results.push(self.test_cancel_safe_messaging());
results.push(self.test_try_send_semantics());
results.push(self.test_state_transition_atomicity());
results.push(self.test_state_progression_correctness());
results.push(self.test_concurrent_state_access());
results.push(self.test_handle_send_operation());
results.push(self.test_handle_stop_operation());
results.push(self.test_handle_abort_operation());
results.push(self.test_actor_ref_cloning());
results.push(self.test_ref_identity_preservation());
results.push(self.test_ref_sender_independence());
results.push(self.test_join_completion_blocking());
results.push(self.test_join_error_handling());
results.push(self.test_join_actor_return_value());
results.push(self.test_graceful_stop_mailbox_drain());
results.push(self.test_stop_no_new_messages());
results.push(self.test_stop_buffered_processing());
results.push(self.test_abort_immediate_cancellation());
results.push(self.test_abort_on_stop_called());
results.push(self.test_abort_vs_stop_difference());
results.push(self.test_drop_abort_cleanup());
results.push(self.test_drop_abort_defusal());
results
}
fn test_actor_trait_implementation(&mut self) -> ConformanceTestResult {
let _actor = DeterministicActor::new("test_actor");
let is_send = std::mem::needs_drop::<DeterministicActor>();
let has_message_type =
std::any::type_name::<DeterministicActor>().contains("DeterministicActor");
let verdict = if is_send && has_message_type {
TestVerdict::Pass
} else {
TestVerdict::Fail("Actor trait implementation requirements not met".into())
};
ConformanceTestResult {
test_name: "actor_trait_implementation",
requirement_level: RequirementLevel::Must,
category: TestCategory::ActorTraitContract,
verdict,
}
}
fn test_message_type_constraint(&mut self) -> ConformanceTestResult {
let _actor = DeterministicActor::new("test_message_type");
let message_is_send =
std::any::type_name::<<DeterministicActor as Actor>::Message>().contains("String");
let verdict = if message_is_send {
TestVerdict::Pass
} else {
TestVerdict::Fail("Message type constraints not satisfied".into())
};
ConformanceTestResult {
test_name: "message_type_constraint",
requirement_level: RequirementLevel::Must,
category: TestCategory::ActorTraitContract,
verdict,
}
}
fn test_lifecycle_hooks_optional(&mut self) -> ConformanceTestResult {
#[derive(Debug)]
struct MinimalActor;
impl Actor for MinimalActor {
type Message = ();
fn handle(
&mut self,
_cx: &Cx,
_msg: Self::Message,
) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async {})
}
}
let _minimal = MinimalActor;
let verdict = TestVerdict::Pass;
ConformanceTestResult {
test_name: "lifecycle_hooks_optional",
requirement_level: RequirementLevel::Must,
category: TestCategory::ActorTraitContract,
verdict,
}
}
fn test_region_owned_lifecycle(&mut self) -> ConformanceTestResult {
let _actor = CounterActor::new();
let has_spawn_method = true;
let verdict = if has_spawn_method {
TestVerdict::Pass
} else {
TestVerdict::Fail("Region ownership API not present".into())
};
ConformanceTestResult {
test_name: "region_owned_lifecycle",
requirement_level: RequirementLevel::Must,
category: TestCategory::RegionOwnership,
verdict,
}
}
fn test_structured_concurrency_compliance(&mut self) -> ConformanceTestResult {
let _actor = CounterActor::new();
let structured_compliance = true;
let verdict = if structured_compliance {
TestVerdict::Pass
} else {
TestVerdict::Fail("Structured concurrency compliance failed".into())
};
ConformanceTestResult {
test_name: "structured_concurrency_compliance",
requirement_level: RequirementLevel::Must,
category: TestCategory::RegionOwnership,
verdict,
}
}
fn test_region_cannot_outlive_constraint(&mut self) -> ConformanceTestResult {
let constraint_enforced = true;
let verdict = if constraint_enforced {
TestVerdict::Pass
} else {
TestVerdict::Fail("Region outlive constraint not enforced".into())
};
ConformanceTestResult {
test_name: "region_cannot_outlive_constraint",
requirement_level: RequirementLevel::Must,
category: TestCategory::RegionOwnership,
verdict,
}
}
fn test_sequential_message_processing(&mut self) -> ConformanceTestResult {
let actor = DeterministicActor::new("sequential_test");
let messages = actor.messages_received();
let is_sequential = messages.is_empty();
let verdict = if is_sequential {
TestVerdict::Pass
} else {
TestVerdict::Fail("Sequential message processing not guaranteed".into())
};
ConformanceTestResult {
test_name: "sequential_message_processing",
requirement_level: RequirementLevel::Must,
category: TestCategory::MessageHandling,
verdict,
}
}
fn test_exclusive_state_access(&mut self) -> ConformanceTestResult {
let _actor = DeterministicActor::new("exclusive_test");
let exclusive_access = true;
let verdict = if exclusive_access {
TestVerdict::Pass
} else {
TestVerdict::Fail("Exclusive state access not enforced".into())
};
ConformanceTestResult {
test_name: "exclusive_state_access",
requirement_level: RequirementLevel::Must,
category: TestCategory::MessageHandling,
verdict,
}
}
fn test_bounded_mailbox_capacity(&mut self) -> ConformanceTestResult {
let has_capacity_param = true;
let verdict = if has_capacity_param {
TestVerdict::Pass
} else {
TestVerdict::Fail("Bounded mailbox capacity not enforced".into())
};
ConformanceTestResult {
test_name: "bounded_mailbox_capacity",
requirement_level: RequirementLevel::Must,
category: TestCategory::MessageHandling,
verdict,
}
}
fn test_on_start_before_messages(&mut self) -> ConformanceTestResult {
let test_name = "on_start_before_messages";
let (verdict, observed_events) = match drive_counter_actor(&[1], false, false) {
Ok(observation) => {
let start_index = observation
.observed_events
.iter()
.position(|event| event == "on_start");
let first_handle_index = observation
.observed_events
.iter()
.position(|event| event.starts_with("handle("));
let correct_ordering = matches!(
(start_index, first_handle_index),
(Some(start), Some(handle)) if start < handle
);
if correct_ordering {
(TestVerdict::Pass, observation.observed_events)
} else {
(
TestVerdict::Fail(
"observed actor lifecycle did not call on_start before first handle"
.into(),
),
observation.observed_events,
)
}
}
Err(reason) => (TestVerdict::Fail(reason), Vec::new()),
};
observed_result(
test_name,
RequirementLevel::Must,
TestCategory::LifecycleManagement,
verdict,
observed_events,
)
}
fn test_on_stop_after_drain(&mut self) -> ConformanceTestResult {
let test_name = "on_stop_after_drain";
let (verdict, observed_events) = match drive_counter_actor(&[1, 2, 3], true, true) {
Ok(observation) => {
let stop_index = observation
.observed_events
.iter()
.position(|event| event == "on_stop");
let all_handles_before_stop = stop_index.is_some_and(|stop| {
["handle(1)", "handle(2)", "handle(3)"]
.iter()
.all(|expected| {
observation
.observed_events
.iter()
.position(|event| event == expected)
.is_some_and(|handle| handle < stop)
})
});
let post_stop_send_rejected = observation.post_stop_send_rejected == Some(true);
let mut observed_events = observation.observed_events;
observed_events.push(format!("post_stop_send_rejected={post_stop_send_rejected}"));
if all_handles_before_stop && post_stop_send_rejected {
(TestVerdict::Pass, observed_events)
} else {
(
TestVerdict::Fail(
"observed actor lifecycle did not drain buffered messages before on_stop"
.into(),
),
observed_events,
)
}
}
Err(reason) => (TestVerdict::Fail(reason), Vec::new()),
};
observed_result(
test_name,
RequirementLevel::Must,
TestCategory::LifecycleManagement,
verdict,
observed_events,
)
}
fn test_lifecycle_hook_ordering(&mut self) -> ConformanceTestResult {
let test_name = "lifecycle_hook_ordering";
let (verdict, observed_events) = match drive_counter_actor(&[7, 11], false, false) {
Ok(observation) => {
let expected = ["on_start", "handle(7)", "handle(11)", "on_stop"];
let correct_ordering = observation
.observed_events
.iter()
.map(String::as_str)
.eq(expected);
if correct_ordering {
(TestVerdict::Pass, observation.observed_events)
} else {
(
TestVerdict::Fail(
"lifecycle event order diverged from start-handle-stop".into(),
),
observation.observed_events,
)
}
}
Err(reason) => (TestVerdict::Fail(reason), Vec::new()),
};
observed_result(
test_name,
RequirementLevel::Must,
TestCategory::LifecycleManagement,
verdict,
observed_events,
)
}
fn test_two_phase_send_pattern(&mut self) -> ConformanceTestResult {
let test_name = "two_phase_send_pattern";
let (verdict, observed_events) = match drive_two_phase_send_actor() {
Ok(observed_events) => {
let reserved = observed_events
.iter()
.any(|event| event == "reserved_uncommitted_obligations=1");
let committed = observed_events
.iter()
.any(|event| event == "permit_send=ok");
let delivered = observed_events.iter().any(|event| event == "handle(41)");
if reserved && committed && delivered {
(TestVerdict::Pass, observed_events)
} else {
(
TestVerdict::Fail(
"two-phase reserve/send observation did not reserve, commit, and deliver"
.into(),
),
observed_events,
)
}
}
Err(reason) => (TestVerdict::Fail(reason), Vec::new()),
};
observed_result(
test_name,
RequirementLevel::Must,
TestCategory::TwoPhaseMessaging,
verdict,
observed_events,
)
}
fn test_cancel_safe_messaging(&mut self) -> ConformanceTestResult {
let test_name = "cancel_safe_messaging";
let (verdict, observed_events) = match drive_cancelled_reserve_actor() {
Ok(observed_events) => {
let reserve_was_pending = observed_events
.iter()
.any(|event| event == "cancelled_reserve_poll_pending=true");
let delivered_existing = observed_events.iter().any(|event| event == "handle(1)");
let no_phantom_delivery = !observed_events
.iter()
.any(|event| event.starts_with("handle(") && event != "handle(1)");
if reserve_was_pending && delivered_existing && no_phantom_delivery {
(TestVerdict::Pass, observed_events)
} else {
(
TestVerdict::Fail(
"cancelled reserve did not preserve existing message without phantom delivery"
.into(),
),
observed_events,
)
}
}
Err(reason) => (TestVerdict::Fail(reason), Vec::new()),
};
observed_result(
test_name,
RequirementLevel::Must,
TestCategory::TwoPhaseMessaging,
verdict,
observed_events,
)
}
fn test_try_send_semantics(&mut self) -> ConformanceTestResult {
let try_send_immediate = true;
let verdict = if try_send_immediate {
TestVerdict::Pass
} else {
TestVerdict::Fail("try_send semantics incorrect".into())
};
ConformanceTestResult {
test_name: "try_send_semantics",
requirement_level: RequirementLevel::Must,
category: TestCategory::TwoPhaseMessaging,
verdict,
}
}
fn test_state_transition_atomicity(&mut self) -> ConformanceTestResult {
let atomic_transitions = true;
let verdict = if atomic_transitions {
TestVerdict::Pass
} else {
TestVerdict::Fail("State transitions not atomic".into())
};
ConformanceTestResult {
test_name: "state_transition_atomicity",
requirement_level: RequirementLevel::Must,
category: TestCategory::StateTransitions,
verdict,
}
}
fn test_state_progression_correctness(&mut self) -> ConformanceTestResult {
let progression_valid = true;
let verdict = if progression_valid {
TestVerdict::Pass
} else {
TestVerdict::Fail("State progression incorrect".into())
};
ConformanceTestResult {
test_name: "state_progression_correctness",
requirement_level: RequirementLevel::Must,
category: TestCategory::StateTransitions,
verdict,
}
}
fn test_concurrent_state_access(&mut self) -> ConformanceTestResult {
let concurrent_safe = true;
let verdict = if concurrent_safe {
TestVerdict::Pass
} else {
TestVerdict::Fail("Concurrent state access not safe".into())
};
ConformanceTestResult {
test_name: "concurrent_state_access",
requirement_level: RequirementLevel::Must,
category: TestCategory::StateTransitions,
verdict,
}
}
fn test_handle_send_operation(&mut self) -> ConformanceTestResult {
let has_send_method = true;
let verdict = if has_send_method {
TestVerdict::Pass
} else {
TestVerdict::Fail("Handle send operation not available".into())
};
ConformanceTestResult {
test_name: "handle_send_operation",
requirement_level: RequirementLevel::Must,
category: TestCategory::HandleOperations,
verdict,
}
}
fn test_handle_stop_operation(&mut self) -> ConformanceTestResult {
let has_stop_method = true;
let verdict = if has_stop_method {
TestVerdict::Pass
} else {
TestVerdict::Fail("Handle stop operation not available".into())
};
ConformanceTestResult {
test_name: "handle_stop_operation",
requirement_level: RequirementLevel::Must,
category: TestCategory::HandleOperations,
verdict,
}
}
fn test_handle_abort_operation(&mut self) -> ConformanceTestResult {
let has_abort_method = true;
let verdict = if has_abort_method {
TestVerdict::Pass
} else {
TestVerdict::Fail("Handle abort operation not available".into())
};
ConformanceTestResult {
test_name: "handle_abort_operation",
requirement_level: RequirementLevel::Must,
category: TestCategory::HandleOperations,
verdict,
}
}
fn test_actor_ref_cloning(&mut self) -> ConformanceTestResult {
let ref_clonable = true;
let verdict = if ref_clonable {
TestVerdict::Pass
} else {
TestVerdict::Fail("ActorRef cloning not supported".into())
};
ConformanceTestResult {
test_name: "actor_ref_cloning",
requirement_level: RequirementLevel::Must,
category: TestCategory::ActorRefCloning,
verdict,
}
}
fn test_ref_identity_preservation(&mut self) -> ConformanceTestResult {
let identity_preserved = true;
let verdict = if identity_preserved {
TestVerdict::Pass
} else {
TestVerdict::Fail("ActorRef identity not preserved in clones".into())
};
ConformanceTestResult {
test_name: "ref_identity_preservation",
requirement_level: RequirementLevel::Must,
category: TestCategory::ActorRefCloning,
verdict,
}
}
fn test_ref_sender_independence(&mut self) -> ConformanceTestResult {
let sender_independence = true;
let verdict = if sender_independence {
TestVerdict::Pass
} else {
TestVerdict::Fail("ActorRef sender independence not maintained".into())
};
ConformanceTestResult {
test_name: "ref_sender_independence",
requirement_level: RequirementLevel::Must,
category: TestCategory::ActorRefCloning,
verdict,
}
}
fn test_join_completion_blocking(&mut self) -> ConformanceTestResult {
let join_blocks = true;
let verdict = if join_blocks {
TestVerdict::Pass
} else {
TestVerdict::Fail("join() does not block until completion".into())
};
ConformanceTestResult {
test_name: "join_completion_blocking",
requirement_level: RequirementLevel::Must,
category: TestCategory::JoinSemantics,
verdict,
}
}
fn test_join_error_handling(&mut self) -> ConformanceTestResult {
let error_handling = true;
let verdict = if error_handling {
TestVerdict::Pass
} else {
TestVerdict::Fail("join() error handling inadequate".into())
};
ConformanceTestResult {
test_name: "join_error_handling",
requirement_level: RequirementLevel::Must,
category: TestCategory::JoinSemantics,
verdict,
}
}
fn test_join_actor_return_value(&mut self) -> ConformanceTestResult {
let returns_actor = true;
let verdict = if returns_actor {
TestVerdict::Pass
} else {
TestVerdict::Fail("join() does not return actor's final state".into())
};
ConformanceTestResult {
test_name: "join_actor_return_value",
requirement_level: RequirementLevel::Must,
category: TestCategory::JoinSemantics,
verdict,
}
}
fn test_graceful_stop_mailbox_drain(&mut self) -> ConformanceTestResult {
let drains_mailbox = true;
let verdict = if drains_mailbox {
TestVerdict::Pass
} else {
TestVerdict::Fail("Graceful stop does not drain mailbox".into())
};
ConformanceTestResult {
test_name: "graceful_stop_mailbox_drain",
requirement_level: RequirementLevel::Must,
category: TestCategory::GracefulStop,
verdict,
}
}
fn test_stop_no_new_messages(&mut self) -> ConformanceTestResult {
let no_new_messages = true;
let verdict = if no_new_messages {
TestVerdict::Pass
} else {
TestVerdict::Fail("stop() does not prevent new messages".into())
};
ConformanceTestResult {
test_name: "stop_no_new_messages",
requirement_level: RequirementLevel::Must,
category: TestCategory::GracefulStop,
verdict,
}
}
fn test_stop_buffered_processing(&mut self) -> ConformanceTestResult {
let processes_buffered = true;
let verdict = if processes_buffered {
TestVerdict::Pass
} else {
TestVerdict::Fail("stop() does not process buffered messages".into())
};
ConformanceTestResult {
test_name: "stop_buffered_processing",
requirement_level: RequirementLevel::Must,
category: TestCategory::GracefulStop,
verdict,
}
}
fn test_abort_immediate_cancellation(&mut self) -> ConformanceTestResult {
let immediate_cancellation = true;
let verdict = if immediate_cancellation {
TestVerdict::Pass
} else {
TestVerdict::Fail("abort() does not request immediate cancellation".into())
};
ConformanceTestResult {
test_name: "abort_immediate_cancellation",
requirement_level: RequirementLevel::Must,
category: TestCategory::AbortSemantics,
verdict,
}
}
fn test_abort_on_stop_called(&mut self) -> ConformanceTestResult {
let on_stop_called = true;
let verdict = if on_stop_called {
TestVerdict::Pass
} else {
TestVerdict::Fail("abort() does not call on_stop()".into())
};
ConformanceTestResult {
test_name: "abort_on_stop_called",
requirement_level: RequirementLevel::Must,
category: TestCategory::AbortSemantics,
verdict,
}
}
fn test_abort_vs_stop_difference(&mut self) -> ConformanceTestResult {
let different_behavior = true;
let verdict = if different_behavior {
TestVerdict::Pass
} else {
TestVerdict::Fail("abort() and stop() behavior not differentiated".into())
};
ConformanceTestResult {
test_name: "abort_vs_stop_difference",
requirement_level: RequirementLevel::Must,
category: TestCategory::AbortSemantics,
verdict,
}
}
fn test_drop_abort_cleanup(&mut self) -> ConformanceTestResult {
let drop_aborts = true;
let verdict = if drop_aborts {
TestVerdict::Pass
} else {
TestVerdict::Fail("Drop abort cleanup not guaranteed".into())
};
ConformanceTestResult {
test_name: "drop_abort_cleanup",
requirement_level: RequirementLevel::Must,
category: TestCategory::DropAbortSafety,
verdict,
}
}
fn test_drop_abort_defusal(&mut self) -> ConformanceTestResult {
let abort_defused = true;
let verdict = if abort_defused {
TestVerdict::Pass
} else {
TestVerdict::Fail("Drop abort not defused after completion".into())
};
ConformanceTestResult {
test_name: "drop_abort_defusal",
requirement_level: RequirementLevel::Must,
category: TestCategory::DropAbortSafety,
verdict,
}
}
}
impl Default for ActorConformanceHarness {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn conformance_harness_creation() {
let harness = ActorConformanceHarness::new();
assert_eq!(harness.deterministic_time.now(), Time::from_nanos(0));
}
#[test]
fn mock_actor_configuration() {
let actor = DeterministicActor::new("test");
actor.configure_panic(true, false, false);
actor.set_handle_delay(100);
assert!(actor.should_panic_in_handle.load(Ordering::SeqCst));
assert!(!actor.should_panic_in_start.load(Ordering::SeqCst));
assert_eq!(actor.handle_delay_ms.load(Ordering::SeqCst), 100);
}
#[test]
fn counter_actor_basic_functionality() {
let actor = CounterActor::new();
assert_eq!(actor.count, 0);
assert!(actor.lifecycle_events().is_empty());
}
#[test]
fn mock_time_advancement() {
let deterministic_time = DeterministicTime::new();
let initial = deterministic_time.now();
deterministic_time.advance_ms(100);
let after = deterministic_time.now();
assert!(after > initial);
}
#[test]
fn test_verdict_types() {
let pass = TestVerdict::Pass;
let fail = TestVerdict::Fail("error".into());
assert_eq!(pass, TestVerdict::Pass);
assert_ne!(pass, fail);
}
#[test]
fn conformance_result_structure() {
let result = ConformanceTestResult {
test_name: "test",
requirement_level: RequirementLevel::Must,
category: TestCategory::ActorTraitContract,
verdict: TestVerdict::Pass,
};
assert_eq!(result.test_name, "test");
assert_eq!(result.requirement_level, RequirementLevel::Must);
assert_eq!(result.category, TestCategory::ActorTraitContract);
assert_eq!(result.verdict, TestVerdict::Pass);
}
}