perfgate_fake/
process_runner.rs1use crate::{AdapterError, CommandSpec, ProcessRunner, RunResult};
4use std::collections::HashMap;
5use std::sync::{Arc, Mutex};
6
7#[derive(Debug, Default, Clone)]
51pub struct FakeProcessRunner {
52 results: Arc<Mutex<HashMap<String, RunResult>>>,
53 fallback: Arc<Mutex<Option<RunResult>>>,
54 history: Arc<Mutex<Vec<CommandSpec>>>,
55}
56
57impl FakeProcessRunner {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn set_result(&self, argv: &[&str], result: RunResult) {
68 let key = argv.join(" ");
69 self.results.lock().expect("lock").insert(key, result);
70 }
71
72 pub fn set_fallback(&self, result: RunResult) {
77 *self.fallback.lock().expect("lock") = Some(result);
78 }
79
80 pub fn history(&self) -> Vec<CommandSpec> {
84 self.history.lock().expect("lock").clone()
85 }
86
87 pub fn clear(&self) {
89 self.results.lock().expect("lock").clear();
90 *self.fallback.lock().expect("lock") = None;
91 self.history.lock().expect("lock").clear();
92 }
93
94 pub fn call_count(&self) -> usize {
96 self.history.lock().expect("lock").len()
97 }
98
99 pub fn was_run(&self, argv: &[&str]) -> bool {
101 let key = argv.join(" ");
102 self.history
103 .lock()
104 .expect("lock")
105 .iter()
106 .any(|spec| spec.argv.join(" ") == key)
107 }
108
109 pub fn nth_call(&self, n: usize) -> Option<CommandSpec> {
111 self.history.lock().expect("lock").get(n).cloned()
112 }
113}
114
115impl ProcessRunner for FakeProcessRunner {
116 fn run(&self, spec: &CommandSpec) -> Result<RunResult, AdapterError> {
117 self.history.lock().expect("lock").push(spec.clone());
118
119 let key = spec.argv.join(" ");
120 let results = self.results.lock().expect("lock");
121 if let Some(res) = results.get(&key) {
122 return Ok(res.clone());
123 }
124
125 let fallback = self.fallback.lock().expect("lock");
126 if let Some(res) = &*fallback {
127 return Ok(res.clone());
128 }
129
130 Err(AdapterError::Other(format!(
131 "FakeProcessRunner: no result configured for command: {:?}",
132 spec.argv
133 )))
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 fn make_result(exit_code: i32, wall_ms: u64) -> RunResult {
142 RunResult {
143 wall_ms,
144 exit_code,
145 timed_out: false,
146 cpu_ms: None,
147 page_faults: None,
148 ctx_switches: None,
149 max_rss_kb: None,
150 io_read_bytes: None,
151 io_write_bytes: None,
152 network_packets: None,
153 energy_uj: None,
154 binary_bytes: None,
155 stdout: vec![],
156 stderr: vec![],
157 }
158 }
159
160 fn make_spec(argv: Vec<&str>) -> CommandSpec {
161 CommandSpec {
162 name: argv.first().unwrap_or(&"unknown").to_string(),
163 argv: argv.into_iter().map(String::from).collect(),
164 cwd: None,
165 env: vec![],
166 timeout: None,
167 output_cap_bytes: 1024,
168 }
169 }
170
171 #[test]
172 fn new_runner_is_empty() {
173 let runner = FakeProcessRunner::new();
174 assert!(runner.history().is_empty());
175 assert_eq!(runner.call_count(), 0);
176 }
177
178 #[test]
179 fn set_result_returns_configured() {
180 let runner = FakeProcessRunner::new();
181 runner.set_result(&["echo", "hello"], make_result(0, 50));
182
183 let result = runner.run(&make_spec(vec!["echo", "hello"])).unwrap();
184 assert_eq!(result.exit_code, 0);
185 assert_eq!(result.wall_ms, 50);
186 }
187
188 #[test]
189 fn fallback_is_used_when_no_match() {
190 let runner = FakeProcessRunner::new();
191 runner.set_fallback(make_result(42, 100));
192
193 let result = runner.run(&make_spec(vec!["unknown"])).unwrap();
194 assert_eq!(result.exit_code, 42);
195 assert_eq!(result.wall_ms, 100);
196 }
197
198 #[test]
199 fn specific_result_takes_precedence_over_fallback() {
200 let runner = FakeProcessRunner::new();
201 runner.set_result(&["echo"], make_result(0, 10));
202 runner.set_fallback(make_result(1, 999));
203
204 let result = runner.run(&make_spec(vec!["echo"])).unwrap();
205 assert_eq!(result.exit_code, 0);
206 }
207
208 #[test]
209 fn error_when_no_result_configured() {
210 let runner = FakeProcessRunner::new();
211 let result = runner.run(&make_spec(vec!["unknown"]));
212 assert!(result.is_err());
213 }
214
215 #[test]
216 fn history_records_commands() {
217 let runner = FakeProcessRunner::new();
218 runner.set_fallback(make_result(0, 0));
219
220 runner.run(&make_spec(vec!["cmd1"])).unwrap();
221 runner.run(&make_spec(vec!["cmd2", "arg"])).unwrap();
222
223 let history = runner.history();
224 assert_eq!(history.len(), 2);
225 assert_eq!(history[0].argv, vec!["cmd1"]);
226 assert_eq!(history[1].argv, vec!["cmd2", "arg"]);
227 }
228
229 #[test]
230 fn was_run_checks_history() {
231 let runner = FakeProcessRunner::new();
232 runner.set_fallback(make_result(0, 0));
233
234 assert!(!runner.was_run(&["echo"]));
235
236 runner.run(&make_spec(vec!["echo", "hello"])).unwrap();
237
238 assert!(runner.was_run(&["echo", "hello"]));
239 assert!(!runner.was_run(&["echo", "goodbye"]));
240 }
241
242 #[test]
243 fn nth_call_returns_correct_command() {
244 let runner = FakeProcessRunner::new();
245 runner.set_fallback(make_result(0, 0));
246
247 runner.run(&make_spec(vec!["first"])).unwrap();
248 runner.run(&make_spec(vec!["second"])).unwrap();
249
250 assert_eq!(runner.nth_call(0).unwrap().argv, vec!["first"]);
251 assert_eq!(runner.nth_call(1).unwrap().argv, vec!["second"]);
252 assert!(runner.nth_call(2).is_none());
253 }
254
255 #[test]
256 fn clear_resets_everything() {
257 let runner = FakeProcessRunner::new();
258 runner.set_result(&["cmd"], make_result(0, 0));
259 runner.set_fallback(make_result(1, 1));
260 runner.run(&make_spec(vec!["cmd"])).unwrap();
261
262 runner.clear();
263
264 assert!(runner.history().is_empty());
265 assert!(runner.run(&make_spec(vec!["cmd"])).is_err());
266 }
267
268 #[test]
269 fn thread_safe_sharing() {
270 use std::sync::Arc;
271 use std::thread;
272
273 let runner = Arc::new(FakeProcessRunner::new());
274 runner.set_fallback(make_result(0, 0));
275
276 let handles: Vec<_> = (0..4)
277 .map(|i| {
278 let r = runner.clone();
279 thread::spawn(move || {
280 r.run(&make_spec(vec!["cmd", &i.to_string()])).unwrap();
281 })
282 })
283 .collect();
284
285 for h in handles {
286 h.join().unwrap();
287 }
288
289 assert_eq!(runner.call_count(), 4);
290 }
291}