#![allow(deprecated)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
#![allow(non_snake_case)]
use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use std::io::Write;
use tempfile::{NamedTempFile, TempDir};
#[allow(deprecated)]
fn bashrs_cmd() -> Command {
assert_cmd::cargo_bin_cmd!("bashrs")
}
fn create_temp_bash_script(content: &str) -> NamedTempFile {
let mut file = NamedTempFile::new().expect("Failed to create temp file");
file.write_all(content.as_bytes())
.expect("Failed to write to temp file");
file
}
#[test]
fn test_WITH_TESTS_001_generates_test_file() {
let bash_script = r#"#!/bin/bash
# Simple script
x=42
echo $x
"#;
let input_file = create_temp_bash_script(bash_script);
let output_dir = TempDir::new().expect("Failed to create temp dir");
let output_file = output_dir.path().join("script.sh");
let test_file = output_dir.path().join("script_test.sh");
bashrs_cmd()
.arg("purify")
.arg(input_file.path())
.arg("--with-tests")
.arg("-o")
.arg(&output_file)
.assert()
.success();
assert!(
test_file.exists(),
"Test file should be generated at {}",
test_file.display()
);
let test_content = fs::read_to_string(&test_file).expect("Failed to read test file");
assert!(
test_content.starts_with("#!/bin/sh"),
"Test file should have POSIX shebang"
);
}
#[test]
fn test_WITH_TESTS_001_test_file_naming_convention() {
let bash_script = "#!/bin/bash\nx=1";
let input_file = create_temp_bash_script(bash_script);
let output_dir = TempDir::new().expect("Failed to create temp dir");
let output_file = output_dir.path().join("my_script.sh");
bashrs_cmd()
.arg("purify")
.arg(input_file.path())
.arg("--with-tests")
.arg("-o")
.arg(&output_file)
.assert()
.success();
let test_file = output_dir.path().join("my_script_test.sh");
assert!(
test_file.exists(),
"Test file should follow <script>_test.sh naming"
);
}
#[test]
fn test_WITH_TESTS_002_generates_determinism_test() {
let bash_script = r#"#!/bin/bash
echo "Hello, World!"
"#;
let input_file = create_temp_bash_script(bash_script);
let output_dir = TempDir::new().expect("Failed to create temp dir");
let output_file = output_dir.path().join("script.sh");
let test_file = output_dir.path().join("script_test.sh");
bashrs_cmd()
.arg("purify")
.arg(input_file.path())
.arg("--with-tests")
.arg("-o")
.arg(&output_file)
.assert()
.success();
let test_content = fs::read_to_string(&test_file).expect("Failed to read test file");
assert!(
test_content.contains("test_determinism") || test_content.contains("determinism"),
"Test file should contain determinism test"
);
assert!(
test_content.contains("output1") && test_content.contains("output2"),
"Determinism test should run script twice and compare outputs"
);
}
#[test]
fn test_WITH_TESTS_002_determinism_test_passes() {
let bash_script = r#"#!/bin/bash
echo "constant"
"#;
let input_file = create_temp_bash_script(bash_script);
let output_dir = TempDir::new().expect("Failed to create temp dir");
let output_file = output_dir.path().join("script.sh");
let test_file = output_dir.path().join("script_test.sh");
bashrs_cmd()
.arg("purify")
.arg(input_file.path())
.arg("--with-tests")
.arg("-o")
.arg(&output_file)
.assert()
.success();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&output_file).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&output_file, perms).unwrap();
let mut test_perms = fs::metadata(&test_file).unwrap().permissions();
test_perms.set_mode(0o755);
fs::set_permissions(&test_file, test_perms).unwrap();
}
let output = Command::new("sh")
.arg(&test_file)
.current_dir(&output_dir)
.output()
.expect("Failed to run generated tests");
assert!(
output.status.success(),
"Generated determinism test should pass for deterministic script"
);
}
#[test]
fn test_WITH_TESTS_003_generates_idempotency_test() {
let bash_script = r#"#!/bin/bash
mkdir -p /tmp/test_dir
"#;
let input_file = create_temp_bash_script(bash_script);
let output_dir = TempDir::new().expect("Failed to create temp dir");
let output_file = output_dir.path().join("script.sh");
let test_file = output_dir.path().join("script_test.sh");
bashrs_cmd()
.arg("purify")
.arg(input_file.path())
.arg("--with-tests")
.arg("-o")
.arg(&output_file)
.assert()
.success();
let test_content = fs::read_to_string(&test_file).expect("Failed to read test file");
assert!(
test_content.contains("test_idempotency") || test_content.contains("idempotent"),
"Test file should contain idempotency test"
);
assert!(
test_content.matches("./").count() >= 2,
"Idempotency test should run script multiple times"
);
}
#[test]
fn test_WITH_TESTS_003_idempotency_test_detects_issues() {
let bash_script = r#"#!/bin/bash
mkdir /tmp/test_dir_$RANDOM
"#;
let input_file = create_temp_bash_script(bash_script);
let output_dir = TempDir::new().expect("Failed to create temp dir");
let output_file = output_dir.path().join("script.sh");
bashrs_cmd()
.arg("purify")
.arg(input_file.path())
.arg("--with-tests")
.arg("-o")
.arg(&output_file)
.assert()
.success();
let purified = fs::read_to_string(&output_file).expect("Failed to read purified");
assert!(
purified.contains("mkdir -p"),
"Purified script should use mkdir -p"
);
assert!(
!purified.contains("$RANDOM"),
"Purified script should not contain $RANDOM"
);
}
#[test]
fn test_WITH_TESTS_004_generates_posix_compliance_test() {
let bash_script = r#"#!/bin/bash
echo "test"
"#;
let input_file = create_temp_bash_script(bash_script);
let output_dir = TempDir::new().expect("Failed to create temp dir");
let output_file = output_dir.path().join("script.sh");
let test_file = output_dir.path().join("script_test.sh");
bashrs_cmd()
.arg("purify")
.arg(input_file.path())
.arg("--with-tests")
.arg("-o")
.arg(&output_file)
.assert()
.success();
let test_content = fs::read_to_string(&test_file).expect("Failed to read test file");
assert!(
test_content.contains("shellcheck") || test_content.contains("POSIX"),
"Test file should contain POSIX compliance test"
);
}
#[test]
fn test_WITH_TESTS_005_property_tests_flag() {
let bash_script = r#"#!/bin/bash
echo "$1"
"#;
let input_file = create_temp_bash_script(bash_script);
let output_dir = TempDir::new().expect("Failed to create temp dir");
let output_file = output_dir.path().join("script.sh");
let test_file = output_dir.path().join("script_test.sh");
bashrs_cmd()
.arg("purify")
.arg(input_file.path())
.arg("--with-tests")
.arg("--property-tests")
.arg("-o")
.arg(&output_file)
.assert()
.success();
let test_content = fs::read_to_string(&test_file).expect("Failed to read test file");
assert!(
test_content.contains("for")
|| test_content.contains("while")
|| test_content.contains("seq"),
"Property tests should iterate over multiple test cases"
);
assert!(
test_content.contains("100") || test_content.contains("50"),
"Property tests should run many cases (e.g., 100)"
);
}
#[test]
fn test_WITH_TESTS_006_generated_tests_are_executable() {
let bash_script = r#"#!/bin/bash
x=42
echo $x
"#;
let input_file = create_temp_bash_script(bash_script);
let output_dir = TempDir::new().expect("Failed to create temp dir");
let output_file = output_dir.path().join("script.sh");
let test_file = output_dir.path().join("script_test.sh");
bashrs_cmd()
.arg("purify")
.arg(input_file.path())
.arg("--with-tests")
.arg("-o")
.arg(&output_file)
.assert()
.success();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&test_file).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&test_file, perms).unwrap();
let mut script_perms = fs::metadata(&output_file).unwrap().permissions();
script_perms.set_mode(0o755);
fs::set_permissions(&output_file, script_perms).unwrap();
}
let output = Command::new("sh")
.arg("-n") .arg(&test_file)
.output()
.expect("Failed to check test file syntax");
assert!(
output.status.success(),
"Generated test file should have valid sh syntax"
);
}
#[test]
fn test_WITH_TESTS_006_all_tests_pass_for_valid_script() {
let bash_script = r#"#!/bin/bash
# Deterministic, idempotent script
echo "Hello, World!"
"#;
let input_file = create_temp_bash_script(bash_script);
let output_dir = TempDir::new().expect("Failed to create temp dir");
let output_file = output_dir.path().join("script.sh");
let test_file = output_dir.path().join("script_test.sh");
bashrs_cmd()
.arg("purify")
.arg(input_file.path())
.arg("--with-tests")
.arg("-o")
.arg(&output_file)
.assert()
.success();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut test_perms = fs::metadata(&test_file).unwrap().permissions();
test_perms.set_mode(0o755);
fs::set_permissions(&test_file, test_perms).unwrap();
let mut script_perms = fs::metadata(&output_file).unwrap().permissions();
script_perms.set_mode(0o755);
fs::set_permissions(&output_file, script_perms).unwrap();
}
let output = Command::new("sh")
.arg(&test_file)
.current_dir(&output_dir)
.output()
.expect("Failed to run generated tests");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Generated tests should pass for valid purified script.\nStdout: {}\nStderr: {}",
stdout,
stderr
);
}
#[test]
fn test_WITH_TESTS_error_missing_output() {
let bash_script = "#!/bin/bash\necho test";
let input_file = create_temp_bash_script(bash_script);
bashrs_cmd()
.arg("purify")
.arg(input_file.path())
.arg("--with-tests")
.assert()
.failure()
.stderr(predicate::str::contains("output").or(predicate::str::contains("-o")));
}
#[test]
fn test_WITH_TESTS_help_flag() {
bashrs_cmd()
.arg("purify")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("--with-tests"))
.stdout(predicate::str::contains("Generate test suite"));
}