1
use pretty_assertions::assert_eq;
2
use std::borrow::Cow;
3
use std::collections::BTreeSet;
4
use std::error::Error;
5
use std::fs;
6
use std::path::Path;
7
use std::path::PathBuf;
8
use std::process::Command;
9
use std::time::Instant;
10
use test_suite::AcceptMode;
11
use test_suite::TestConfig;
12
use test_suite::golden_files::assert_no_redundant_expected_files;
13
use test_suite::test_config::{round_expected_time, upsert_expected_time_config};
14

            
15
127
pub fn custom_test(test_dir: &str) -> Result<(), Box<dyn Error>> {
16
127
    let accept_mode = AcceptMode::from_env();
17
127
    let accept = accept_mode.accepts_outputs();
18
127
    let started_at = Instant::now();
19

            
20
    // Convert test directory to a PathBuf
21
127
    let test_path = PathBuf::from(test_dir);
22
127
    assert!(
23
127
        test_path.exists(),
24
        "Test directory not found: {test_path:?}"
25
    );
26

            
27
    // Get paths
28
127
    let script_path = test_path.join("run.sh");
29
127
    assert!(
30
127
        script_path.exists(),
31
        "Test script not found: {script_path:?}"
32
    );
33
127
    let expected_output_path = test_path.join("stdout.expected");
34
127
    let expected_error_path = test_path.join("stderr.expected");
35
127
    let config_path = test_path.join("config.toml");
36
127
    let file_config: TestConfig = if let Ok(config_contents) = fs::read_to_string(&config_path) {
37
127
        toml::from_str(&config_contents).unwrap()
38
    } else {
39
        Default::default()
40
    };
41

            
42
    // Execute the test script in the correct directory
43
127
    let output = Command::new("sh")
44
127
        .arg("run.sh")
45
127
        .current_dir(&test_path)
46
127
        .output()?;
47

            
48
    // Convert captured output/error to string
49
127
    let actual_output = String::from_utf8_lossy(&output.stdout);
50
127
    let actual_error = String::from_utf8_lossy(&output.stderr);
51

            
52
127
    if accept {
53
        // Overwrite expected files
54
        update_file(expected_output_path, &actual_output)?;
55
        update_file(expected_error_path, &actual_error)?;
56
    } else {
57
        // Compare results
58
127
        let expected_output = if expected_output_path.exists() {
59
105
            fs::read_to_string(&expected_output_path)?
60
        } else {
61
22
            String::new()
62
        };
63
127
        let expected_error = if expected_error_path.exists() {
64
19
            fs::read_to_string(&expected_error_path)?
65
        } else {
66
108
            String::new()
67
        };
68

            
69
127
        assert_eq!(expected_error, actual_error, "Standard error mismatch");
70
127
        assert_eq!(expected_output, actual_output, "Standard output mismatch");
71
    }
72

            
73
127
    let allowed_expected_files = expected_custom_files_for_case(&actual_output, &actual_error);
74
127
    assert_no_redundant_expected_files(Path::new(&test_path), &allowed_expected_files, None)?;
75

            
76
127
    if accept_mode.records_expected_time() {
77
        let observed_expected_time = round_expected_time(started_at.elapsed());
78
        if let Some(expected_time) =
79
            accept_mode.expected_time_to_record(file_config.expected_time, observed_expected_time)
80
        {
81
            upsert_expected_time_config(&config_path, expected_time)?;
82
        }
83
127
    }
84

            
85
127
    Ok(())
86
127
}
87

            
88
fn update_file(
89
    expected_file_path: PathBuf,
90
    actual_output: &Cow<'_, str>,
91
) -> Result<(), Box<dyn Error>> {
92
    if expected_file_path.exists() {
93
        fs::remove_file(&expected_file_path)?;
94
    }
95
    if !actual_output.trim().is_empty() {
96
        fs::write(&expected_file_path, actual_output.as_ref())?;
97
    }
98
    Ok(())
99
}
100

            
101
/// Returns the expected snapshot files for the observed custom test output.
102
127
fn expected_custom_files_for_case(
103
127
    stdout: &Cow<'_, str>,
104
127
    stderr: &Cow<'_, str>,
105
127
) -> BTreeSet<String> {
106
127
    let mut expected_files = BTreeSet::new();
107
127
    if !stdout.trim().is_empty() {
108
105
        expected_files.insert("stdout.expected".to_string());
109
22
    }
110
127
    if !stderr.trim().is_empty() {
111
19
        expected_files.insert("stderr.expected".to_string());
112
108
    }
113
127
    expected_files
114
127
}
115

            
116
#[test]
117
1
fn assert_conjure_present() {
118
1
    conjure_cp_cli::find_conjure::conjure_executable().unwrap();
119
1
}
120

            
121
include!(concat!(env!("OUT_DIR"), "/gen_tests_custom.rs"));