xacli_testing/testing/
execute.rs1use std::collections::VecDeque;
2
3use indexmap::IndexMap;
4
5use super::{ctx::MockContext, test::TestCase};
6use crate::{
7 report::xacli::{
8 TestCaseConfig, TestCaseResult, TestCaseStatus, TestError, TestFailure, TestSuiteResult,
9 TestSuitesResult,
10 },
11 spec, TestingApp,
12};
13
14pub struct ExecuteResult {
15 pub start_time: std::time::Instant,
16 pub duration: std::time::Duration,
17 pub code: i64,
18 pub stdout: String,
19 pub stderr: String,
20}
21
22impl TestingApp {
23 pub fn execute(&self, test_case: TestCase) -> TestCaseResult {
28 let start_time = std::time::Instant::now();
29 let timestamp = std::time::SystemTime::now();
30
31 let config = TestCaseConfig {
33 commands: test_case.commands.clone(),
34 args: test_case.args.clone(),
35 };
36
37 let mut full_args = vec![self.app.info.name.clone()];
39 full_args.extend(test_case.commands.clone());
40 full_args.extend(test_case.args.clone());
41
42 let ctx_info = match self.app.parse(full_args) {
43 Ok(info) => info,
44 Err(e) => {
45 return TestCaseResult {
46 config,
47 timestamp,
48 duration: std::time::Duration::new(0, 0),
49 stdout: String::new(),
50 stderr: e.to_string(),
51 status: TestCaseStatus::Error {
52 error: TestError {
53 message: "Failed to parse arguments".to_string(),
54 },
55 },
56 };
57 }
58 };
59
60 let mut ctx = MockContext::new(
61 ctx_info,
62 VecDeque::from(test_case.input_events.clone()),
63 self.config.tty,
64 );
65
66 let result = self.app.execute_with_ctx(&mut ctx);
67 let code = match &result {
68 Ok(_) => 0,
69 Err(_) => 1,
70 };
71
72 let end_time = std::time::Instant::now();
73 let duration = end_time.duration_since(start_time);
74
75 let stdout = ctx.stdout_plain();
76 let mut stderr = ctx.stderr_plain();
77
78 if let Err(e) = &result {
80 if !stderr.is_empty() {
81 stderr.push('\n');
82 }
83 stderr.push_str(&e.to_string());
84 }
85
86 let execute_result = ExecuteResult {
88 start_time,
89 duration,
90 code,
91 stdout: stdout.clone(),
92 stderr: stderr.clone(),
93 };
94
95 let mut all_success = true;
97 let mut error_messages = Vec::new();
98
99 for asserter in &test_case.assertions {
100 if let Ok(result) = asserter.validate(&execute_result) {
101 if !result.success {
102 all_success = false;
103 error_messages.extend(result.messages);
104 }
105 }
106 }
107
108 let status = if all_success {
110 TestCaseStatus::Passed
111 } else {
112 TestCaseStatus::Failed {
113 failure: TestFailure {
114 message: error_messages.join("\n"),
115 },
116 }
117 };
118
119 TestCaseResult {
120 config,
121 timestamp,
122 duration,
123 stdout,
124 stderr,
125 status,
126 }
127 }
128
129 pub fn execute_spec(&self, spec: &spec::SpecFile) -> TestSuitesResult {
134 let mut suites_result = IndexMap::new();
135
136 for (suite_name, suite) in &spec.suite {
137 if suite.features.skip {
139 continue;
140 }
141
142 let mut suite_result = IndexMap::new();
143
144 for (test_name, test_case) in &suite.test {
145 if test_case.features.skip {
147 continue;
148 }
149
150 let test_result = match TestCase::try_from((test_name.as_str(), test_case)) {
152 Ok(testing_case) => {
153 let mut result = self.execute(testing_case);
154 result.config.commands = test_case.commands.clone();
156 result
157 }
158 Err(e) => TestCaseResult {
159 config: TestCaseConfig {
160 commands: test_case.commands.clone(),
161 args: test_case.args.clone(),
162 },
163 timestamp: std::time::SystemTime::now(),
164 duration: std::time::Duration::default(),
165 stdout: String::new(),
166 stderr: e.to_string(),
167 status: TestCaseStatus::Error {
168 error: TestError {
169 message: format!("Failed to convert test case: {}", e),
170 },
171 },
172 },
173 };
174
175 suite_result.insert(test_name.clone(), test_result);
176 }
177
178 if !suite_result.is_empty() {
179 suites_result.insert(
180 suite_name.clone(),
181 TestSuiteResult {
182 tests: suite_result,
183 },
184 );
185 }
186 }
187
188 TestSuitesResult {
189 suites: suites_result,
190 }
191 }
192}