#![allow(clippy::as_conversions)]
use std::time::Instant;
use telltale_runtime::{
ast::*,
compiler::{extension_parser::*, grammar::*, parser::parse_choreography_str, ProjectionError},
extensions::*,
};
#[derive(Debug)]
struct TestGrammarExtension;
impl GrammarExtension for TestGrammarExtension {
fn grammar_rules(&self) -> &'static str {
r#"
test_extension_stmt = { "test" ~ ident ~ "{" ~ protocol_body ~ "}" }
test_extension_simple = { "simple" ~ ident }
"#
}
fn statement_rules(&self) -> Vec<&'static str> {
vec!["test_extension_stmt", "test_extension_simple"]
}
fn extension_id(&self) -> &'static str {
"test_extension"
}
fn priority(&self) -> u32 {
100
}
}
#[derive(Debug)]
struct HighPriorityExtension;
impl GrammarExtension for HighPriorityExtension {
fn grammar_rules(&self) -> &'static str {
r#"high_priority_stmt = { "priority" ~ integer }"#
}
fn statement_rules(&self) -> Vec<&'static str> {
vec!["high_priority_stmt"]
}
fn extension_id(&self) -> &'static str {
"high_priority"
}
fn priority(&self) -> u32 {
500 }
}
#[derive(Debug)]
struct TestStatementParser;
impl StatementParser for TestStatementParser {
fn can_parse(&self, rule_name: &str) -> bool {
matches!(rule_name, "test_extension_stmt" | "test_extension_simple")
}
fn supported_rules(&self) -> Vec<String> {
vec![
"test_extension_stmt".to_string(),
"test_extension_simple".to_string(),
]
}
fn parse_statement(
&self,
_rule_name: &str,
_content: &str,
_context: &ParseContext,
) -> Result<Box<dyn ProtocolExtension>, ParseError> {
Ok(Box::new(TestProtocolExtension {
name: "test".to_string(),
}))
}
}
#[derive(Debug, Clone)]
struct TestProtocolExtension {
#[allow(dead_code)]
name: String,
}
impl ProtocolExtension for TestProtocolExtension {
fn type_name(&self) -> &'static str {
"TestProtocolExtension"
}
fn mentions_role(&self, _role: &Role) -> bool {
true
}
fn validate(&self, _roles: &[Role]) -> Result<(), ExtensionValidationError> {
Ok(())
}
fn project(
&self,
_role: &Role,
_context: &ProjectionContext,
) -> Result<LocalType, ProjectionError> {
Err(ProjectionError::UnsupportedParallel("test".to_string()))
}
fn generate_code(&self, _context: &CodegenContext) -> proc_macro2::TokenStream {
quote::quote! { }
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<Self>()
}
fn clone_box(&self) -> Box<dyn ProtocolExtension> {
Box::new(self.clone())
}
}
#[cfg(test)]
mod grammar_composition_tests {
use super::*;
fn count_braces_outside_quotes(input: &str) -> (usize, usize) {
let mut open = 0;
let mut close = 0;
let mut in_string = false;
let mut escape = false;
for ch in input.chars() {
if in_string {
if escape {
escape = false;
} else if ch == '\\' {
escape = true;
} else if ch == '"' {
in_string = false;
}
continue;
}
if ch == '"' {
in_string = true;
continue;
}
match ch {
'{' => open += 1,
'}' => close += 1,
_ => {}
}
}
(open, close)
}
#[test]
fn test_basic_grammar_composition() {
let mut composer = GrammarComposer::new();
composer
.register_extension(TestGrammarExtension)
.expect("extension should register");
let result = composer.compose();
assert!(result.is_ok(), "Grammar composition should succeed");
let composed = result.unwrap();
assert!(composed.contains("test_extension_stmt"));
assert!(composed.contains("choreography"));
assert!(composed.contains("// Extension Rules"));
}
#[test]
fn test_multiple_extension_composition() {
let mut composer = GrammarComposer::new();
composer
.register_extension(TestGrammarExtension)
.expect("extension should register");
composer
.register_extension(HighPriorityExtension)
.expect("extension should register");
let result = composer.compose();
assert!(
result.is_ok(),
"Multiple extension composition should succeed"
);
let composed = result.unwrap();
assert!(composed.contains("test_extension_stmt"));
assert!(composed.contains("high_priority_stmt"));
assert!(composer.extension_count() == 2);
}
#[test]
fn test_grammar_composition_caching() {
let mut composer = GrammarComposer::new();
composer
.register_extension(TestGrammarExtension)
.expect("extension should register");
let start = Instant::now();
let result1 = composer.compose().unwrap();
let first_time = start.elapsed();
let start = Instant::now();
let result2 = composer.compose().unwrap();
let second_time = start.elapsed();
assert_eq!(result1, result2);
assert!(
second_time < first_time / 2,
"Cached composition should be much faster: first={:?}, second={:?}",
first_time,
second_time
);
}
#[test]
fn test_cache_invalidation() {
let mut composer = GrammarComposer::new();
let result1 = composer.compose().unwrap();
composer
.register_extension(TestGrammarExtension)
.expect("extension should register");
let result2 = composer.compose().unwrap();
assert_ne!(result1, result2);
assert!(result2.contains("test_extension_stmt"));
assert!(!result1.contains("test_extension_stmt"));
}
#[test]
fn test_extension_priority_ordering() {
let mut composer = GrammarComposer::new();
composer
.register_extension(TestGrammarExtension)
.expect("extension should register"); composer
.register_extension(HighPriorityExtension)
.expect("extension should register");
let result = composer.compose().unwrap();
assert!(result.contains("test_extension_stmt"));
assert!(result.contains("high_priority_stmt"));
let test_pos = result.find("test_extension_stmt").unwrap();
let priority_pos = result.find("high_priority_stmt").unwrap();
assert!(test_pos > 0);
assert!(priority_pos > 0);
}
#[test]
fn test_extension_rule_detection() {
let mut composer = GrammarComposer::new();
composer
.register_extension(TestGrammarExtension)
.expect("extension should register");
assert!(composer.has_extension_rule("test_extension_stmt"));
assert!(composer.has_extension_rule("test_extension_simple"));
assert!(!composer.has_extension_rule("nonexistent_rule"));
}
#[test]
fn test_composed_grammar_validation() {
let mut composer = GrammarComposer::new();
composer
.register_extension(TestGrammarExtension)
.expect("extension should register");
let composed = composer.compose().unwrap();
assert!(composed.contains("choreography"));
assert!(composed.contains("send_stmt"));
assert!(composed.contains("roles"));
assert!(composed.contains("test_extension_stmt"));
let (open_braces, close_braces) = count_braces_outside_quotes(&composed);
assert_eq!(
open_braces, close_braces,
"Grammar should have balanced braces"
);
}
#[test]
fn test_grammar_composer_builder_pattern() {
let composer = GrammarComposerBuilder::new()
.with_extension(TestGrammarExtension)
.expect("test extension should register")
.with_extension(HighPriorityExtension)
.expect("priority extension should register")
.build();
assert_eq!(composer.extension_count(), 2);
assert!(composer.has_extension_rule("test_extension_stmt"));
assert!(composer.has_extension_rule("high_priority_stmt"));
}
}
#[cfg(test)]
mod extension_parser_tests {
use super::*;
#[test]
fn test_extension_parser_creation() {
let parser = ExtensionParser::new();
let stats = parser.extension_stats();
assert_eq!(stats.grammar_extensions, 0);
assert_eq!(stats.statement_parsers, 0);
}
#[test]
fn test_extension_registration() {
let mut parser = ExtensionParser::new();
parser
.register_extension(TestGrammarExtension, TestStatementParser)
.expect("extension should register");
let stats = parser.extension_stats();
assert_eq!(stats.grammar_extensions, 1);
assert!(parser.can_handle_statement("test_extension_stmt"));
assert!(parser.can_handle_statement("test_extension_simple"));
assert!(!parser.can_handle_statement("unknown_stmt"));
}
#[test]
fn test_standard_choreography_parsing() {
let mut parser = ExtensionParser::new();
let input = r#"
protocol TestProtocol =
roles Alice, Bob
Alice -> Bob : Message
"#;
let result = parser.parse_with_extensions(input);
assert!(result.is_ok(), "Should parse standard choreography");
let choreography = result.unwrap();
assert_eq!(choreography.roles.len(), 2);
assert_eq!(choreography.roles[0].name(), "Alice");
assert_eq!(choreography.roles[1].name(), "Bob");
}
#[test]
fn test_extension_parser_builder() {
let parser = ExtensionParserBuilder::new()
.with_extension(TestGrammarExtension, TestStatementParser)
.expect("test extension should register")
.build();
assert!(parser.can_handle_statement("test_extension_stmt"));
let stats = parser.extension_stats();
assert_eq!(stats.grammar_extensions, 1);
}
#[test]
fn test_composed_grammar_generation() {
let mut parser = ExtensionParser::new();
parser
.register_extension(TestGrammarExtension, TestStatementParser)
.expect("extension should register");
let result = parser.get_composed_grammar();
assert!(result.is_ok(), "Should generate composed grammar");
let grammar = result.unwrap();
assert!(
grammar.contains("test_extension_stmt"),
"Should contain extension rule"
);
assert!(
grammar.contains("choreography"),
"Should contain base rules"
);
}
#[test]
fn test_extension_parser_performance() {
let mut parser = ExtensionParser::new();
parser
.register_extension(TestGrammarExtension, TestStatementParser)
.expect("extension should register");
let input = r#"
protocol LargeProtocol =
roles A, B, C, D, E
A -> B : Message1
B -> C : Message2
C -> D : Message3
D -> E : Message4
E -> A : Message5
"#;
for _ in 0..10 {
let result = parser.parse_with_extensions(input);
assert!(result.is_ok(), "Should parse efficiently");
}
}
}
#[cfg(test)]
mod feature_inheritance_tests {
use super::*;
#[test]
fn test_choice_construct_inheritance() {
let choreography = parse_choreography_str(
r#"
protocol ChoiceExample =
roles Alice, Bob, Charlie
choice Alice at
| path1 =>
Alice -> Bob : Request
| path2 =>
Alice -> Charlie : Alternative
"#,
)
.expect("Should parse choice construct");
assert_eq!(choreography.roles.len(), 3);
match &choreography.protocol {
Protocol::Choice { role, branches, .. } => {
assert_eq!(role.name().to_string(), "Alice");
assert_eq!(branches.len(), 2);
assert_eq!(branches[0].label.to_string(), "path1");
assert_eq!(branches[1].label.to_string(), "path2");
}
_ => panic!("Expected choice protocol"),
}
}
#[test]
fn test_parameterized_roles_inheritance() {
let choreography = parse_choreography_str(
r#"
protocol ParameterizedExample =
roles Worker[N], Manager, Client[3]
Worker[*] -> Manager : Status
Manager -> Client[0] : Response
"#,
)
.expect("Should parse parameterized roles");
let worker_role = choreography
.roles
.iter()
.find(|r| *r.name() == "Worker")
.unwrap();
assert!(worker_role.param().is_some());
let client_role = choreography
.roles
.iter()
.find(|r| *r.name() == "Client")
.unwrap();
assert!(client_role.param().is_some());
let manager_role = choreography
.roles
.iter()
.find(|r| *r.name() == "Manager")
.unwrap();
assert!(manager_role.param().is_none());
}
#[test]
fn test_loop_construct_inheritance() {
let choreography = parse_choreography_str(
r#"
protocol LoopExample =
roles Producer, Consumer
loop forever
Producer -> Consumer : Data
"#,
)
.expect("Should parse loop construct");
assert_eq!(choreography.roles.len(), 2);
match &choreography.protocol {
Protocol::Loop { body, .. } => match body.as_ref() {
Protocol::Send { from, to, .. } => {
assert_eq!(from.name(), "Producer");
assert_eq!(to.name(), "Consumer");
}
_ => panic!("Expected send in loop body"),
},
_ => panic!("Expected loop protocol"),
}
}
#[test]
fn test_broadcast_inheritance() {
let choreography = parse_choreography_str(
r#"
protocol BroadcastExample =
roles Server, Client1, Client2
Server ->* : Notification
"#,
)
.expect("Should parse broadcast");
match &choreography.protocol {
Protocol::Broadcast { from, to_all, .. } => {
assert_eq!(from.name(), "Server");
assert_eq!(to_all.len(), 2);
assert!(to_all.iter().any(|r| *r.name() == "Client1"));
assert!(to_all.iter().any(|r| *r.name() == "Client2"));
}
_ => panic!("Expected broadcast protocol"),
}
}
#[test]
fn test_complex_protocol_inheritance() {
let choreography = parse_choreography_str(
r#"
protocol ComplexExample =
roles Coordinator, Worker[N], Client
choice Coordinator at
| distribute =>
loop forever
Coordinator -> Worker[*] : Task
Worker[*] -> Coordinator : Result
Coordinator -> Client : Summary
| direct =>
Coordinator -> Client : DirectResponse
"#,
)
.expect("Should parse complex protocol");
assert_eq!(choreography.roles.len(), 3);
let worker_role = choreography
.roles
.iter()
.find(|r| *r.name() == "Worker")
.unwrap();
assert!(worker_role.param().is_some());
match &choreography.protocol {
Protocol::Choice { branches, .. } => {
assert_eq!(branches.len(), 2);
assert_eq!(branches[0].label.to_string(), "distribute");
assert_eq!(branches[1].label.to_string(), "direct");
}
_ => panic!("Expected choice protocol"),
}
}
}
#[cfg(test)]
mod error_handling_tests {
use super::*;
#[test]
fn test_invalid_choreography_syntax() {
let invalid_choreographies = vec![
"not a protocol",
"protocol { }",
"protocol Test =\n roles",
"protocol Test =\n roles A, B\n A -> : Message",
"protocol Test =\n roles A\n A -> B : Message", ];
for invalid in invalid_choreographies {
let result = parse_choreography_str(invalid);
assert!(
result.is_err(),
"Should fail to parse invalid syntax: {}",
invalid
);
}
}
#[test]
fn test_extension_parser_error_handling() {
let mut parser = ExtensionParser::new();
parser
.register_extension(TestGrammarExtension, TestStatementParser)
.expect("extension should register");
let invalid_input = "protocol Test =\n invalid syntax";
let result = parser.parse_with_extensions(invalid_input);
assert!(result.is_err());
match result.unwrap_err() {
ExtensionParseError::StandardParseError(_) => {
}
other => panic!("Unexpected error type: {:?}", other),
}
}
#[test]
fn test_grammar_composition_edge_cases() {
let mut composer = GrammarComposer::new();
let result = composer.compose();
assert!(result.is_ok(), "Empty composer should still work");
composer
.register_extension(TestGrammarExtension)
.expect("first extension should register");
let dup_result = composer.register_extension(TestGrammarExtension);
assert!(
dup_result.is_err(),
"duplicate extension registration should be rejected"
);
let result = composer.compose();
assert!(
result.is_ok(),
"composition should succeed after valid registration"
);
assert!(composer.extension_count() > 0);
}
#[test]
fn test_role_validation_edge_cases() {
let edge_cases = vec![
(
r#"
protocol Empty =
roles }
"#,
false,
),
(
r#"
protocol Single =
roles Alice
"#,
true,
),
(
r#"
protocol Special =
roles Role-With-Dashes
"#,
false,
),
];
for (choreo, should_succeed) in edge_cases {
let result = parse_choreography_str(choreo);
if should_succeed {
assert!(result.is_ok(), "Should parse: {}", choreo);
} else {
assert!(result.is_err(), "Should fail to parse: {}", choreo);
}
}
}
#[test]
fn test_large_choreography_parsing() {
let mut large_choreo = String::from(
r#"
protocol LargeExample =
roles "#,
);
for i in 0..100 {
large_choreo.push_str(&format!("Role{}", i));
if i < 99 {
large_choreo.push_str(", ");
}
}
large_choreo.push('\n');
for i in 0..100 {
large_choreo.push_str(&format!(
" Role{} -> Role{} : Message{}\n",
i % 100,
(i + 1) % 100,
i
));
}
let start = Instant::now();
let result = parse_choreography_str(&large_choreo);
let parse_time = start.elapsed();
assert!(result.is_ok(), "Should parse large choreography");
assert!(
parse_time.as_millis() < 1000,
"Should parse in reasonable time"
);
let choreography = result.unwrap();
assert_eq!(choreography.roles.len(), 100);
}
#[test]
fn test_extension_parser_buffer_reuse() {
let mut parser = ExtensionParser::new();
parser
.register_extension(TestGrammarExtension, TestStatementParser)
.expect("extension should register");
let test_input = r#"
protocol BufferTest =
roles A, B
A -> B : Message
"#;
for _ in 0..10 {
let result = parser.parse_with_extensions(test_input);
assert!(result.is_ok(), "Buffer reuse should work correctly");
}
}
}
#[cfg(test)]
mod performance_tests {
use super::*;
#[test]
fn test_grammar_composition_performance_regression() {
let mut composer = GrammarComposer::new();
composer
.register_extension(TestGrammarExtension)
.expect("extension should register");
composer
.register_extension(HighPriorityExtension)
.expect("extension should register");
let start = Instant::now();
let _result1 = composer.compose().unwrap();
let first_time = start.elapsed();
let start = Instant::now();
let _result2 = composer.compose().unwrap();
let second_time = start.elapsed();
let speedup_ratio = first_time.as_nanos() as f64 / second_time.as_nanos() as f64;
assert!(
speedup_ratio > 10.0,
"Cache should provide at least 10x speedup, got {:.2}x",
speedup_ratio
);
}
#[test]
fn test_extension_parser_memory_efficiency() {
let mut parser = ExtensionParser::new();
parser
.register_extension(TestGrammarExtension, TestStatementParser)
.expect("extension should register");
let medium_choreo = r#"
protocol MemoryTest =
roles A, B, C, D, E, F, G, H, I, J
A -> B : M1
B -> C : M2
C -> D : M3
D -> E : M4
E -> F : M5
F -> G : M6
G -> H : M7
H -> I : M8
I -> J : M9
J -> A : M10
"#;
for _ in 0..100 {
let result = parser.parse_with_extensions(medium_choreo);
assert!(result.is_ok(), "Should handle repeated parsing");
}
}
#[test]
fn test_concurrent_composer_usage() {
use std::sync::{Arc, Mutex};
use std::thread;
let composer = Arc::new(Mutex::new({
let mut c = GrammarComposer::new();
c.register_extension(TestGrammarExtension)
.expect("extension should register");
c
}));
let handles: Vec<_> = (0..4)
.map(|_| {
let composer = Arc::clone(&composer);
thread::spawn(move || {
for _ in 0..10 {
let result = composer.lock().unwrap().compose();
assert!(result.is_ok(), "Concurrent composition should work");
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
}
#[cfg(test)]
mod regression_tests {
use super::*;
#[test]
fn test_choice_with_parameterized_roles() {
let choreography = parse_choreography_str(
r#"
protocol Regression1 =
roles Worker[N], Coordinator
choice Coordinator at
| parallel =>
Worker[*] -> Coordinator : Status
| sequential =>
Worker[0] -> Coordinator : FirstStatus
Worker[1] -> Coordinator : SecondStatus
"#,
)
.expect("Should parse complex regression case");
let worker_role = choreography
.roles
.iter()
.find(|r| *r.name() == "Worker")
.unwrap();
assert!(worker_role.param().is_some());
}
#[test]
fn test_nested_protocol_structures() {
let choreography = parse_choreography_str(
r#"
protocol NestedExample =
roles A, B, C
choice A at
| path1 =>
loop forever
choice B at
| continue =>
B -> A : Continue
| stop =>
B -> A : Stop
| path2 =>
A -> C : Direct
"#,
)
.expect("Should parse nested structures");
assert_eq!(choreography.roles.len(), 3);
}
}