1use std::collections::VecDeque;
10use std::io::{self, Read, Write};
11use std::sync::{Arc, Condvar, Mutex};
12use std::time::Duration;
13
14use super::handle::{
15 ExitStatus, ProcessError, ProcessHandle, ProcessKiller, ProcessSpawner, SpawnSpec,
16};
17
18#[derive(Clone, Debug)]
20pub struct MockProcessConfig {
21 pub pid: u32,
24 pub pgid: Option<u32>,
26 pub stdout: Vec<u8>,
28 pub stderr: Vec<u8>,
30 pub exit_status: Option<ExitStatus>,
34 pub force_timeout: bool,
38 pub spawn_error: Option<ProcessError>,
42 pub wait_error: Option<String>,
44}
45
46impl Default for MockProcessConfig {
47 fn default() -> Self {
48 Self {
49 pid: 99_999,
50 pgid: Some(99_999),
51 stdout: Vec::new(),
52 stderr: Vec::new(),
53 exit_status: Some(ExitStatus::from_code(0)),
54 force_timeout: false,
55 spawn_error: None,
56 wait_error: None,
57 }
58 }
59}
60
61impl MockProcessConfig {
62 pub fn completed(exit_code: i32) -> Self {
65 Self {
66 exit_status: Some(ExitStatus::from_code(exit_code)),
67 ..Self::default()
68 }
69 }
70
71 pub fn with_stdout(exit_code: i32, stdout: impl Into<Vec<u8>>) -> Self {
74 Self {
75 stdout: stdout.into(),
76 exit_status: Some(ExitStatus::from_code(exit_code)),
77 ..Self::default()
78 }
79 }
80
81 pub fn running() -> Self {
85 Self {
86 exit_status: None,
87 ..Self::default()
88 }
89 }
90}
91
92#[derive(Default)]
93struct MockSpawnerInner {
94 queue: VecDeque<(MockProcessConfig, Arc<MockState>)>,
95 captured: Vec<SpawnSpec>,
96 last_controller: Option<MockHandleController>,
97}
98
99pub struct MockSpawner {
102 inner: Mutex<MockSpawnerInner>,
103}
104
105impl Default for MockSpawner {
106 fn default() -> Self {
107 Self::new()
108 }
109}
110
111impl MockSpawner {
112 pub fn new() -> Self {
115 Self {
116 inner: Mutex::new(MockSpawnerInner::default()),
117 }
118 }
119
120 pub fn enqueue(&self, config: MockProcessConfig) -> MockHandleController {
125 let state = Arc::new(MockState::new(&config));
126 let controller = MockHandleController {
127 state: Arc::clone(&state),
128 };
129 let mut inner = self.inner.lock().expect("MockSpawner mutex poisoned");
130 inner.queue.push_back((config, state));
131 inner.last_controller = Some(controller.clone());
132 controller
133 }
134
135 pub fn captured(&self) -> Vec<SpawnSpec> {
137 self.inner
138 .lock()
139 .expect("MockSpawner mutex poisoned")
140 .captured
141 .clone()
142 }
143
144 pub fn last_controller(&self) -> Option<MockHandleController> {
147 self.inner
148 .lock()
149 .expect("MockSpawner mutex poisoned")
150 .last_controller
151 .clone()
152 }
153}
154
155impl ProcessSpawner for MockSpawner {
156 fn spawn(&self, spec: SpawnSpec) -> Result<Box<dyn ProcessHandle>, ProcessError> {
157 let (config, state) = {
158 let mut inner = self.inner.lock().expect("MockSpawner mutex poisoned");
159 inner.captured.push(spec);
160 inner.queue.pop_front().expect(
161 "MockSpawner: spawn() called with no enqueued configuration. Call \
162 MockSpawner::enqueue(...) before each expected spawn.",
163 )
164 };
165
166 if let Some(err) = config.spawn_error {
167 return Err(err);
168 }
169
170 let killer: Arc<dyn ProcessKiller> = Arc::new(MockKiller {
171 state: Arc::clone(&state),
172 });
173
174 Ok(Box::new(MockProcess {
175 pid: config.pid,
176 pgid: config.pgid,
177 killer,
178 state,
179 stdin_taken: false,
180 stdout_taken: false,
181 stderr_taken: false,
182 }))
183 }
184}
185
186#[derive(Clone)]
189pub struct MockHandleController {
190 state: Arc<MockState>,
191}
192
193impl MockHandleController {
194 pub fn append_stdout(&self, bytes: &[u8]) {
197 let mut data = self.state.stdout.lock().unwrap();
198 data.extend_from_slice(bytes);
199 self.state.stdout_cv.notify_all();
200 }
201
202 pub fn append_stderr(&self, bytes: &[u8]) {
204 let mut data = self.state.stderr.lock().unwrap();
205 data.extend_from_slice(bytes);
206 self.state.stderr_cv.notify_all();
207 }
208
209 pub fn complete_with(&self, status: ExitStatus) {
212 let mut exit = self.state.exit.lock().unwrap();
213 if exit.is_none() {
214 *exit = Some(ExitOutcome {
215 status,
216 killed: false,
217 });
218 }
219 drop(exit);
220 self.state.notify_exit_and_pipes();
221 }
222
223 pub fn was_killed(&self) -> bool {
225 self.state
226 .exit
227 .lock()
228 .unwrap()
229 .as_ref()
230 .map(|o| o.killed)
231 .unwrap_or(false)
232 }
233
234 pub fn stdin_written(&self) -> Vec<u8> {
237 self.state.stdin_written.lock().unwrap().clone()
238 }
239}
240
241struct MockState {
242 stdout: Mutex<Vec<u8>>,
244 stderr: Mutex<Vec<u8>>,
246 stdin_written: Mutex<Vec<u8>>,
248 exit: Mutex<Option<ExitOutcome>>,
250 exit_cv: Condvar,
251 stdout_cv: Condvar,
252 stderr_cv: Condvar,
253 force_timeout: bool,
255 wait_error: Option<String>,
256}
257
258#[derive(Clone, Copy, Debug)]
259struct ExitOutcome {
260 status: ExitStatus,
261 killed: bool,
262}
263
264impl MockState {
265 fn new(config: &MockProcessConfig) -> Self {
266 let exit = config.exit_status.map(|status| ExitOutcome {
267 status,
268 killed: false,
269 });
270 Self {
271 stdout: Mutex::new(config.stdout.clone()),
272 stderr: Mutex::new(config.stderr.clone()),
273 stdin_written: Mutex::new(Vec::new()),
274 exit: Mutex::new(exit),
275 exit_cv: Condvar::new(),
276 stdout_cv: Condvar::new(),
277 stderr_cv: Condvar::new(),
278 force_timeout: config.force_timeout,
279 wait_error: config.wait_error.clone(),
280 }
281 }
282
283 fn is_exited(&self) -> bool {
284 self.exit.lock().unwrap().is_some()
285 }
286
287 fn wait_for_exit(&self, timeout: Option<Duration>) -> Option<ExitOutcome> {
288 let mut exit = self.exit.lock().unwrap();
289 if let Some(timeout) = timeout {
290 if exit.is_none() {
291 let (next, result) = self.exit_cv.wait_timeout(exit, timeout).unwrap();
292 exit = next;
293 if result.timed_out() && exit.is_none() {
294 return None;
295 }
296 }
297 } else {
298 while exit.is_none() {
299 exit = self.exit_cv.wait(exit).unwrap();
300 }
301 }
302 *exit
303 }
304
305 fn record_kill(&self) {
306 let mut exit = self.exit.lock().unwrap();
307 if exit.is_none() {
308 *exit = Some(ExitOutcome {
309 status: ExitStatus::from_signal(9),
310 killed: true,
311 });
312 } else if let Some(outcome) = exit.as_mut() {
313 outcome.killed = true;
314 }
315 drop(exit);
316 self.notify_exit_and_pipes();
317 }
318
319 fn notify_exit_and_pipes(&self) {
320 self.exit_cv.notify_all();
321
322 {
326 let _stdout = self.stdout.lock().unwrap();
327 self.stdout_cv.notify_all();
328 }
329 {
330 let _stderr = self.stderr.lock().unwrap();
331 self.stderr_cv.notify_all();
332 }
333 }
334}
335
336pub struct MockProcess {
338 pid: u32,
339 pgid: Option<u32>,
340 killer: Arc<dyn ProcessKiller>,
341 state: Arc<MockState>,
342 stdin_taken: bool,
343 stdout_taken: bool,
344 stderr_taken: bool,
345}
346
347impl ProcessHandle for MockProcess {
348 fn pid(&self) -> Option<u32> {
349 Some(self.pid)
350 }
351
352 fn process_group_id(&self) -> Option<u32> {
353 self.pgid
354 }
355
356 fn killer(&self) -> Arc<dyn ProcessKiller> {
357 Arc::clone(&self.killer)
358 }
359
360 fn take_stdin(&mut self) -> Option<Box<dyn Write + Send>> {
361 if self.stdin_taken {
362 return None;
363 }
364 self.stdin_taken = true;
365 Some(Box::new(MockStdin {
366 state: Arc::clone(&self.state),
367 }))
368 }
369
370 fn take_stdout(&mut self) -> Option<Box<dyn Read + Send>> {
371 if self.stdout_taken {
372 return None;
373 }
374 self.stdout_taken = true;
375 Some(Box::new(MockStdoutReader {
376 state: Arc::clone(&self.state),
377 kind: PipeKind::Stdout,
378 }))
379 }
380
381 fn take_stderr(&mut self) -> Option<Box<dyn Read + Send>> {
382 if self.stderr_taken {
383 return None;
384 }
385 self.stderr_taken = true;
386 Some(Box::new(MockStdoutReader {
387 state: Arc::clone(&self.state),
388 kind: PipeKind::Stderr,
389 }))
390 }
391
392 fn wait_with_timeout(
393 &mut self,
394 timeout: Option<Duration>,
395 ) -> io::Result<(Option<ExitStatus>, bool)> {
396 if let Some(error) = self.state.wait_error.as_ref() {
397 return Err(io::Error::other(error.clone()));
398 }
399 if self.state.force_timeout {
400 self.state.record_kill();
401 return Ok((None, true));
402 }
403 let Some(timeout) = timeout else {
404 let outcome = self
405 .state
406 .wait_for_exit(None)
407 .expect("wait without timeout returned None");
408 return Ok((Some(outcome.status), false));
409 };
410 match self.state.wait_for_exit(Some(timeout)) {
411 Some(outcome) => Ok((Some(outcome.status), false)),
412 None => {
413 self.state.record_kill();
414 Ok((None, true))
415 }
416 }
417 }
418
419 fn wait(&mut self) -> io::Result<ExitStatus> {
420 if let Some(error) = self.state.wait_error.as_ref() {
421 return Err(io::Error::other(error.clone()));
422 }
423 let outcome = self
424 .state
425 .wait_for_exit(None)
426 .expect("wait without timeout returned None");
427 Ok(outcome.status)
428 }
429}
430
431struct MockStdin {
432 state: Arc<MockState>,
433}
434
435impl Write for MockStdin {
436 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
437 self.state
438 .stdin_written
439 .lock()
440 .unwrap()
441 .extend_from_slice(buf);
442 Ok(buf.len())
443 }
444
445 fn flush(&mut self) -> io::Result<()> {
446 Ok(())
447 }
448}
449
450#[derive(Clone, Copy)]
451enum PipeKind {
452 Stdout,
453 Stderr,
454}
455
456struct MockStdoutReader {
457 state: Arc<MockState>,
458 kind: PipeKind,
459}
460
461impl MockStdoutReader {
462 fn pipe_lock(&self) -> &Mutex<Vec<u8>> {
463 match self.kind {
464 PipeKind::Stdout => &self.state.stdout,
465 PipeKind::Stderr => &self.state.stderr,
466 }
467 }
468
469 fn pipe_cv(&self) -> &Condvar {
470 match self.kind {
471 PipeKind::Stdout => &self.state.stdout_cv,
472 PipeKind::Stderr => &self.state.stderr_cv,
473 }
474 }
475}
476
477impl Read for MockStdoutReader {
478 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
479 let lock = self.pipe_lock();
480 let cv = self.pipe_cv();
481 let mut data = lock.lock().unwrap();
482 loop {
483 if !data.is_empty() {
484 let n = data.len().min(buf.len());
485 buf[..n].copy_from_slice(&data[..n]);
486 data.drain(..n);
487 return Ok(n);
488 }
489 if self.state.is_exited() {
492 return Ok(0);
493 }
494 data = cv.wait(data).unwrap();
495 }
496 }
497}
498
499struct MockKiller {
500 state: Arc<MockState>,
501}
502
503impl ProcessKiller for MockKiller {
504 fn kill(&self) {
505 self.state.record_kill();
506 }
507}