1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
use crate::{
model::{CommandKind, Invocation, TestFile, TestResultKind, TestFailReason, ProgramOutput},
Config,
vars,
VariablesExt,
};
use self::state::TestRunState;
use std::{collections::HashMap, env, fs, process};
mod state;
#[cfg(test)] mod state_tests;
const DEFAULT_SHELL: &'static str = "bash";
#[derive(Clone)]
pub struct TestEvaluator
{
pub invocation: Invocation,
}
pub fn execute_tests<'test>(test_file: &'test TestFile, config: &Config) -> Vec<(TestResultKind, &'test Invocation, CommandLine, ProgramOutput)> {
test_file.run_command_invocations().map(|invocation| {
let initial_variables = {
let mut vars = HashMap::new();
vars.extend(config.constants.clone());
vars.extend(test_file.variables());
vars
};
let mut test_run_state = TestRunState::new(initial_variables);
let (command, command_line) = self::build_command(invocation, test_file, config);
let (program_output, execution_result) = self::collect_output(command, command_line.clone());
test_run_state.append_program_output(&program_output.stdout);
test_run_state.append_program_stderr(&program_output.stderr);
if execution_result.is_erroneous() {
return (execution_result, invocation, command_line, program_output);
}
let overall_test_result_kind = run_test_checks(&mut test_run_state, test_file, config);
(overall_test_result_kind, invocation, command_line, program_output)
}).collect()
}
fn run_test_checks(
test_run_state: &mut TestRunState,
test_file: &TestFile,
config: &Config,
) -> TestResultKind {
let mut check_result = TestResultKind::EmptyTest;
for command in test_file.commands.iter() {
let test_result = match command.kind {
CommandKind::Run(..) |
CommandKind::XFail => {
TestResultKind::Pass
},
CommandKind::Check(ref text_pattern) => test_run_state.check(text_pattern, config),
CommandKind::CheckNext(ref text_pattern) => test_run_state.check_next(text_pattern, config),
};
if config.cleanup_temporary_files {
let tempfile_paths = test_run_state.variables().tempfile_paths();
for tempfile in tempfile_paths {
fs::remove_file(tempfile).ok();
}
}
if test_result.is_erroneous() {
check_result = test_result;
break;
} else {
check_result = TestResultKind::Pass;
}
}
match check_result {
TestResultKind::Fail { reason, hint } => {
if test_file.is_expected_failure() {
TestResultKind::ExpectedFailure { actual_reason: reason }
} else {
TestResultKind::Fail { reason, hint}
}
},
r => r,
}
}
fn collect_output(
mut command: process::Command,
command_line: CommandLine,
) -> (ProgramOutput, TestResultKind) {
let mut test_result_kind = TestResultKind::Pass;
let output = match command.output() {
Ok(o) => o,
Err(e) => {
let error_message = match e.kind() {
std::io::ErrorKind::NotFound => format!("shell '{}' does not exist", DEFAULT_SHELL).into(),
_ => e.to_string(),
};
return (ProgramOutput::empty(), TestResultKind::Error { message: error_message });
},
};
let program_output = ProgramOutput {
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
};
if !output.status.success() {
test_result_kind = TestResultKind::Fail {
reason: TestFailReason::UnsuccessfulExecution {
exit_status: output.status.code().unwrap_or_else(|| if output.status.success() { 0 } else { 1 }),
program_command_line: command_line.0,
},
hint: None,
};
}
(program_output, test_result_kind)
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CommandLine(pub String);
fn build_command(invocation: &Invocation,
test_file: &TestFile,
config: &Config) -> (process::Command, CommandLine) {
let mut variables = config.constants.clone();
variables.extend(test_file.variables());
let command_line: String = vars::resolve::invocation(invocation, &config, &mut variables);
let mut cmd = process::Command::new(DEFAULT_SHELL);
cmd.args(&["-c", &command_line]);
if !config.extra_executable_search_paths.is_empty() {
let os_path_separator = if cfg!(windows) { ";" } else { ":" };
let current_path = env::var("PATH").unwrap_or(String::new());
let paths_to_inject = config.extra_executable_search_paths.iter().map(|p| p.display().to_string()).collect::<Vec<_>>();
let os_path_to_inject = format!("{}{}{}", paths_to_inject.join(os_path_separator), os_path_separator, current_path);
cmd.env("PATH", os_path_to_inject);
}
(cmd, CommandLine(command_line))
}
impl std::fmt::Display for CommandLine {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(fmt)
}
}