#![allow(clippy::needless_return)]
use std::collections::BTreeMap;
use axon_frontend::multiparty::GlobalType;
use axon_frontend::session::{Polarity, SessionType};
struct Lcg(u64);
impl Lcg {
fn new(seed: u64) -> Self {
Self(seed.max(1))
}
fn next_u64(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.0
}
fn next_in(&mut self, lo: u64, hi: u64) -> u64 {
debug_assert!(hi >= lo);
let span = hi - lo + 1;
lo + (self.next_u64() % span)
}
fn next_bool(&mut self) -> bool {
self.next_u64() & 1 == 1
}
}
const SEEDS: &[u64] = &[
0x1, 0x3, 0x7, 0xd, 0xf, 0x11, 0x13, 0x17, 0x1d, 0x1f, 0x25, 0x29, 0x2b, 0x2f, 0x35, 0x3b,
0x3d, 0x43, 0x47, 0x49, 0x4f, 0x53, 0x59, 0x61, 0x65, 0x67, 0x6b, 0x6d, 0x71, 0x7f, 0x83, 0x89,
0x8b, 0x95, 0x97, 0x9d, 0xa3, 0xa7, 0xad, 0xb3, 0xb5, 0xbf, 0xc1, 0xc5, 0xc7, 0xd3, 0xdf, 0xe3,
0xe5, 0xe9,
];
const PAYLOADS: &[&str] = &["A", "B", "Msg", "Token", "T", "Q"];
const LABELS: &[&str] = &["ask", "done", "more", "ok", "cancel"];
fn random_session(rng: &mut Lcg, depth: u8) -> SessionType {
random_session_in(rng, depth, &[])
}
fn random_session_in(rng: &mut Lcg, depth: u8, in_scope: &[String]) -> SessionType {
if depth == 0 {
if !in_scope.is_empty() && rng.next_bool() {
let idx = rng.next_in(0, in_scope.len() as u64 - 1) as usize;
return SessionType::var(in_scope[idx].clone());
}
return SessionType::End;
}
match rng.next_in(0, 5) {
0 => SessionType::End,
1 => {
let payload = PAYLOADS[rng.next_in(0, PAYLOADS.len() as u64 - 1) as usize];
SessionType::send(payload, random_session_in(rng, depth - 1, in_scope))
}
2 => {
let payload = PAYLOADS[rng.next_in(0, PAYLOADS.len() as u64 - 1) as usize];
SessionType::recv(payload, random_session_in(rng, depth - 1, in_scope))
}
3 => random_choice(rng, depth, in_scope, true),
4 => random_choice(rng, depth, in_scope, false),
_ => {
let var = format!("X{}", in_scope.len());
let mut scope = in_scope.to_vec();
scope.push(var.clone());
let body = random_session_in(rng, depth - 1, &scope);
SessionType::rec(var, body)
}
}
}
fn random_choice(rng: &mut Lcg, depth: u8, in_scope: &[String], internal: bool) -> SessionType {
let arm_count = rng.next_in(1, 3) as usize;
let mut arms: BTreeMap<String, SessionType> = BTreeMap::new();
for _ in 0..arm_count {
let label = LABELS[rng.next_in(0, LABELS.len() as u64 - 1) as usize].to_string();
arms.entry(label)
.or_insert_with(|| random_session_in(rng, depth.saturating_sub(1), in_scope));
}
if internal {
SessionType::Select(arms)
} else {
SessionType::Branch(arms)
}
}
const ROLES: &[&str] = &["Alice", "Bob", "Carol", "Dave"];
fn random_global(rng: &mut Lcg, depth: u8) -> GlobalType {
random_global_in(rng, depth, &[])
}
fn random_global_in(rng: &mut Lcg, depth: u8, in_scope: &[String]) -> GlobalType {
if depth == 0 {
if !in_scope.is_empty() && rng.next_bool() {
let idx = rng.next_in(0, in_scope.len() as u64 - 1) as usize;
return GlobalType::var(in_scope[idx].clone());
}
return GlobalType::End;
}
match rng.next_in(0, 3) {
0 => GlobalType::End,
1 => {
let (from, to) = distinct_roles(rng);
let payload = PAYLOADS[rng.next_in(0, PAYLOADS.len() as u64 - 1) as usize];
GlobalType::message(
from,
to,
payload,
random_global_in(rng, depth - 1, in_scope),
)
}
2 => {
let (from, to) = distinct_roles(rng);
let arm_count = rng.next_in(1, 3) as usize;
let mut arms: BTreeMap<String, GlobalType> = BTreeMap::new();
for _ in 0..arm_count {
let label = LABELS[rng.next_in(0, LABELS.len() as u64 - 1) as usize].to_string();
arms.entry(label)
.or_insert_with(|| random_global_in(rng, depth.saturating_sub(1), in_scope));
}
GlobalType::choice(from, to, arms)
}
_ => {
let var = format!("Y{}", in_scope.len());
let mut scope = in_scope.to_vec();
scope.push(var.clone());
let body = random_global_in(rng, depth - 1, &scope);
GlobalType::rec(var, body)
}
}
}
fn distinct_roles(rng: &mut Lcg) -> (String, String) {
loop {
let a = ROLES[rng.next_in(0, ROLES.len() as u64 - 1) as usize].to_string();
let b = ROLES[rng.next_in(0, ROLES.len() as u64 - 1) as usize].to_string();
if a != b {
return (a, b);
}
}
}
#[test]
fn duality_is_involutive_for_random_closed_session_types() {
for &seed in SEEDS {
let mut rng = Lcg::new(seed);
for _ in 0..20 {
let depth = rng.next_in(1, 4) as u8;
let s = random_session(&mut rng, depth);
let twice = s.dual().dual();
assert!(
twice.equiv(&s),
"(S⊥)⊥ ≢ S at seed=0x{seed:x} for: {s}"
);
}
}
}
#[test]
fn connection_law_holds_for_dual_pairs_in_both_directions() {
for &seed in SEEDS {
let mut rng = Lcg::new(seed);
for _ in 0..20 {
let depth = rng.next_in(1, 3) as u8;
let s = random_session(&mut rng, depth);
let t = s.dual();
assert!(
s.is_dual_to(&t),
"S.is_dual_to(S⊥) must hold at seed=0x{seed:x} for: {s}"
);
assert!(
t.is_dual_to(&s),
"S⊥.is_dual_to(S) must hold at seed=0x{seed:x} for: {s}"
);
}
}
}
#[test]
fn credit_analyse_is_total_over_random_sessions_and_budgets() {
for &seed in SEEDS {
let mut rng = Lcg::new(seed);
for _ in 0..20 {
let depth = rng.next_in(1, 3) as u8;
let s = random_session(&mut rng, depth);
let budget = rng.next_in(0, 8);
let _verdict = s.credit_analyse(budget);
}
}
}
#[test]
fn polarity_predicate_obeys_dual_symmetry() {
for &seed in SEEDS {
let mut rng = Lcg::new(seed);
for _ in 0..20 {
let depth = rng.next_in(1, 3) as u8;
let s = random_session(&mut rng, depth);
assert_eq!(
s.projects_to_sse(),
s.dual().projects_to_sse_consumer(),
"polarity symmetry violated at seed=0x{seed:x} for: {s}"
);
assert_eq!(
s.has_polarity(Polarity::Producer),
s.projects_to_sse()
);
assert_eq!(
s.has_polarity(Polarity::Consumer),
s.projects_to_sse_consumer()
);
}
}
}
#[test]
fn multiparty_projection_is_total_over_random_closed_global_types() {
for &seed in SEEDS {
let mut rng = Lcg::new(seed);
for _ in 0..20 {
let depth = rng.next_in(1, 3) as u8;
let g = random_global(&mut rng, depth);
let role_set = g.roles();
match g.project_all() {
Ok(projection) => {
assert_eq!(
projection.len(),
role_set.len(),
"project_all must yield one binding per role at seed=0x{seed:x}: {g:?}"
);
for r in &role_set {
assert!(
projection.contains_key(r),
"role {r} missing from projection at seed=0x{seed:x}"
);
}
}
Err(_e) => {
}
}
}
}
}
#[test]
fn projection_for_a_dual_pair_realises_the_connection_law() {
for &seed in SEEDS {
let mut rng = Lcg::new(seed);
for _ in 0..10 {
let (r1, r2) = distinct_roles(&mut rng);
let t1 = PAYLOADS[rng.next_in(0, PAYLOADS.len() as u64 - 1) as usize];
let t2 = PAYLOADS[rng.next_in(0, PAYLOADS.len() as u64 - 1) as usize];
let g = GlobalType::message(
r1.as_str(),
r2.as_str(),
t1,
GlobalType::message(r2.as_str(), r1.as_str(), t2, GlobalType::End),
);
let proj = g.project_all().expect("linear two-role protocol is realizable");
let role1 = axon_frontend::multiparty::Role::new(r1);
let role2 = axon_frontend::multiparty::Role::new(r2);
let p1 = &proj[&role1];
let p2 = &proj[&role2];
assert!(
p1.is_dual_to(p2),
"two-role projection breaks the connection law at seed=0x{seed:x}"
);
}
}
}