#![allow(missing_docs)]
#![cfg(not(target_arch = "wasm32"))]
#![allow(
clippy::cast_possible_wrap,
clippy::as_conversions,
clippy::needless_collect,
clippy::let_underscore_must_use,
clippy::useless_vec
)]
#[allow(dead_code, unreachable_pub)]
#[path = "support/mod.rs"]
mod test_support;
use std::collections::BTreeMap;
use proptest::prelude::*;
use proptest::strategy::ValueTree;
use proptest::test_runner::{Config, RngAlgorithm, TestRng, TestRunner};
use telltale_machine::buffer::{BackpressurePolicy, BoundedBuffer, BufferConfig, BufferMode};
use telltale_machine::coroutine::Value;
use telltale_machine::{ObsEvent, ProtocolMachine, ProtocolMachineConfig};
use telltale_types::{GlobalType, Label};
use test_support::{
code_image_from_global, well_formed_global_strategy, FailingHandler, PassthroughHandler, SEED,
};
fn make_runner(cases: u32) -> TestRunner {
TestRunner::new_with_rng(
Config {
cases,
..Config::default()
},
TestRng::from_seed(RngAlgorithm::ChaCha, &SEED),
)
}
#[test]
fn fuzz_random_protocol_compile_execute() {
let mut runner = make_runner(200);
let strategy = well_formed_global_strategy(3);
for _ in 0..200 {
let tree = strategy.new_tree(&mut runner).unwrap();
let global = tree.current();
if matches!(global, GlobalType::End | GlobalType::Mu { .. }) {
continue;
}
let image = match code_image_from_global(&global) {
Some(img) => img,
None => continue,
};
let mut machine = ProtocolMachine::new(ProtocolMachineConfig::default());
if machine.load_choreography(&image).is_err() {
continue;
}
let handler = PassthroughHandler;
let _ = machine.run(&handler, 1000);
let faults: Vec<_> = machine
.trace()
.iter()
.filter(|e| matches!(e, ObsEvent::Faulted { .. }))
.collect();
assert!(faults.is_empty(), "faults for {global:?}: {faults:?}");
let mut sent_by_edge: BTreeMap<(String, String), Vec<String>> = BTreeMap::new();
let mut recv_by_edge: BTreeMap<(String, String), Vec<String>> = BTreeMap::new();
for event in machine.trace() {
match event {
ObsEvent::Sent {
from, to, label, ..
} => {
sent_by_edge
.entry((from.clone(), to.clone()))
.or_default()
.push(label.clone());
}
ObsEvent::Received {
from, to, label, ..
} => {
recv_by_edge
.entry((from.clone(), to.clone()))
.or_default()
.push(label.clone());
}
_ => {}
}
}
for (edge, recvs) in &recv_by_edge {
if let Some(sents) = sent_by_edge.get(edge) {
assert!(recvs.len() <= sents.len());
for (i, r) in recvs.iter().enumerate() {
assert_eq!(r, &sents[i], "FIFO violated on edge {edge:?} at {i}");
}
} else {
panic!("received on edge {edge:?} without sends");
}
}
}
}
#[test]
fn fuzz_random_buffer_operations() {
let mut runner = make_runner(500);
let op_strategy = prop_oneof![Just(true), Just(false)]; let len_strategy = 1..200usize;
for _ in 0..500 {
let len_tree = len_strategy.new_tree(&mut runner).unwrap();
let n = len_tree.current();
let config = BufferConfig {
mode: BufferMode::Fifo,
initial_capacity: n + 1,
policy: BackpressurePolicy::Block,
};
let mut buf = BoundedBuffer::new(&config);
let mut expected: Vec<u64> = Vec::new();
let mut next_val = 0u64;
for _ in 0..n {
let op_tree = op_strategy.new_tree(&mut runner).unwrap();
let enqueue = op_tree.current();
if enqueue {
buf.enqueue(Value::Nat(next_val));
expected.push(next_val);
next_val += 1;
} else if !expected.is_empty() {
let val = buf.dequeue();
let exp = expected.remove(0);
assert_eq!(val, Some(Value::Nat(exp)));
}
assert_eq!(buf.len(), expected.len());
}
}
}
#[test]
fn fuzz_multi_session_interleave() {
let mut runner = make_runner(100);
let strategy = well_formed_global_strategy(2);
for _ in 0..100 {
let tree1 = strategy.new_tree(&mut runner).unwrap();
let tree2 = strategy.new_tree(&mut runner).unwrap();
let g1 = tree1.current();
let g2 = tree2.current();
if matches!(g1, GlobalType::End | GlobalType::Mu { .. })
|| matches!(g2, GlobalType::End | GlobalType::Mu { .. })
{
continue;
}
let img1 = match code_image_from_global(&g1) {
Some(i) => i,
None => continue,
};
let img2 = match code_image_from_global(&g2) {
Some(i) => i,
None => continue,
};
let mut machine = ProtocolMachine::new(ProtocolMachineConfig::default());
let sid1 = match machine.load_choreography(&img1) {
Ok(s) => s,
Err(_) => continue,
};
let sid2 = match machine.load_choreography(&img2) {
Ok(s) => s,
Err(_) => continue,
};
let handler = PassthroughHandler;
let _ = machine.run(&handler, 1000);
let faults: Vec<_> = machine
.trace()
.iter()
.filter(|e| matches!(e, ObsEvent::Faulted { .. }))
.collect();
assert!(faults.is_empty(), "faults in multi-session: {faults:?}");
for event in machine.trace() {
match event {
ObsEvent::Sent { session, .. } | ObsEvent::Received { session, .. } => {
assert!(
*session == sid1 || *session == sid2,
"event from unknown session"
);
}
_ => {}
}
}
}
}
#[test]
fn fuzz_recursive_protocols_bounded() {
let pairs = [("A", "B"), ("B", "A")];
for i in 0..100 {
let (s, r) = pairs[i % pairs.len()];
let global = GlobalType::mu(
"t",
GlobalType::send(s, r, Label::new("msg"), GlobalType::var("t")),
);
let image = match code_image_from_global(&global) {
Some(i) => i,
None => continue,
};
let mut machine = ProtocolMachine::new(ProtocolMachineConfig::default());
if machine.load_choreography(&image).is_err() {
continue;
}
let handler = PassthroughHandler;
let _ = machine.run(&handler, 500);
let faults: Vec<_> = machine
.trace()
.iter()
.filter(|e| matches!(e, ObsEvent::Faulted { .. }))
.collect();
assert!(
faults.is_empty(),
"faults in recursive protocol: {faults:?}"
);
}
}
#[test]
fn fuzz_handler_errors_dont_panic() {
let mut runner = make_runner(100);
let strategy = well_formed_global_strategy(2);
for _ in 0..100 {
let tree = strategy.new_tree(&mut runner).unwrap();
let global = tree.current();
if matches!(global, GlobalType::End) {
continue;
}
let image = match code_image_from_global(&global) {
Some(i) => i,
None => continue,
};
let mut machine = ProtocolMachine::new(ProtocolMachineConfig::default());
if machine.load_choreography(&image).is_err() {
continue;
}
let handler = FailingHandler::new("deliberate test error");
let _ = machine.run(&handler, 100);
}
}