1 //-
2 // Copyright 2018 Jason Lingle
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9 
10 use std::fs;
11 use std::env;
12 use std::hash::{Hash, Hasher};
13 use std::io::{self, BufRead, Seek};
14 use std::panic;
15 use std::process;
16 
17 use fnv;
18 use tempfile;
19 
20 use crate::cmdline;
21 use crate::error::*;
22 use crate::child_wrapper::ChildWrapper;
23 
24 const OCCURS_ENV: &str = "RUSTY_FORK_OCCURS";
25 const OCCURS_TERM_LENGTH: usize = 17; /* ':' plus 16 hexits */
26 
27 /// Simulate a process fork.
28 ///
29 /// The function documentation here only lists information unique to calling it
30 /// directly; please see the crate documentation for more details on how the
31 /// forking process works.
32 ///
33 /// Since this is not a true process fork, the calling code must be structured
34 /// to ensure that the child process, upon starting from the same entry point,
35 /// also reaches this same `fork()` call. Recursive forks are supported; the
36 /// child branch is taken from all child processes of the fork even if it is
37 /// not directly the child of a particular branch. However, encountering the
38 /// same fork point more than once in a single execution sequence of a child
39 /// process is not (e.g., putting this call in a recursive function) and
40 /// results in unspecified behaviour.
41 ///
42 /// The child's output is buffered into an anonymous temporary file. Before
43 /// this call returns, this output is copied to the parent's standard output
44 /// (passing through the redirect mechanism Rust test uses).
45 ///
46 /// `test_name` must exactly match the full path of the test function being
47 /// run.
48 ///
49 /// `fork_id` is a unique identifier identifying this particular fork location.
50 /// This *must* be stable across processes of the same executable; pointers are
51 /// not suitable stable, and string constants may not be suitably unique. The
52 /// [`rusty_fork_id!()`](macro.rusty_fork_id.html) macro is the recommended way
53 /// to supply this parameter.
54 ///
55 /// If this is the parent process, `in_parent` is invoked, and the return value
56 /// becomes the return value from this function. The callback is passed a
57 /// handle to the file which receives the child's output. If is the callee's
58 /// responsibility to wait for the child to exit. If this is the child process,
59 /// `in_child` is invoked, and when the callback returns, the child process
60 /// exits.
61 ///
62 /// If `in_parent` returns or panics before the child process has terminated,
63 /// the child process is killed.
64 ///
65 /// If `in_child` panics, the child process exits with a failure code
66 /// immediately rather than let the panic propagate out of the `fork()` call.
67 ///
68 /// `process_modifier` is invoked on the `std::process::Command` immediately
69 /// before spawning the new process. The callee may modify the process
70 /// parameters if desired, but should not do anything that would modify or
71 /// remove any environment variables beginning with `RUSTY_FORK_`.
72 ///
73 /// ## Panics
74 ///
75 /// Panics if the environment indicates that there are already at least 16
76 /// levels of fork nesting.
77 ///
78 /// Panics if `std::env::current_exe()` fails determine the path to the current
79 /// executable.
80 ///
81 /// Panics if any argument to the current process is not valid UTF-8.
fork<ID, MODIFIER, PARENT, CHILD, R>( test_name: &str, fork_id: ID, process_modifier: MODIFIER, in_parent: PARENT, in_child: CHILD) -> Result<R> where ID : Hash, MODIFIER : FnOnce (&mut process::Command), PARENT : FnOnce (&mut ChildWrapper, &mut fs::File) -> R, CHILD : FnOnce ()82 pub fn fork<ID, MODIFIER, PARENT, CHILD, R>(
83     test_name: &str,
84     fork_id: ID,
85     process_modifier: MODIFIER,
86     in_parent: PARENT,
87     in_child: CHILD) -> Result<R>
88 where
89     ID : Hash,
90     MODIFIER : FnOnce (&mut process::Command),
91     PARENT : FnOnce (&mut ChildWrapper, &mut fs::File) -> R,
92     CHILD : FnOnce ()
93 {
94     let fork_id = id_str(fork_id);
95 
96     // Erase the generics so we don't instantiate the actual implementation for
97     // every single test
98     let mut return_value = None;
99     let mut process_modifier = Some(process_modifier);
100     let mut in_parent = Some(in_parent);
101     let mut in_child = Some(in_child);
102 
103     fork_impl(test_name, fork_id,
104               &mut |cmd| process_modifier.take().unwrap()(cmd),
105               &mut |child, file| return_value = Some(
106                   in_parent.take().unwrap()(child, file)),
107               &mut || in_child.take().unwrap()())
108         .map(|_| return_value.unwrap())
109 }
110 
fork_impl(test_name: &str, fork_id: String, process_modifier: &mut dyn FnMut (&mut process::Command), in_parent: &mut dyn FnMut (&mut ChildWrapper, &mut fs::File), in_child: &mut dyn FnMut ()) -> Result<()>111 fn fork_impl(test_name: &str, fork_id: String,
112              process_modifier: &mut dyn FnMut (&mut process::Command),
113              in_parent: &mut dyn FnMut (&mut ChildWrapper, &mut fs::File),
114              in_child: &mut dyn FnMut ()) -> Result<()> {
115     let mut occurs = env::var(OCCURS_ENV).unwrap_or_else(|_| String::new());
116     if occurs.contains(&fork_id) {
117         match panic::catch_unwind(panic::AssertUnwindSafe(in_child)) {
118             Ok(_) => process::exit(0),
119             // Assume that the default panic handler already printed something
120             //
121             // We don't use process::abort() since it produces core dumps on
122             // some systems and isn't something more special than a normal
123             // panic.
124             Err(_) => process::exit(70 /* EX_SOFTWARE */),
125         }
126     } else {
127         // Prevent misconfiguration creating a fork bomb
128         if occurs.len() > 16 * OCCURS_TERM_LENGTH {
129             panic!("rusty-fork: Not forking due to >=16 levels of recursion");
130         }
131 
132         let file = tempfile::tempfile()?;
133 
134         struct KillOnDrop(ChildWrapper, fs::File);
135         impl Drop for KillOnDrop {
136             fn drop(&mut self) {
137                 // Kill the child if it hasn't exited yet
138                 let _ = self.0.kill();
139 
140                 // Copy the child's output to our own
141                 // Awkwardly, `print!()` and `println!()` are our only gateway
142                 // to putting things in the captured output. Generally test
143                 // output really is text, so work on that assumption and read
144                 // line-by-line, converting lossily into UTF-8 so we can
145                 // println!() it.
146                 let _ = self.1.seek(io::SeekFrom::Start(0));
147 
148                 let mut buf = Vec::new();
149                 let mut br = io::BufReader::new(&mut self.1);
150                 loop {
151                     // We can't use read_line() or lines() since they break if
152                     // there's any non-UTF-8 output at all. \n occurs at the
153                     // end of the line endings on all major platforms, so we
154                     // can just use that as a delimiter.
155                     if br.read_until(b'\n', &mut buf).is_err() {
156                         break;
157                     }
158                     if buf.is_empty() {
159                         break;
160                     }
161 
162                     // not println!() because we already have a line ending
163                     // from above.
164                     print!("{}", String::from_utf8_lossy(&buf));
165                     buf.clear();
166                 }
167             }
168         }
169 
170         occurs.push_str(&fork_id);
171         let mut command =
172             process::Command::new(
173                 env::current_exe()
174                     .expect("current_exe() failed, cannot fork"));
175         command
176             .args(cmdline::strip_cmdline(env::args())?)
177             .args(cmdline::RUN_TEST_ARGS)
178             .arg(test_name)
179             .env(OCCURS_ENV, &occurs)
180             .stdin(process::Stdio::null())
181             .stdout(file.try_clone()?)
182             .stderr(file.try_clone()?);
183         process_modifier(&mut command);
184 
185         let mut child = command.spawn().map(ChildWrapper::new)
186             .map(|p| KillOnDrop(p, file))?;
187 
188         let ret = in_parent(&mut child.0, &mut child.1);
189 
190         Ok(ret)
191     }
192 }
193 
id_str<ID : Hash>(id: ID) -> String194 fn id_str<ID : Hash>(id: ID) -> String {
195     let mut hasher = fnv::FnvHasher::default();
196     id.hash(&mut hasher);
197 
198     return format!(":{:016X}", hasher.finish());
199 }
200 
201 #[cfg(test)]
202 mod test {
203     use std::io::Read;
204     use std::thread;
205 
206     use super::*;
207 
sleep(ms: u64)208     fn sleep(ms: u64) {
209         thread::sleep(::std::time::Duration::from_millis(ms));
210     }
211 
capturing_output(cmd: &mut process::Command)212     fn capturing_output(cmd: &mut process::Command) {
213         // Only actually capture stdout since we can't use
214         // wait_with_output() since it for some reason consumes the `Child`.
215         cmd.stdout(process::Stdio::piped())
216             .stderr(process::Stdio::inherit());
217     }
218 
inherit_output(cmd: &mut process::Command)219     fn inherit_output(cmd: &mut process::Command) {
220         cmd.stdout(process::Stdio::inherit())
221             .stderr(process::Stdio::inherit());
222     }
223 
wait_for_child_output(child: &mut ChildWrapper, _file: &mut fs::File) -> String224     fn wait_for_child_output(child: &mut ChildWrapper,
225                              _file: &mut fs::File) -> String {
226         let mut output = String::new();
227         child.inner_mut().stdout.as_mut().unwrap()
228             .read_to_string(&mut output).unwrap();
229         assert!(child.wait().unwrap().success());
230         output
231     }
232 
wait_for_child(child: &mut ChildWrapper, _file: &mut fs::File)233     fn wait_for_child(child: &mut ChildWrapper,
234                       _file: &mut fs::File) {
235         assert!(child.wait().unwrap().success());
236     }
237 
238     #[test]
fork_basically_works()239     fn fork_basically_works() {
240         let status =
241             fork("fork::test::fork_basically_works", rusty_fork_id!(),
242                  |_| (),
243                  |child, _| child.wait().unwrap(),
244                  || println!("hello from child")).unwrap();
245         assert!(status.success());
246     }
247 
248     #[test]
child_output_captured_and_repeated()249     fn child_output_captured_and_repeated() {
250         let output = fork(
251             "fork::test::child_output_captured_and_repeated",
252             rusty_fork_id!(),
253             capturing_output, wait_for_child_output,
254             || fork(
255                 "fork::test::child_output_captured_and_repeated",
256                 rusty_fork_id!(),
257                 |_| (), wait_for_child,
258                 || println!("hello from child")).unwrap())
259             .unwrap();
260         assert!(output.contains("hello from child"));
261     }
262 
263     #[test]
child_killed_if_parent_exits_first()264     fn child_killed_if_parent_exits_first() {
265         let output = fork(
266             "fork::test::child_killed_if_parent_exits_first",
267             rusty_fork_id!(),
268             capturing_output, wait_for_child_output,
269             || fork(
270                 "fork::test::child_killed_if_parent_exits_first",
271                 rusty_fork_id!(),
272                 inherit_output, |_, _| (),
273                 || {
274                     sleep(1_000);
275                     println!("hello from child");
276                 }).unwrap()).unwrap();
277 
278         sleep(2_000);
279         assert!(!output.contains("hello from child"),
280                 "Had unexpected output:\n{}", output);
281     }
282 
283     #[test]
child_killed_if_parent_panics_first()284     fn child_killed_if_parent_panics_first() {
285         let output = fork(
286             "fork::test::child_killed_if_parent_panics_first",
287             rusty_fork_id!(),
288             capturing_output, wait_for_child_output,
289             || {
290                 assert!(
291                     panic::catch_unwind(panic::AssertUnwindSafe(|| fork(
292                         "fork::test::child_killed_if_parent_panics_first",
293                         rusty_fork_id!(),
294                         inherit_output,
295                         |_, _| panic!("testing a panic, nothing to see here"),
296                         || {
297                             sleep(1_000);
298                             println!("hello from child");
299                         }).unwrap())).is_err());
300             }).unwrap();
301 
302         sleep(2_000);
303         assert!(!output.contains("hello from child"),
304                 "Had unexpected output:\n{}", output);
305     }
306 
307     #[test]
child_aborted_if_panics()308     fn child_aborted_if_panics() {
309         let status = fork(
310             "fork::test::child_aborted_if_panics",
311             rusty_fork_id!(),
312             |_| (),
313             |child, _| child.wait().unwrap(),
314             || panic!("testing a panic, nothing to see here")).unwrap();
315         assert_eq!(70, status.code().unwrap());
316     }
317 }
318