mirror of
https://github.com/imjasonh/testscript-rs
synced 2026-07-08 09:05:34 +00:00
549 lines
14 KiB
Rust
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
|
|
}
|
|
}
|