#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use telltale_runtime::{
ast::{LocalType, Role},
compiler::{
codegen::{generate_choreography_code_with_dynamic_roles, generate_dynamic_role_support},
parser::parse_choreography_str,
projection::project,
},
};
#[test]
fn test_namespaced_choreographies() {
let protocol1 = r#"
module secure_messaging exposing (EncryptedChat)
protocol EncryptedChat =
roles Client, Server
Client -> Server : SecureMessage
Server -> Client : Acknowledgment
"#;
let protocol2 = r#"
module file_transfer exposing (SecureFileTransfer)
protocol SecureFileTransfer =
roles Sender, Receiver
Sender -> Receiver : FileChunk
Receiver -> Sender : ChunkAck
"#;
let choreo1 = parse_choreography_str(protocol1).expect("Protocol 1 should parse");
let choreo2 = parse_choreography_str(protocol2).expect("Protocol 2 should parse");
assert_eq!(choreo1.namespace.as_ref().unwrap(), "secure_messaging");
assert_eq!(choreo2.namespace.as_ref().unwrap(), "file_transfer");
assert_eq!(choreo1.roles.len(), 2);
assert_eq!(choreo2.roles.len(), 2);
for role in &choreo1.roles {
let local_type = project(&choreo1, role).expect("Projection should succeed");
assert_ne!(local_type, LocalType::End); }
for role in &choreo2.roles {
let local_type = project(&choreo2, role).expect("Projection should succeed");
assert_ne!(local_type, LocalType::End);
}
}
#[test]
fn test_dynamic_roles() {
let protocol = r#"
module threshold_consensus exposing (AnnotatedThreshold)
protocol AnnotatedThreshold =
roles Leader, Followers[*]
Leader -> Followers[*] : PrepareRequest
Followers[0..quorum] -> Leader : PrepareResponse
Leader -> Followers[*] : CommitRequest
Followers[0..quorum] -> Leader : CommitResponse
"#;
let choreo = parse_choreography_str(protocol).expect("Dynamic annotated protocol should parse");
assert_eq!(choreo.namespace.as_ref().unwrap(), "threshold_consensus");
assert_eq!(choreo.roles.len(), 2);
let leader = &choreo.roles[0];
let followers = &choreo.roles[1];
assert_eq!(leader.name().to_string(), "Leader");
assert_eq!(followers.name().to_string(), "Followers");
assert!(followers.is_dynamic());
let local_types = vec![
(leader.clone(), LocalType::End),
(followers.clone(), LocalType::End),
];
let generated_code = generate_choreography_code_with_dynamic_roles(&choreo, &local_types);
let code_str = generated_code.to_string();
assert!(code_str.contains("AnnotatedThresholdRuntime"));
assert!(code_str.contains("dynamic"));
assert!(code_str.contains("bind_role_count"));
}
#[test]
fn test_complex_multi_feature_protocol() {
let protocol = r#"
module advanced_example exposing (ComplexProtocol)
protocol ComplexProtocol =
roles Coordinator, Workers[N], Database
Coordinator -> Workers[*] : InitRequest
Workers[i] -> Database : DataQuery
Database -> Workers[i] : QueryResult
Workers[0..majority] -> Coordinator : WorkResult
choice Coordinator at
| success =>
Coordinator -> Workers[*] : SuccessNotification
Coordinator -> Database : FinalizeTransaction
| retry =>
Coordinator -> Workers[*] : RetryRequest
| abort =>
Coordinator -> Database : AbortTransaction
Coordinator -> Workers[*] : AbortNotification
"#;
let choreo = parse_choreography_str(protocol).expect("Complex protocol should parse");
assert_eq!(choreo.namespace.as_ref().unwrap(), "advanced_example");
assert_eq!(choreo.roles.len(), 3);
let coordinator = &choreo.roles[0];
let workers = &choreo.roles[1];
let database = &choreo.roles[2];
assert_eq!(coordinator.name().to_string(), "Coordinator");
assert_eq!(workers.name().to_string(), "Workers");
assert_eq!(database.name().to_string(), "Database");
assert!(!coordinator.is_parameterized());
assert!(workers.is_symbolic()); assert!(!database.is_parameterized());
for role in &choreo.roles {
let result = project(&choreo, role);
match result {
Ok(local_type) => {
println!("Projection succeeded for {}: {:?}", role.name(), local_type);
}
Err(projection_error) => {
println!(
"Expected projection error for {}: {:?}",
role.name(),
projection_error
);
}
}
}
let local_types = vec![
(coordinator.clone(), LocalType::End),
(workers.clone(), LocalType::End),
(database.clone(), LocalType::End),
];
let generated_code = generate_choreography_code_with_dynamic_roles(&choreo, &local_types);
let code_str = generated_code.to_string();
assert!(code_str.contains("ComplexProtocolRuntime"));
assert!(code_str.contains("validate_workers_count"));
assert!(code_str.contains("bind_role_count"));
}
#[test]
fn test_multiple_dynamic_role_types() {
let protocol = r#"
module multi_dynamic exposing (MultiDynamicRoles)
protocol MultiDynamicRoles =
roles Controller, StaticWorkers[3], DynamicWorkers[*], SymbolicWorkers[M]
Controller -> StaticWorkers[0] : StaticTask
Controller -> DynamicWorkers[*] : DynamicTask
Controller -> SymbolicWorkers[*] : SymbolicTask
StaticWorkers[0] -> Controller : StaticResult
DynamicWorkers[0..response_count] -> Controller : DynamicResult
SymbolicWorkers[i] -> Controller : SymbolicResult
"#;
let choreo = parse_choreography_str(protocol).expect("Multi-dynamic protocol should parse");
assert_eq!(choreo.roles.len(), 4);
let controller = &choreo.roles[0];
let static_workers = &choreo.roles[1];
let dynamic_workers = &choreo.roles[2];
let symbolic_workers = &choreo.roles[3];
assert!(!controller.is_parameterized());
assert!(
static_workers.is_parameterized()
&& !static_workers.is_dynamic()
&& !static_workers.is_symbolic()
);
assert!(dynamic_workers.is_dynamic());
assert!(symbolic_workers.is_symbolic());
assert!(controller.validate().is_ok());
assert!(static_workers.validate().is_ok());
assert!(dynamic_workers.validate().is_ok());
assert!(symbolic_workers.validate().is_ok());
let dynamic_support = generate_dynamic_role_support(&choreo);
let code_str = dynamic_support.to_string();
assert!(code_str.contains("validate_dynamicworkers_count"));
assert!(code_str.contains("validate_symbolicworkers_count"));
assert!(!code_str.contains("validate_staticworkers_count"));
}
#[test]
fn test_nested_choices() {
let protocol = r#"
module nested_complex exposing (NestedProtocol)
protocol NestedProtocol =
roles Client, Server, Database
Client -> Server : StartSession
choice Server at
| authenticate =>
Server -> Database : AuthQuery
choice Database at
| success =>
Database -> Server : AuthSuccess
Server -> Client : AuthToken
| failure =>
Database -> Server : AuthFailure
Server -> Client : AuthDenied
| reject =>
Server -> Client : Rejected
"#;
let choreo = parse_choreography_str(protocol).expect("Nested protocol should parse");
assert_eq!(choreo.namespace.as_ref().unwrap(), "nested_complex");
assert_eq!(choreo.roles.len(), 3);
for role in &choreo.roles {
let local_type = project(&choreo, role).expect("Projection should handle nested choices");
match local_type {
LocalType::End => panic!("Should not get End for complex protocol"),
LocalType::Send { .. }
| LocalType::Receive { .. }
| LocalType::Select { .. }
| LocalType::Branch { .. } => {
}
_ => {
}
}
}
}
#[test]
fn test_error_handling_integration() {
let invalid_protocol1 = r#"
module error_test exposing (InvalidProtocol)
protocol InvalidProtocol =
roles A, B[*]
A -> UndefinedRole : Message
"#;
let result1 = parse_choreography_str(invalid_protocol1);
assert!(result1.is_err());
let invalid_protocol3 = r#"
protocol InvalidDynamic =
roles A, B[invalid]
A -> B[999999999] : Message
"#;
let result2 = parse_choreography_str(invalid_protocol3);
assert!(result2.is_err());
}
#[test]
fn test_performance_characteristics() {
use std::time::Instant;
let complex_protocol = r#"
module performance_test exposing (PerformanceTest)
protocol PerformanceTest =
roles Controller, Workers[*]
Controller -> Workers[*] : StartWork
Workers[0..batch_size] -> Controller : WorkComplete
choice Controller at
| continue =>
Controller -> Workers[*] : ContinueWork
| stop =>
Controller -> Workers[*] : StopWork
"#;
let start = Instant::now();
for _ in 0..100 {
let choreo = parse_choreography_str(complex_protocol).expect("Should parse quickly");
assert_eq!(choreo.namespace.as_ref().unwrap(), "performance_test");
assert_eq!(choreo.roles.len(), 2);
for role in &choreo.roles {
drop(project(&choreo, role)); }
}
let duration = start.elapsed();
println!(
"Performance test completed 100 iterations in {:?}",
duration
);
}
#[test]
fn test_full_compilation_pipeline() {
let protocol = r#"
module compilation_test exposing (CompilationTest)
protocol CompilationTest =
roles Client, Servers[*], Database
Client -> Servers[*] : Request
Servers[i] -> Database : Query
Database -> Servers[i] : Response
Servers[0..quorum] -> Client : AggregatedResponse
"#;
let choreo = parse_choreography_str(protocol).expect("Protocol should parse");
let local_types: Vec<(Role, LocalType)> = choreo
.roles
.iter()
.map(|role| (role.clone(), LocalType::End))
.collect();
let generated_code = generate_choreography_code_with_dynamic_roles(&choreo, &local_types);
let code_str = generated_code.to_string();
assert!(code_str.contains("CompilationTestRuntime"));
assert!(code_str.contains("bind_role_count"));
assert!(code_str.contains("validate_servers_count"));
assert!(code_str.contains("impl"));
assert!(code_str.contains("pub struct"));
assert!(code_str.contains("pub fn"));
println!("Full compilation pipeline test completed successfully");
println!("Generated code length: {} characters", code_str.len());
}