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