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