Skip to main content

xacli_testing/testing/
execute.rs

1use 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    /// Execute a test case and return the test result
24    ///
25    /// This method parses the arguments, runs the application with a mock context,
26    /// and validates all assertions.
27    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        // Extract test config from test case
32        let config = TestCaseConfig {
33            commands: test_case.commands.clone(),
34            args: test_case.args.clone(),
35        };
36
37        // Build full args: [app_name, ...commands, ...args]
38        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 execution failed, append error to stderr
79        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        // Build ExecuteResult for assertions
87        let execute_result = ExecuteResult {
88            start_time,
89            duration,
90            code,
91            stdout: stdout.clone(),
92            stderr: stderr.clone(),
93        };
94
95        // Run all assertions
96        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        // Determine status based on assertions
109        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    /// Execute all tests in a spec file and return results
130    ///
131    /// This method iterates through all suites and tests in the spec file,
132    /// converts each test case, executes it, and collects results.
133    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            // Skip suite if marked as skip
138            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                // Skip test if marked as skip
146                if test_case.features.skip {
147                    continue;
148                }
149
150                // Convert and execute test case
151                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                        // Update config with commands from spec
155                        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}