1use std::env;
11use std::fmt::Debug;
12use std::fs;
13use std::hash::{Hash, Hasher};
14use std::io::{self, BufRead, Seek};
15use std::panic;
16use std::process;
17
18use crate::child_wrapper::ChildWrapper;
19use crate::cmdline;
20use crate::error::*;
21
22const OCCURS_ENV: &str = "RUSTY_FORK_OCCURS";
23const OCCURS_TERM_LENGTH: usize = 17; pub trait TestExitStatus<E: Debug> {
26 fn status(self) -> std::result::Result<(), E>;
27}
28
29impl<T, E: Debug> TestExitStatus<E> for std::result::Result<T, E> {
30 fn status(self) -> std::result::Result<(), E> {
31 match self {
32 Ok(_) => Ok(()),
33 Err(err) => Err(err),
34 }
35 }
36}
37
38impl<T> TestExitStatus<()> for Option<T> {
39 fn status(self) -> std::result::Result<(), ()> {
40 match self {
41 Some(_) => Ok(()),
42 None => Err(()),
43 }
44 }
45}
46
47impl TestExitStatus<()> for () {
48 fn status(self) -> std::result::Result<(), ()> {
49 Ok(())
50 }
51}
52
53pub fn fork<ID, MODIFIER, PARENT, CHILD, R, T, E: Debug>(
109 test_name: &str,
110 fork_id: ID,
111 process_modifier: MODIFIER,
112 in_parent: PARENT,
113 in_child: CHILD,
114) -> Result<R>
115where
116 ID: Hash,
117 MODIFIER: FnOnce(&mut process::Command),
118 PARENT: FnOnce(&mut ChildWrapper, &mut fs::File) -> R,
119 T: TestExitStatus<E>,
120 CHILD: FnOnce() -> T,
121{
122 let fork_id = id_str(fork_id);
123
124 let mut return_value = None;
127 let mut process_modifier = Some(process_modifier);
128 let mut in_parent = Some(in_parent);
129 let mut in_child = Some(in_child);
130
131 fork_impl(
132 test_name,
133 fork_id,
134 &mut |cmd| process_modifier.take().unwrap()(cmd),
135 &mut |child, file| return_value = Some(in_parent.take().unwrap()(child, file)),
136 &mut || in_child.take().unwrap()(),
137 )
138 .map(|_| return_value.unwrap())
139}
140
141fn fork_impl<E: Debug, T: TestExitStatus<E>>(
142 test_name: &str,
143 fork_id: String,
144 process_modifier: &mut dyn FnMut(&mut process::Command),
145 in_parent: &mut dyn FnMut(&mut ChildWrapper, &mut fs::File),
146 in_child: &mut dyn FnMut() -> T,
147) -> Result<()> {
148 let mut occurs = env::var(OCCURS_ENV).unwrap_or_else(|_| String::new());
149 if occurs.contains(&fork_id) {
150 match panic::catch_unwind(panic::AssertUnwindSafe(in_child)) {
151 Ok(test_result) => match test_result.status() {
152 Ok(_) => process::exit(0),
153 Err(err) => {
154 eprintln!("Test failure cause by: {:?}", err);
155 process::exit(70 )
156 }
157 },
158 Err(_) => process::exit(70 ),
164 }
165 } else {
166 if occurs.len() > 16 * OCCURS_TERM_LENGTH {
168 panic!("rusty-fork: Not forking due to >=16 levels of recursion");
169 }
170
171 let file = tempfile::tempfile()?;
172
173 struct KillOnDrop(ChildWrapper, fs::File);
174 impl Drop for KillOnDrop {
175 fn drop(&mut self) {
176 let _ = self.0.kill();
178
179 let _ = self.1.seek(io::SeekFrom::Start(0));
186
187 let mut buf = Vec::new();
188 let mut br = io::BufReader::new(&mut self.1);
189 loop {
190 if br.read_until(b'\n', &mut buf).is_err() {
195 break;
196 }
197 if buf.is_empty() {
198 break;
199 }
200
201 print!("{}", String::from_utf8_lossy(&buf));
204 buf.clear();
205 }
206 }
207 }
208
209 occurs.push_str(&fork_id);
210 let mut command =
211 process::Command::new(env::current_exe().expect("current_exe() failed, cannot fork"));
212 command
213 .args(cmdline::strip_cmdline(env::args())?)
214 .args(cmdline::RUN_TEST_ARGS)
215 .arg(test_name)
216 .env(OCCURS_ENV, &occurs)
217 .stdin(process::Stdio::null())
218 .stdout(file.try_clone()?)
219 .stderr(file.try_clone()?);
220 process_modifier(&mut command);
221
222 let mut child = command
223 .spawn()
224 .map(ChildWrapper::new)
225 .map(|p| KillOnDrop(p, file))?;
226
227 let ret = in_parent(&mut child.0, &mut child.1);
228
229 Ok(ret)
230 }
231}
232
233fn id_str<ID: Hash>(id: ID) -> String {
234 let mut hasher = fnv::FnvHasher::default();
235 id.hash(&mut hasher);
236
237 return format!(":{:016X}", hasher.finish());
238}
239
240#[cfg(test)]
241mod test {
242 use std::io::Read;
243 use std::thread;
244
245 use super::*;
246
247 fn sleep(ms: u64) {
248 thread::sleep(::std::time::Duration::from_millis(ms));
249 }
250
251 fn capturing_output(cmd: &mut process::Command) {
252 cmd.stdout(process::Stdio::piped())
255 .stderr(process::Stdio::inherit());
256 }
257
258 fn inherit_output(cmd: &mut process::Command) {
259 cmd.stdout(process::Stdio::inherit())
260 .stderr(process::Stdio::inherit());
261 }
262
263 fn wait_for_child_output(child: &mut ChildWrapper, _file: &mut fs::File) -> String {
264 let mut output = String::new();
265 child
266 .inner_mut()
267 .stdout
268 .as_mut()
269 .unwrap()
270 .read_to_string(&mut output)
271 .unwrap();
272 assert!(child.wait().unwrap().success());
273 output
274 }
275
276 fn wait_for_child(child: &mut ChildWrapper, _file: &mut fs::File) {
277 assert!(child.wait().unwrap().success());
278 }
279
280 #[test]
281 fn fork_basically_works() {
282 let status = fork(
283 "fork::test::fork_basically_works",
284 rusty_fork_id!(),
285 |_| (),
286 |child, _| child.wait().unwrap(),
287 || println!("hello from child"),
288 )
289 .unwrap();
290 assert!(status.success());
291 }
292
293 #[test]
294 fn child_output_captured_and_repeated() {
295 let output = fork(
296 "fork::test::child_output_captured_and_repeated",
297 rusty_fork_id!(),
298 capturing_output,
299 wait_for_child_output,
300 || {
301 fork(
302 "fork::test::child_output_captured_and_repeated",
303 rusty_fork_id!(),
304 |_| (),
305 wait_for_child,
306 || println!("hello from child"),
307 )
308 .unwrap()
309 },
310 )
311 .unwrap();
312 assert!(output.contains("hello from child"));
313 }
314
315 #[test]
316 fn child_killed_if_parent_exits_first() {
317 let output = fork(
318 "fork::test::child_killed_if_parent_exits_first",
319 rusty_fork_id!(),
320 capturing_output,
321 wait_for_child_output,
322 || {
323 fork(
324 "fork::test::child_killed_if_parent_exits_first",
325 rusty_fork_id!(),
326 inherit_output,
327 |_, _| (),
328 || {
329 sleep(1_000);
330 println!("hello from child");
331 },
332 )
333 .unwrap()
334 },
335 )
336 .unwrap();
337
338 sleep(2_000);
339 assert!(
340 !output.contains("hello from child"),
341 "Had unexpected output:\n{}",
342 output
343 );
344 }
345
346 #[test]
347 fn child_killed_if_parent_panics_first() {
348 let output = fork(
349 "fork::test::child_killed_if_parent_panics_first",
350 rusty_fork_id!(),
351 capturing_output,
352 wait_for_child_output,
353 || {
354 assert!(panic::catch_unwind(panic::AssertUnwindSafe(|| fork(
355 "fork::test::child_killed_if_parent_panics_first",
356 rusty_fork_id!(),
357 inherit_output,
358 |_, _| panic!("testing a panic, nothing to see here"),
359 || {
360 sleep(1_000);
361 println!("hello from child");
362 }
363 )
364 .unwrap()))
365 .is_err());
366 },
367 )
368 .unwrap();
369
370 sleep(2_000);
371 assert!(
372 !output.contains("hello from child"),
373 "Had unexpected output:\n{}",
374 output
375 );
376 }
377
378 #[test]
379 fn child_aborted_if_panics() {
380 let status = fork(
381 "fork::test::child_aborted_if_panics",
382 rusty_fork_id!(),
383 |_| (),
384 |child, _| child.wait().unwrap(),
385 || panic!("testing a panic, nothing to see here"),
386 )
387 .unwrap();
388 assert_eq!(70, status.code().unwrap());
389 }
390}