#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use quote::{format_ident, quote};
use std::collections::HashMap;
use telltale_runtime::ast::{
protocol::Condition, Annotations, Branch, Choreography, LocalType, MessageType, NonEmptyVec,
Protocol, Role,
};
use telltale_runtime::compiler::projection::project;
fn role(name: &str) -> Role {
Role::new(format_ident!("{}", name)).unwrap()
}
#[test]
fn test_local_choice_without_send() {
let alice = role("Alice");
let choreo = Choreography {
name: format_ident!("LocalChoiceTest"),
namespace: None,
roles: vec![alice.clone()],
protocol: Protocol::Choice {
role: alice.clone(),
branches: NonEmptyVec::from_head_tail(
Branch {
label: format_ident!("option1"),
guard: None,
protocol: Protocol::End, },
vec![Branch {
label: format_ident!("option2"),
guard: None,
protocol: Protocol::End,
}],
),
annotations: Annotations::new(),
},
attrs: HashMap::new(),
};
let projected = project(&choreo, &alice).unwrap();
match projected {
LocalType::LocalChoice { branches } => {
assert_eq!(branches.len(), 2, "Should have both branches");
assert_eq!(branches[0].0.to_string(), "option1");
assert_eq!(branches[1].0.to_string(), "option2");
}
_ => panic!("Expected LocalChoice, got: {projected:?}"),
}
}
#[test]
fn test_loop_with_condition() {
let alice = role("Alice");
let bob = role("Bob");
let choreo = Choreography {
name: format_ident!("LoopConditionTest"),
namespace: None,
roles: vec![alice.clone(), bob.clone()],
protocol: Protocol::Loop {
condition: Some(Condition::Count(5)),
body: Box::new(Protocol::Send {
from: alice.clone(),
to: bob.clone(),
message: MessageType {
name: format_ident!("Data"),
type_annotation: None,
payload: Some(quote! { String }),
},
continuation: Box::new(Protocol::End),
annotations: Annotations::new(),
from_annotations: Annotations::new(),
to_annotations: Annotations::new(),
}),
},
attrs: HashMap::new(),
};
let alice_proj = project(&choreo, &alice).unwrap();
match alice_proj {
LocalType::Loop { condition, body } => {
assert!(condition.is_some(), "Condition should be preserved");
if let Some(Condition::Count(n)) = condition {
assert_eq!(n, 5, "Count should be 5");
} else {
panic!("Expected Count condition");
}
assert!(matches!(*body, LocalType::Send { .. }));
}
_ => panic!("Expected Loop, got: {alice_proj:?}"),
}
}
#[test]
fn test_parallel_no_conflict() {
let alice = role("Alice");
let bob = role("Bob");
let charlie = role("Charlie");
let choreo = Choreography {
name: format_ident!("ParallelNoConflict"),
namespace: None,
roles: vec![alice.clone(), bob.clone(), charlie.clone()],
protocol: Protocol::Parallel {
protocols: NonEmptyVec::from_head_tail(
Protocol::Send {
from: alice.clone(),
to: bob.clone(),
message: MessageType {
name: format_ident!("Msg1"),
type_annotation: None,
payload: Some(quote! { String }),
},
continuation: Box::new(Protocol::End),
annotations: Annotations::new(),
from_annotations: Annotations::new(),
to_annotations: Annotations::new(),
},
vec![Protocol::Send {
from: alice.clone(),
to: charlie.clone(),
message: MessageType {
name: format_ident!("Msg2"),
type_annotation: None,
payload: Some(quote! { i32 }),
},
continuation: Box::new(Protocol::End),
annotations: Annotations::new(),
from_annotations: Annotations::new(),
to_annotations: Annotations::new(),
}],
),
},
attrs: HashMap::new(),
};
let alice_proj = project(&choreo, &alice);
assert!(alice_proj.is_ok(), "Parallel merge should succeed");
match alice_proj.unwrap() {
LocalType::Send { to, .. } => {
assert!(to == bob || to == charlie);
}
_ => panic!("Expected Send"),
}
}
#[test]
fn test_parallel_with_conflict() {
let alice = role("Alice");
let bob = role("Bob");
let choreo = Choreography {
name: format_ident!("ParallelConflict"),
namespace: None,
roles: vec![alice.clone(), bob.clone()],
protocol: Protocol::Parallel {
protocols: NonEmptyVec::from_head_tail(
Protocol::Send {
from: alice.clone(),
to: bob.clone(),
message: MessageType {
name: format_ident!("Msg1"),
type_annotation: None,
payload: Some(quote! { String }),
},
continuation: Box::new(Protocol::End),
annotations: Annotations::new(),
from_annotations: Annotations::new(),
to_annotations: Annotations::new(),
},
vec![Protocol::Send {
from: alice.clone(),
to: bob.clone(), message: MessageType {
name: format_ident!("Msg2"),
type_annotation: None,
payload: Some(quote! { i32 }),
},
continuation: Box::new(Protocol::End),
annotations: Annotations::new(),
from_annotations: Annotations::new(),
to_annotations: Annotations::new(),
}],
),
},
attrs: HashMap::new(),
};
let alice_proj = project(&choreo, &alice);
assert!(alice_proj.is_err(), "Should detect parallel conflict");
}
#[test]
fn test_mixed_choice_communicated_vs_local() {
let alice = role("Alice");
let bob = role("Bob");
let choreo = Choreography {
name: format_ident!("CommunicatedChoice"),
namespace: None,
roles: vec![alice.clone(), bob.clone()],
protocol: Protocol::Choice {
role: alice.clone(),
branches: NonEmptyVec::from_head_tail(
Branch {
label: format_ident!("yes"),
guard: None,
protocol: Protocol::Send {
from: alice.clone(),
to: bob.clone(),
message: MessageType {
name: format_ident!("Data"),
type_annotation: None,
payload: Some(quote! { String }),
},
continuation: Box::new(Protocol::End),
annotations: Annotations::new(),
from_annotations: Annotations::new(),
to_annotations: Annotations::new(),
},
},
vec![Branch {
label: format_ident!("no"),
guard: None,
protocol: Protocol::Send {
from: alice.clone(),
to: bob.clone(),
message: MessageType {
name: format_ident!("NoData"),
type_annotation: None,
payload: Some(quote! { () }),
},
continuation: Box::new(Protocol::End),
annotations: Annotations::new(),
from_annotations: Annotations::new(),
to_annotations: Annotations::new(),
},
}],
),
annotations: Annotations::new(),
},
attrs: HashMap::new(),
};
let alice_proj = project(&choreo, &alice).unwrap();
match alice_proj {
LocalType::Select { to, branches } => {
assert_eq!(to, bob, "Select should be to Bob");
assert_eq!(branches.len(), 2, "Should have both branches");
}
_ => panic!("Expected Select, got: {alice_proj:?}"),
}
let bob_proj = project(&choreo, &bob).unwrap();
match bob_proj {
LocalType::Branch { from, branches } => {
assert_eq!(from, alice, "Branch should be from Alice");
assert_eq!(branches.len(), 2, "Should have both branches");
}
_ => panic!("Expected Branch, got: {bob_proj:?}"),
}
}
#[test]
fn test_loop_without_condition() {
let alice = role("Alice");
let choreo = Choreography {
name: format_ident!("LoopNoCondition"),
namespace: None,
roles: vec![alice.clone()],
protocol: Protocol::Loop {
condition: None,
body: Box::new(Protocol::End),
},
attrs: HashMap::new(),
};
let projected = project(&choreo, &alice).unwrap();
assert_eq!(projected, LocalType::End);
}