//! 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); assert!( result.is_ok(), "Stdout exact match test failed: {:?}", result ); } #[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); assert!( result.is_ok(), "Stderr exact match test failed: {:?}", result ); } #[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); assert!(result.is_ok(), "Stdout regex test failed: {:?}", result); } #[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); assert!(result.is_ok(), "Stderr regex test failed: {:?}", result); } #[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); assert!( result.is_ok(), "Env var substitution test failed: {:?}", result ); } #[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); assert!(result.is_ok(), "Unicode handling test failed: {:?}", result); } #[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); assert!(result.is_ok(), "Empty output test failed: {:?}", result); } #[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); assert!(result.is_ok(), "Newline handling test failed: {:?}", result); } #[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); assert!(result.is_ok(), "File vs literal test failed: {:?}", result); } #[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); assert!( result.is_ok(), "Whitespace trimming test failed: {:?}", result ); } #[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); assert!( result.is_ok(), "Multiple commands test failed: {:?}", result ); } #[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); assert!( result.is_ok(), "Negated stdout/stderr test failed: {:?}", result ); } #[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); assert!( result.is_ok(), "Env substitution in regex test failed: {:?}", result ); } #[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); assert!( result.is_ok(), "Dash for empty output test failed: {:?}", result ); } #[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 } }