use super::*;
use std::time::Instant;
fn errors_contain(errors: &[ParseError], needle: &str) -> bool {
errors.iter().any(|e| e.to_string().contains(needle))
}
#[test]
fn test_recursive_heredoc_terminator_hang() {
let code = r#"
my $outer = <<'END';
Content before inner
my $inner = <<'END';
Inner content
END
Content after inner
END
"#;
let start = Instant::now();
let mut parser = Parser::new(code);
let result = parser.parse();
let duration = start.elapsed();
assert!(duration.as_secs() < 2, "Parser should not hang on recursive terminators");
match result {
Ok(_) => {}
Err(err) => unreachable!("parse should complete without fatal error: {}", err),
}
let errors = parser.errors();
assert!(
!errors_contain(errors, "Heredoc parsing timed out"),
"unexpected heredoc timeout: {errors:?}"
);
}
#[test]
fn test_excessive_pending_heredocs() {
let mut code = String::from("print ");
let heredocs: Vec<&str> = (0..110).map(|_| "<<EOF").collect();
code.push_str(&heredocs.join(", "));
code.push_str(";\n");
for _ in 0..110 {
code.push_str("content\nEOF\n");
}
let mut parser = Parser::new(&code);
let result = parser.parse();
if let Err(err) = result {
assert!(
err.to_string().contains("Heredoc depth limit exceeded"),
"Unexpected error: {}",
err
);
return;
}
let errors = parser.errors();
assert!(
errors_contain(errors, "Heredoc depth limit exceeded"),
"Expected heredoc depth limit error, got: {errors:?}"
);
}
#[test]
fn test_heredoc_parsing_timeout() {
let code = "my $x = <<EOF;\ncontent\nEOF";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "expected parse to succeed for simple heredoc");
let errors = parser.errors();
assert!(
!errors_contain(errors, "Heredoc parsing timed out"),
"unexpected heredoc timeout: {errors:?}"
);
}
#[test]
fn test_multiple_heredocs_same_line() {
let code = r#"my ($a, $b, $c) = (<<EOF1, <<EOF2, <<EOF3);
Content for first heredoc
EOF1
Content for second heredoc
EOF2
Content for third heredoc
EOF3
"#;
let start = Instant::now();
let mut parser = Parser::new(code);
let result = parser.parse();
let duration = start.elapsed();
assert!(duration.as_secs() < 2, "Parser should handle multiple heredocs efficiently");
match result {
Ok(_) => {}
Err(err) => {
assert!(!err.to_string().contains("timeout"), "Should not timeout: {}", err);
}
}
let errors = parser.errors();
assert!(
!errors_contain(errors, "Heredoc parsing timed out"),
"unexpected heredoc timeout: {errors:?}"
);
}
#[test]
fn test_nested_heredocs_within_limit() {
let mut code = String::from("my @h = (");
for i in 0..50 {
code.push_str(&format!("<<EOF{}, ", i));
}
code.push_str(");\n");
for i in 0..50 {
code.push_str(&format!("Content for heredoc {}\n", i));
code.push_str(&format!("EOF{}\n", i));
}
let start = Instant::now();
let mut parser = Parser::new(&code);
let _result = parser.parse();
let duration = start.elapsed();
assert!(duration.as_secs() < 2, "Parser should handle 50 heredocs efficiently");
let errors = parser.errors();
assert!(
!errors_contain(errors, "Heredoc depth limit exceeded"),
"Should not have depth errors within limit: {errors:?}"
);
}
#[test]
fn test_deeply_nested_heredocs_hit_limit() {
let mut code = String::from("print ");
for i in 0..101 {
code.push_str(&format!("<<EOF{}, ", i));
}
code.push_str(";\n");
for i in 0..101 {
code.push_str(&format!("Content for heredoc {}\n", i));
code.push_str(&format!("EOF{}\n", i));
}
let start = Instant::now();
let mut parser = Parser::new(&code);
let _result = parser.parse();
let duration = start.elapsed();
assert!(duration.as_secs() < 2, "Parser should hit heredoc depth limit efficiently");
let errors = parser.errors();
assert!(
errors_contain(errors, "Heredoc depth limit exceeded"),
"Expected depth limit error for 150 heredocs: {errors:?}"
);
}