#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use telltale_runtime::ast::{LocalType, Protocol};
use telltale_runtime::compiler::parser::{parse_choreography_str, ParseError};
use telltale_runtime::compiler::projection::project;
#[test]
fn test_parse_simple_protocol() {
let input = r"
protocol PingPong =
roles Alice, Bob
Alice -> Bob : Ping
Bob -> Alice : Pong
";
let result = parse_choreography_str(input);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let choreo = result.unwrap();
assert_eq!(choreo.name.to_string(), "PingPong");
assert_eq!(choreo.roles.len(), 2);
}
#[test]
fn test_parse_canonical_timeout_surface() {
let input = r#"
protocol TimedRequest =
roles Client, Server
timeout 5s Client at
Server -> Client : Response
on timeout =>
Client -> Server : Cancel
"#;
let choreo = parse_choreography_str(input).expect("canonical timeout should parse");
assert!(matches!(choreo.protocol, Protocol::Timeout { .. }));
}
#[test]
fn test_reject_multiline_type_equals_surface() {
let input = r#"
type AcceptError
= NotFound
protocol Demo =
roles A, B
A -> B : Ping
"#;
parse_choreography_str(input).expect_err("multiline type equals should fail");
}
#[test]
fn test_reject_multiline_protocol_equals_surface() {
let input = r#"
protocol Demo
=
roles A, B
A -> B : Ping
"#;
parse_choreography_str(input).expect_err("multiline protocol equals should fail");
}
#[test]
fn test_projection_projects_authority_case_constructs() {
let input = r#"
effect Runtime
authoritative ready : Session -> Result CommitError ReadyWitness
{
class : authoritative
progress : may_block
region : fragment
agreement_use : required
reentrancy : reject_same_fragment
}
protocol CommitFlow uses Runtime =
roles Coordinator, Worker
authoritative let readiness = check Runtime.ready(session)
case readiness of
| Ok(witness) =>
Coordinator -> Worker : Commit(witness)
| Err(reason) =>
Coordinator -> Worker : Retry(reason)
"#;
let choreo = parse_choreography_str(input).expect("parse should succeed");
let coordinator = choreo
.roles
.iter()
.find(|role| role.name() == "Coordinator")
.expect("coordinator role");
let worker = choreo
.roles
.iter()
.find(|role| role.name() == "Worker")
.expect("worker role");
let coordinator_projection =
project(&choreo, coordinator).expect("case projection should succeed");
let worker_projection = project(&choreo, worker).expect("case projection should succeed");
assert!(matches!(
coordinator_projection,
LocalType::LocalChoice { ref branches }
if branches.iter().map(|(label, _)| label.to_string()).collect::<Vec<_>>()
== vec!["Ok", "Err"]
));
assert!(matches!(
worker_projection,
LocalType::Branch { ref from, ref branches }
if from.name() == "Coordinator"
&& branches.iter().map(|(label, _)| label.to_string()).collect::<Vec<_>>()
== vec!["Err", "Ok"]
));
}
#[test]
fn test_projection_projects_timeout_constructs() {
let input = r#"
protocol TimeoutFlow =
roles Coordinator, Worker
timeout 5s Coordinator at
Worker -> Coordinator : Ready
on timeout =>
Coordinator -> Worker : Cancel
"#;
let choreo = parse_choreography_str(input).expect("parse should succeed");
let coordinator = choreo
.roles
.iter()
.find(|role| role.name() == "Coordinator")
.expect("coordinator role");
let worker = choreo
.roles
.iter()
.find(|role| role.name() == "Worker")
.expect("worker role");
let coordinator_projection =
project(&choreo, coordinator).expect("timeout projection should succeed");
let worker_projection = project(&choreo, worker).expect("timeout projection should succeed");
assert!(matches!(
coordinator_projection,
LocalType::Timeout {
body,
on_timeout,
on_cancel: None,
..
} if matches!(
(body.as_ref(), on_timeout.as_ref()),
(
LocalType::Receive { from, .. },
LocalType::Send { to, .. },
) if from.name() == "Worker" && to.name() == "Worker"
)
));
assert!(matches!(
worker_projection,
LocalType::Timeout {
body,
on_timeout,
on_cancel: None,
..
} if matches!(
(body.as_ref(), on_timeout.as_ref()),
(
LocalType::Send { to, .. },
LocalType::Receive { from, .. },
) if to.name() == "Coordinator" && from.name() == "Coordinator"
)
));
}
#[test]
fn test_projection_projects_evidence_binding_guards() {
let input = r#"
effect Runtime
authoritative ready : Session -> Result CommitError ReadyWitness
{
class : authoritative
progress : may_block
region : fragment
agreement_use : required
reentrancy : reject_same_fragment
}
protocol GuardedChoice uses Runtime =
roles Coordinator, Worker
choice Coordinator at
| Commit when check Runtime.ready(session) yields witness =>
Coordinator -> Worker : Commit(witness)
| Abort =>
Coordinator -> Worker : Abort
"#;
let choreo = parse_choreography_str(input).expect("parse should succeed");
let worker = choreo
.roles
.iter()
.find(|role| role.name() == "Worker")
.expect("worker role");
let projection = project(&choreo, worker).expect("guarded choice should project directly");
assert!(matches!(
projection,
LocalType::Branch { ref from, ref branches }
if from.name() == "Coordinator" && branches.len() == 2
));
}
#[test]
fn test_projection_projects_authority_wrappers_through_continuation() {
let input = r#"
protocol WrapperFlow =
roles Coordinator, Worker
let receipt = transfer Session from Coordinator to Worker
publish receipt as DelegationRecorded
materialize delegationProof from DelegationRecorded
handoff acceptInvite to Worker with receipt
dependent work SyncMembership(channel) required for acceptInvite
Coordinator -> Worker : Commit
"#;
let choreo = parse_choreography_str(input).expect("parse should succeed");
let coordinator = choreo
.roles
.iter()
.find(|role| role.name() == "Coordinator")
.expect("coordinator role");
let projection = project(&choreo, coordinator).expect("wrapper projection should succeed");
assert!(matches!(
projection,
LocalType::Send { ref to, .. } if to.name() == "Worker"
));
}
#[test]
fn test_parse_three_party_protocol() {
let input = r"
protocol ThreeParty =
roles Alice, Bob, Carol
Alice -> Bob : Hello
Bob -> Carol : Forward
Carol -> Alice : Response
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
let choreo = result.unwrap();
assert_eq!(choreo.roles.len(), 3);
}
#[test]
fn test_parse_broadcast() {
let input = r"
protocol Broadcast =
roles Leader, Worker1, Worker2
Leader ->* : Start
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse broadcast: {:?}",
result.err()
);
let choreo = result.unwrap();
assert_eq!(choreo.roles.len(), 3);
use telltale_runtime::ast::Protocol;
match &choreo.protocol {
Protocol::Broadcast {
from,
to_all,
message,
..
} => {
assert_eq!(from.name().to_string(), "Leader");
assert_eq!(message.name.to_string(), "Start");
assert_eq!(
to_all.len(),
2,
"Broadcast should target all roles except sender"
);
let recipient_names: Vec<String> =
to_all.iter().map(|r| r.name().to_string()).collect();
assert!(recipient_names.contains(&"Worker1".to_string()));
assert!(recipient_names.contains(&"Worker2".to_string()));
assert!(
!recipient_names.contains(&"Leader".to_string()),
"Sender should not be in to_all"
);
}
_ => panic!("Expected Protocol::Broadcast, got {:?}", choreo.protocol),
}
}
#[test]
fn test_parse_choice_two_branches() {
let input = r"
protocol Choice =
roles A, B
A -> B : Propose
choice B at
| accept =>
B -> A : Accept
| reject =>
B -> A : Reject
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_choice_three_branches() {
let input = r"
protocol ThreeWayChoice =
roles Client, Server
choice Client at
| get =>
Client -> Server : Get
| post =>
Client -> Server : Post
| delete =>
Client -> Server : Delete
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_nested_choice() {
let input = r"
protocol NestedChoice =
roles A, B, C
choice A at
| path1 =>
A -> B : First
choice B at
| inner1 =>
B -> C : InnerA
| inner2 =>
B -> C : InnerB
| path2 =>
A -> C : Second
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_loop_with_count() {
let input = r"
protocol LoopCount =
roles Client, Server
loop repeat 5
Client -> Server : Request
Server -> Client : Response
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_loop_with_role_decides() {
let input = r"
protocol LoopRoleDecides =
roles Client, Server
loop decide by Client
Client -> Server : Request
Server -> Client : Response
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_loop_with_custom_condition() {
let input = r#"
protocol LoopCustom =
roles A, B
loop while "has_more_data"
A -> B : Data
"#;
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_loop_without_condition() {
let input = r"
protocol InfiniteLoop =
roles A, B
loop forever
A -> B : Tick
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_parallel() {
let input = r"
protocol Parallel =
roles A, B, C, D
par
| A -> B : Msg1
| C -> D : Msg2
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_recursive() {
let input = r"
protocol Recursive =
roles A, B
rec Loop
A -> B : Data
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_recursive_with_continue() {
let input = r"
protocol Recursive =
roles A, B
rec Loop
A -> B : Ping
B -> A : Pong
continue Loop
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
use telltale_runtime::ast::Protocol;
let choreo = result.unwrap();
match &choreo.protocol {
Protocol::Rec { label, body } => {
assert_eq!(label.to_string(), "Loop");
match body.as_ref() {
Protocol::Send { continuation, .. } => match continuation.as_ref() {
Protocol::Send { continuation, .. } => match continuation.as_ref() {
Protocol::Var(var_label) => {
assert_eq!(var_label.to_string(), "Loop");
}
other => panic!("Expected Var, got {:?}", other),
},
other => panic!("Expected Send, got {:?}", other),
},
other => panic!("Expected Send, got {:?}", other),
}
}
other => panic!("Expected Rec, got {:?}", other),
}
}
#[test]
fn test_parse_complex_protocol() {
let input = r"
protocol ComplexProtocol =
roles Buyer, Seller, Shipper
Buyer -> Seller : Inquiry
Seller -> Buyer : Quote
choice Buyer at
| order =>
Buyer -> Seller : Order
Seller -> Shipper : ShipRequest
Shipper -> Buyer : Tracking
loop decide by Buyer
Buyer -> Shipper : StatusCheck
Shipper -> Buyer : StatusUpdate
Shipper -> Buyer : Delivered
Buyer -> Seller : Confirmation
| cancel =>
Buyer -> Seller : Cancel
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse complex protocol: {:?}",
result.err()
);
}
#[test]
fn test_parse_with_payload() {
let input = r"
protocol WithPayload =
roles A, B
A -> B : Message(data : String)
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_error_undefined_role_in_send() {
let input = r"
protocol Invalid =
roles Alice
Alice -> Bob : Hello
";
let result = parse_choreography_str(input);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, ParseError::UndefinedRole { ref role, .. } if role == "Bob"));
let err_str = err.to_string();
assert!(err_str.contains("Undefined role"));
assert!(err_str.contains("Bob"));
assert!(err_str.contains("-->")); }
#[test]
fn test_error_undefined_role_in_choice() {
let input = r"
protocol Invalid =
roles Alice
choice Bob at
| opt =>
Alice -> Alice : Self
";
let result = parse_choreography_str(input);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, ParseError::UndefinedRole { ref role, .. } if role == "Bob"));
let err_str = err.to_string();
assert!(err_str.contains("Undefined role"));
assert!(err_str.contains("Bob"));
}
#[test]
fn test_error_undefined_role_in_loop_decides() {
let input = r"
protocol Invalid =
roles Alice
loop decide by Bob
Alice -> Alice : Msg
";
let result = parse_choreography_str(input);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, ParseError::UndefinedRole { ref role, .. } if role == "Bob"));
let err_str = err.to_string();
assert!(err_str.contains("Undefined role"));
assert!(err_str.contains("Bob"));
}
#[test]
fn test_error_duplicate_role() {
let input = r"
protocol Invalid =
roles Alice, Bob, Alice
Alice -> Bob : Hello
";
let result = parse_choreography_str(input);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, ParseError::DuplicateRole { ref role, .. } if role == "Alice"));
let err_str = err.to_string();
assert!(err_str.contains("Duplicate role"));
assert!(err_str.contains("Alice"));
}
#[test]
fn test_error_no_roles() {
let input = r"
protocol Invalid =
Alice -> Bob : Hello
";
let result = parse_choreography_str(input);
assert!(result.is_err());
}
#[test]
fn test_error_invalid_syntax() {
let input = r"
protocol Invalid =
roles A, B
A -> -> B : Hello
";
let result = parse_choreography_str(input);
assert!(result.is_err());
}
#[test]
fn test_parse_with_comments() {
let input = r"
-- This is a comment
protocol CommentTest =
roles Alice, Bob -- Inline comment
{- Multi-line
comment -}
Alice -> Bob : Hello
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse with comments: {:?}",
result.err()
);
}
#[test]
fn test_parse_typed_payload_with_inline_comment() {
let input = r"
protocol TypedCommentedPayload =
roles A, B
A -> B : Message(
value = 1 -- inline payload comment
flag = true
)
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse typed payload with inline comment: {:?}",
result.err()
);
}
#[test]
fn test_parse_typed_payload_with_block_comment() {
let input = r#"
protocol TypedPayloadBlockComment =
roles A, B
A -> B : Message(
value = 1
{- block payload comment -}
kind = "ok"
)
"#;
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse typed payload with block comment: {:?}",
result.err()
);
}
#[test]
fn test_parse_whitespace_variations() {
let input = r"
protocol WhitespaceTest =
roles Alice,Bob
Alice->Bob:Hello
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_empty_protocol_body() {
let input = r"
protocol Empty =
roles A, B
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_role_names_with_underscores() {
let input = r"
protocol UnderscoreRoles =
roles Alice_Client, Bob_Server
Alice_Client -> Bob_Server : Request
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_role_names_with_numbers() {
let input = r"
protocol NumericRoles =
roles Worker1, Worker2, Worker3
Worker1 -> Worker2 : Data
Worker2 -> Worker3 : Forward
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_parse_sequence_of_sends() {
let input = r"
protocol Sequence =
roles A, B, C, D
A -> B : Msg1
B -> C : Msg2
C -> D : Msg3
D -> A : Msg4
";
let result = parse_choreography_str(input);
assert!(result.is_ok());
}
#[test]
fn test_integration_with_projection() {
use telltale_runtime::compiler::projection;
let input = r"
protocol TwoParty =
roles Client, Server
Client -> Server : Request
Server -> Client : Response
";
let choreo = parse_choreography_str(input).expect("Failed to parse");
for role in &choreo.roles {
let result = projection::project(&choreo, role);
assert!(
result.is_ok(),
"Failed to project for role {}: {:?}",
role.name(),
result.err()
);
}
}
#[test]
fn test_integration_validation() {
let input = r"
protocol ValidProtocol =
roles A, B
A -> B : Hello
B -> A : World
";
let choreo = parse_choreography_str(input).expect("Failed to parse");
let validation_result = choreo.validate();
assert!(
validation_result.is_ok(),
"Validation failed: {:?}",
validation_result.err()
);
}
#[test]
fn test_error_message_quality() {
let input = r"
protocol Example =
roles Alice, Bob
Alice -> Charlie : Hello
";
let result = parse_choreography_str(input);
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains("Undefined role"));
assert!(err_msg.contains("Charlie"));
assert!(err_msg.contains("-->"));
assert!(err_msg.contains("5:"));
assert!(err_msg.contains("Alice -> Charlie : Hello"));
assert!(err_msg.contains('^'));
}
#[test]
fn test_error_span_precision() {
let input = r"
protocol Test =
roles Alice, Bob
Alice -> UnknownRole : Message
Bob -> Alice : Response
";
let result = parse_choreography_str(input);
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains("UnknownRole"));
assert!(err_msg.contains("Alice -> UnknownRole : Message"));
}
#[test]
fn test_parse_protocol_composition_simple() {
let input = r"
protocol CompositionExample =
roles Alice, Bob
call Handshake
Alice -> Bob : Data
where
protocol Handshake =
Alice -> Bob : Hello
Bob -> Alice : Hi
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse protocol composition: {:?}",
result.err()
);
let choreo = result.unwrap();
assert_eq!(choreo.name.to_string(), "CompositionExample");
}
#[test]
fn test_parse_protocol_composition_multiple_defs() {
let input = r"
protocol MultipleProtocols =
roles A, B, C
call Step1
call Step2
call Step3
where
protocol Step1 =
A -> B : Start
protocol Step2 =
B -> C : Continue
protocol Step3 =
C -> A : Finish
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse multiple protocols: {:?}",
result.err()
);
}
#[test]
fn test_parse_protocol_composition_nested_calls() {
let input = r"
protocol NestedCalls =
roles Alice, Bob
call Outer
where
protocol Inner =
Alice -> Bob : Data1
Bob -> Alice : Ack1
protocol Outer =
call Inner
Alice -> Bob : Data2
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse nested calls: {:?}",
result.err()
);
}
#[test]
fn test_parse_protocol_composition_in_choice() {
let input = r"
protocol CallInChoice =
roles Client, Server
Client -> Server : Request
choice Server at
| ok =>
call Success
| error =>
call Failure
where
protocol Success =
Server -> Client : Success
protocol Failure =
Server -> Client : Failure
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse call in choice: {:?}",
result.err()
);
}
#[test]
fn test_parse_protocol_composition_in_loop() {
let input = r"
protocol CallInLoop =
roles A, B
loop repeat 3
call Exchange
where
protocol Exchange =
A -> B : Request
B -> A : Response
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse call in loop: {:?}",
result.err()
);
}
#[test]
fn test_error_undefined_protocol_call() {
let input = r"
protocol UndefinedCall =
roles A, B
call NonExistent
";
let result = parse_choreography_str(input);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(
err,
telltale_runtime::compiler::parser::ParseError::UndefinedProtocol { .. }
));
let err_msg = err.to_string();
assert!(err_msg.contains("Undefined protocol"));
assert!(err_msg.contains("NonExistent"));
}
#[test]
fn test_error_duplicate_protocol_def() {
let input = r"
protocol DuplicateDef =
roles A, B
where
protocol MyProtocol =
A -> B : First
protocol MyProtocol =
A -> B : Second
";
let result = parse_choreography_str(input);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(
err,
telltale_runtime::compiler::parser::ParseError::DuplicateProtocol { .. }
));
let err_msg = err.to_string();
assert!(err_msg.contains("Duplicate protocol"));
assert!(err_msg.contains("MyProtocol"));
}
#[test]
fn test_parse_choice_with_guard() {
let input = r"
protocol GuardExample =
roles Client, Server
choice Client at
| buy when (balance > price) =>
Client -> Server : Purchase
| cancel =>
Client -> Server : Cancel
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse choice with guard: {:?}",
result.err()
);
}
#[test]
fn test_parse_choice_with_multiple_guards() {
let input = r"
protocol MultiGuards =
roles A, B
choice A at
| option1 when (x > 0) =>
A -> B : Msg1
| option2 when (x < 0) =>
A -> B : Msg2
| option3 =>
A -> B : Msg3
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse multiple guards: {:?}",
result.err()
);
}
#[test]
fn test_parse_guard_with_complex_expression() {
let input = r"
protocol ComplexGuard =
roles Client, Server
choice Client at
| proceed when (balance >= price && is_authenticated) =>
Client -> Server : Action
| reject =>
Client -> Server : Reject
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse complex guard: {:?}",
result.err()
);
}
#[test]
fn test_parse_guard_in_nested_choice() {
let input = r"
protocol NestedGuard =
roles A, B, C
choice A at
| outer when (condition1) =>
A -> B : Start
choice B at
| inner when (condition2) =>
B -> C : Inner
| fallback =>
B -> C : Fallback
| skip =>
A -> C : Skip
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse nested guard: {:?}",
result.err()
);
}
#[test]
fn test_annotation_syntax_is_rejected() {
let input = r"
@optimize
protocol Simple =
roles A, B
A -> B : Msg
";
let result = parse_choreography_str(input);
assert!(result.is_err(), "Annotation syntax should be rejected");
}
#[test]
fn test_parse_message_with_of_type() {
let input = r"
protocol TypedMessages =
roles A, B
A -> B : Request of shop.Request
B -> A : Response of shop.Response
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse `of`-typed messages: {:?}",
result.err()
);
}
#[test]
fn test_parse_message_with_structured_payload() {
let input = r"
protocol StructuredPayload =
roles A, B
A -> B : Data(first : String, second : I32, flag : Bool)
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse structured payload: {:?}",
result.err()
);
}
#[test]
fn test_reject_message_with_generic_angle_bracket_types() {
let input = r"
protocol Generics =
roles A, B
A -> B : Container<Vec<String>>
B -> A : Result<i32, Error>
";
let result = parse_choreography_str(input);
assert!(
result.is_err(),
"angle-bracket generic message types must be rejected"
);
}
#[test]
fn test_parse_message_with_of_type_and_payload() {
let input = r"
protocol TypedWithPayload =
roles A, B
A -> B : Request of shop.Request(data)
B -> A : Response of shop.Response(result)
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse `of`-typed message with payload: {:?}",
result.err()
);
}
#[test]
fn test_parse_message_with_path_types() {
let input = r"
protocol PathTypes =
roles A, B
A -> B : Data of std.string.String
B -> A : Result of std.collections.Vector
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse dotted path types: {:?}",
result.err()
);
}
#[test]
fn test_parse_message_with_of_payload_and_dotted_path() {
let input = r"
protocol PayloadOf =
roles A, B
A -> B : Request of shop.Order
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse message with `of` payload syntax: {:?}",
result.err()
);
let choreo = result.unwrap();
match &choreo.protocol {
Protocol::Send { message, .. } => {
assert_eq!(message.name.to_string(), "Request");
assert_eq!(
message.payload.as_ref().map(ToString::to_string),
Some("shop :: Order".to_string())
);
}
_ => panic!("Expected Send"),
}
}
#[test]
fn test_reject_message_with_of_missing_payload_type() {
let input = r"
protocol MissingPayloadOf =
roles A, B
A -> B : Request of
";
let result = parse_choreography_str(input);
assert!(
result.is_err(),
"missing payload type after `of` should not parse"
);
}
#[test]
fn test_reject_message_with_dotted_angle_bracket_type() {
let input = r"
protocol DottedTyped =
roles A, B
A -> B : Request<shop.Order>
";
let result = parse_choreography_str(input);
assert!(
result.is_err(),
"angle-bracket dotted types must be rejected in favor of `of` syntax"
);
}
#[test]
fn test_parse_parameterized_role() {
let input = r"
protocol WorkerPool =
roles Master, Worker[N]
Master -> Worker[0] : Task
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse parameterized role: {:?}",
result.err()
);
let choreo = result.unwrap();
assert_eq!(choreo.roles.len(), 2);
assert!(choreo.roles[1].is_parameterized());
}
#[test]
fn test_parse_concrete_indexed_role() {
let input = r"
protocol IndexedWorkers =
roles Master, Worker[3]
Master -> Worker[0] : Task1
Master -> Worker[1] : Task2
Master -> Worker[2] : Task3
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse indexed roles: {:?}",
result.err()
);
}
#[test]
fn test_parse_multiple_parameterized_roles() {
let input = r"
protocol MultiParam =
roles Coordinator, Worker[N], Monitor[M]
Coordinator -> Worker[i] : Start
Worker[i] -> Monitor[j] : Report
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse multiple parameterized roles: {:?}",
result.err()
);
let choreo = result.unwrap();
assert_eq!(choreo.roles.len(), 3);
}
#[test]
fn test_parse_parameterized_role_in_choice() {
let input = r"
protocol ParameterizedChoice =
roles Master, Worker[N]
choice Master at
| assign =>
Master -> Worker[i] : Task
| skip =>
Master -> Worker[0] : Skip
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse parameterized role in choice: {:?}",
result.err()
);
}
#[test]
fn test_parse_parameterized_role_loop() {
let input = r"
protocol ParameterizedLoop =
roles Master, Worker[N]
loop repeat N
Master -> Worker[i] : Work
Worker[i] -> Master : Result
";
let result = parse_choreography_str(input);
assert!(
result.is_ok(),
"Failed to parse parameterized role in loop: {:?}",
result.err()
);
}