1
0
Fork 0
mirror of https://github.com/imjasonh/testscript-rs synced 2026-07-08 09:05:34 +00:00
testscript-rs/tests/stdout_stderr_verification.rs
Jason Hall 8b267451de improve error reporting
Signed-off-by: Jason Hall <imjasonh@gmail.com>
2026-01-01 13:53:10 -05:00

549 lines
14 KiB
Rust

//! Comprehensive tests to verify that our stdout/stderr matching logic
//! exactly matches the behavior of Go's testscript package.
//!
//! These tests cover:
//! - Basic exact string matching
//! - Regex pattern matching
//! - Environment variable substitution
//! - The @R syntax for regex quoting
//! - Unicode handling
//! - Empty output handling
//! - Newline handling
//! - File vs literal content
//! - Edge cases and error conditions
use std::fs;
use tempfile::TempDir;
use testscript_rs::run_test;
#[test]
fn test_stdout_exact_match() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("stdout_exact.txt");
let script_content = r#"# Test exact string matching
exec echo "hello world"
stdout "hello world"
exec echo "with spaces "
stdout "with spaces"
exec printf "%s" "no newline"
stdout "no newline"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Stdout exact match test failed:\n{}", error);
}
}
#[test]
fn test_stderr_exact_match() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("stderr_exact.txt");
let script_content = r#"# Test stderr exact string matching
exec sh -c 'echo "error message" >&2'
stderr "error message"
exec sh -c 'printf "%s" "error no newline" >&2'
stderr "error no newline"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Stderr exact match test failed:\n{}", error);
}
}
#[test]
fn test_stdout_regex_patterns() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("stdout_regex.txt");
let script_content = r#"# Test regex pattern matching
exec echo "hello123world"
stdout "hello[0-9]+world"
exec echo "start middle end"
stdout "^start.*end$"
exec echo "test"
stdout "t.st"
exec echo "multiple lines"
stdout "multiple.*lines"
# Test dot matches newline with (?s) flag
exec sh -c 'printf "line1\nline2"'
stdout "line1.*line2"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Stdout regex test failed:\n{}", error);
}
}
#[test]
fn test_stderr_regex_patterns() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("stderr_regex.txt");
let script_content = r#"# Test stderr regex pattern matching
exec sh -c 'echo "error123code" >&2'
stderr "error[0-9]+code"
exec sh -c 'printf "start\nerror\nend" >&2'
stderr "start.*error.*end"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Stderr regex test failed:\n{}", error);
}
}
#[test]
fn test_env_var_substitution() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("env_substitution.txt");
let script_content = r#"# Test environment variable substitution
env MSG=hello
env NUM=42
exec echo "hello"
stdout "$MSG"
exec echo "number: 42"
stdout "number: $NUM"
exec echo "hello 42 test"
stdout "$MSG $NUM test"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Env var substitution test failed:\n{}", error);
}
}
#[test]
fn test_regex_quote_syntax() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("regex_quote.txt");
let script_content = r#"# Test @R syntax for regex quoting
env SPECIAL='hello[world]'
env DOTS='test.file'
env PARENS='func(arg)'
exec echo "hello[world]"
stdout "${SPECIAL@R}"
exec echo "test.file"
stdout "${DOTS@R}"
exec echo "func(arg)"
stdout "${PARENS@R}"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if result.is_err() {
println!("Note: @R syntax test failed - this indicates missing implementation");
println!("Error: {:?}", result);
// Don't fail the test yet - we'll implement this if missing
}
}
#[test]
fn test_unicode_handling() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("unicode.txt");
let script_content = r#"# Test Unicode character handling
exec echo "héllo wørld"
stdout "héllo wørld"
exec echo "emoji: 🦀 🚀"
stdout "emoji: 🦀 🚀"
exec echo "unicode regex: café"
stdout "caf."
exec echo "中文测试"
stdout "中文测试"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Unicode handling test failed:\n{}", error);
}
}
#[test]
fn test_empty_output() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("empty_output.txt");
let script_content = r#"# Test empty output handling
exec echo -n ""
stdout ""
exec true
! stdout .
# Test stderr too
exec sh -c 'true'
! stderr .
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Empty output test failed:\n{}", error);
}
}
#[test]
fn test_newline_handling() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("newlines.txt");
let script_content = r#"# Test newline handling
exec echo "line with newline"
stdout "line with newline"
exec printf "%s" "no trailing newline"
stdout "no trailing newline"
exec sh -c 'printf "line1\nline2\n"'
stdout "line1\nline2"
exec sh -c 'printf "just newline\n"'
stdout "just newline"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Newline handling test failed:\n{}", error);
}
}
#[test]
fn test_file_vs_literal_content() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("file_vs_literal.txt");
let script_content = r#"# Test file content vs literal text
exec cat expected.txt
stdout expected.txt
exec echo "literal text"
stdout "literal text"
# Test with regex in file
exec echo "pattern123"
stdout regex_pattern.txt
-- expected.txt --
file content here
-- regex_pattern.txt --
pattern[0-9]+
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("File vs literal test failed:\n{}", error);
}
}
#[test]
fn test_whitespace_trimming() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("whitespace.txt");
let script_content = r#"# Test whitespace trimming behavior
exec sh -c 'printf "text \n\n "'
stdout "text"
exec sh -c 'printf " leading and trailing \n"'
stdout " leading and trailing"
exec sh -c 'printf "tabs\t\t\n"'
stdout "tabs"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Whitespace trimming test failed:\n{}", error);
}
}
#[test]
fn test_special_chars_escaping() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("special_chars.txt");
let script_content = r#"# Test special character handling
exec echo 'quotes "and" more'
stdout 'quotes "and" more'
exec echo "backslash: \\"
stdout "backslash: \\"
exec echo "dollar: $"
stdout "dollar: \\$"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if result.is_err() {
println!("Special chars test failed: {:?}", result);
// This might reveal escaping issues
}
}
#[test]
fn test_regex_detection_edge_cases() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("regex_detection.txt");
let script_content = r#"# Test edge cases for regex detection
exec echo "text with . dot"
stdout "text with \\. dot"
exec echo "text with * star"
stdout "text with \\* star"
exec echo "brackets [test]"
stdout "brackets \\[test\\]"
exec echo "parens (test)"
stdout "parens \\(test\\)"
# These should be treated as regex
exec echo "actual.test"
stdout "actual.test"
exec echo "starts with test"
stdout "^starts.*test"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if result.is_err() {
println!("Regex detection test failed: {:?}", result);
// This will help us understand regex detection behavior
}
}
#[test]
fn test_multiple_commands_output() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("multiple_commands.txt");
let script_content = r#"# Test that stdout/stderr refers to most recent command
exec echo "first"
stdout "first"
exec echo "second"
stdout "second"
exec sh -c 'echo "out" && echo "err" >&2'
stdout "out"
stderr "err"
exec echo "third"
stdout "third"
! stderr "err"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Multiple commands test failed:\n{}", error);
}
}
#[test]
fn test_stdout_stderr_no_output_available() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("no_output.txt");
let script_content = r#"# Test error when no command has been run
stdout "should fail"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
assert!(
result.is_err(),
"Should fail when no command output available"
);
// Check that the error message is appropriate
let error = result.unwrap_err();
let error_msg = format!("{}", error);
assert!(
error_msg.contains("No command output available")
|| error_msg.contains("no output")
|| error_msg.contains("stdout"),
"Error message should indicate no output available: {}",
error_msg
);
}
#[test]
fn test_negated_stdout_stderr() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("negated.txt");
let script_content = r#"# Test negated stdout/stderr commands
exec echo "output"
! stdout "wrong"
stdout "output"
exec sh -c 'echo "error" >&2'
! stderr "wrong"
stderr "error"
exec true
! stdout .
! stderr .
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Negated stdout/stderr test failed:\n{}", error);
}
}
#[test]
fn test_complex_regex_patterns() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("complex_regex.txt");
let script_content = r#"# Test complex regex patterns
exec echo "version: 1.2.3-beta"
stdout "version: [0-9]+\\.[0-9]+\\.[0-9]+(-[a-z]+)?"
exec sh -c 'printf "multiline\noutput\nhere"'
stdout "multiline.*output.*here"
exec echo "email@example.com"
stdout "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if result.is_err() {
println!("Complex regex test failed: {:?}", result);
// This might reveal regex handling differences
}
}
#[test]
fn test_env_substitution_in_regex() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("env_regex.txt");
let script_content = r#"# Test environment variables in regex patterns
env PATTERN="test[0-9]+"
env PREFIX="start"
exec echo "test123"
stdout "$PATTERN"
exec echo "start-test456"
stdout "$PREFIX-test[0-9]+"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Env substitution in regex test failed:\n{}", error);
}
}
#[test]
fn test_stdout_stderr_with_count_option() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("count_option.txt");
// Note: This test is to check if we support the -count=N option
// which is mentioned in the Go documentation
let script_content = r#"# Test -count option (if supported)
exec printf "test\ntest\nother"
stdout -count=2 "test"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if result.is_err() {
println!("Count option test failed - this may indicate missing implementation");
println!("Error: {:?}", result);
// Don't fail the test yet - we'll implement this if missing
}
}
#[test]
fn test_dash_for_empty_output() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("dash_empty.txt");
let script_content = r#"# Test using - for empty output (mentioned in our code)
exec echo -n ""
stdout -
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if let Err(error) = result {
panic!("Dash for empty output test failed:\n{}", error);
}
}
#[test]
fn test_literal_regex_characters() {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("literal_regex.txt");
// Test how we handle strings that contain regex characters but should be literal
let script_content = r#"# Test literal handling of regex characters
exec echo "file.txt"
stdout "file\\.txt"
exec echo "cost: $5.00"
stdout "cost: \\$5\\.00"
exec echo "pattern[1]"
stdout "pattern\\[1\\]"
exec echo "func(x)"
stdout "func\\(x\\)"
exec echo "2*3=6"
stdout "2\\*3=6"
"#;
fs::write(&script_path, script_content).unwrap();
let result = run_test(&script_path);
if result.is_err() {
println!("Literal regex chars test failed: {:?}", result);
// This will help us understand how to properly escape regex characters
}
}