Repository: xd009642/tarpaulin
Pull request #962 (2022-02-19 15:47:03)

Revamp --follow-exec
https://github.com/xd009642/tarpaulin/issues/953 


Patch:
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 88d5327..112f22b 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -33,7 +33,7 @@ jobs:
         env:
           RUST_BACKTRACE: 1
       - name: test
-        run: cargo test
+        run: cargo test 
         env:
           RUST_BACKTRACE: 1
       - name: ignored test
diff --git a/.gitignore b/.gitignore
index 7356d81..82553c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 # will have compiled files and executables
 **/target
 *.swp
+*.swo
 **/*.rs.bk
 *.log
 *.profdata
@@ -9,3 +10,5 @@
 *.profdata
 # Generated by IntelliJ IDEs
 .idea
+tarpaulin*.json
+tarpaulin-report.html
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d23ca09..1dd3113 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,12 @@ file.
 ### Changed
 - Stop adding `LD_LIBRARY_PATHS` to process env twice
 - [Internal] can now run `cargo test` on tarpaulin without need of `--test-threads 1`
+- Force --test-threads 1 for --follow-exec unless there's `--implicit-test-threads`
+- Add markers to event log to show where state machine iterations start and end, fix fork parent tracing
+- Handle exec following in vfork children
+- Continue vfork parents so test execution isn't stalled when tracing children
+- Make `--forward` default signal behaviour
+- Fix follow-exec aliasing for config file
 
 ## [0.19.1] 2022-01-16
 ### Added
diff --git a/Cargo.lock b/Cargo.lock
index 84ecb71..f642eb5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -292,9 +292,9 @@ dependencies = [
 
 [[package]]
 name = "git2"
-version = "0.13.25"
+version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6"
+checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c"
 dependencies = [
  "bitflags",
  "libc",
@@ -343,9 +343,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
 [[package]]
 name = "humantime-serde"
-version = "1.0.1"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
+checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c"
 dependencies = [
  "humantime",
  "serde",
@@ -411,9 +411,9 @@ checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
 
 [[package]]
 name = "libgit2-sys"
-version = "0.12.26+1.3.0"
+version = "0.13.2+1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494"
+checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b"
 dependencies = [
  "cc",
  "libc",
@@ -660,9 +660,9 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.15"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
+checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57"
 dependencies = [
  "proc-macro2",
 ]
@@ -678,9 +678,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.5.4"
+version = "1.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -842,9 +842,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
 
 [[package]]
 name = "syn"
-version = "1.0.86"
+version = "1.0.89"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
+checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -920,9 +920,9 @@ dependencies = [
 
 [[package]]
 name = "tracing"
-version = "0.1.31"
+version = "0.1.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f"
+checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f"
 dependencies = [
  "cfg-if",
  "pin-project-lite",
diff --git a/Cargo.toml b/Cargo.toml
index 6a36796..f037d9d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,7 +26,7 @@ clap = "2.34.0"
 coveralls-api = "0.5.0"
 fallible-iterator = "0.2.0"
 gimli = "0.26.1"
-git2 = "0.13"
+git2 = "0.14"
 humantime-serde = "1"
 indexmap = { version = "1.8.0", features = ["serde-1"] }
 lazy_static = "1.0"
@@ -37,7 +37,7 @@ object = "0.28"
 proc-macro2 = { version = "1.0", features = ["span-locations"] }
 quick-xml = "0.22"
 quote = "1.0"
-regex = "1.4"
+regex = "1.5"
 rustc-demangle = "0.1.21"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 811ce67..91e3978 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -166,6 +166,7 @@ pub struct Config {
     /// Colouring of logging
     pub color: Color,
     /// Follow traced executables down
+    #[serde(rename = "follow-exec")]
     pub follow_exec: bool,
     /// Number of jobs used for building the tests
     pub jobs: Option<usize>,
@@ -209,7 +210,7 @@ impl Default for Config {
             coveralls: None,
             ci_tool: None,
             report_uri: None,
-            forward_signals: false,
+            forward_signals: true,
             no_default_features: false,
             features: None,
             unstable_features: vec![],
@@ -302,7 +303,7 @@ impl<'a> From<&'a ArgMatches<'a>> for ConfigWrapper {
             coveralls: get_coveralls(args),
             ci_tool: get_ci(args),
             report_uri: get_report_uri(args),
-            forward_signals: args.is_present("forward"),
+            forward_signals: true, // No longer an option
             all_features: args.is_present("all-features"),
             no_default_features: args.is_present("no-default-features"),
             features,
@@ -540,6 +541,7 @@ impl Config {
         self.no_run |= other.no_run;
         self.no_default_features |= other.no_default_features;
         self.ignore_panics |= other.ignore_panics;
+        // Since true is the default
         self.forward_signals |= other.forward_signals;
         self.run_ignored |= other.run_ignored;
         self.release |= other.release;
diff --git a/src/event_log.rs b/src/event_log.rs
index ff7dd39..6cd1087 100644
--- a/src/event_log.rs
+++ b/src/event_log.rs
@@ -5,15 +5,16 @@ use crate::ptrace_control::*;
 use crate::statemachine::ProcessInfo;
 use crate::statemachine::TracerAction;
 use crate::traces::{Location, TraceMap};
-use chrono::offset::Local;
+use chrono::{offset::Local, SecondsFormat};
 #[cfg(target_os = "linux")]
 use nix::libc::*;
 #[cfg(target_os = "linux")]
 use nix::sys::{signal::Signal, wait::WaitStatus};
 use serde::{Deserialize, Serialize};
 use std::cell::RefCell;
+use std::collections::HashSet;
 use std::fs::File;
-use std::path::Path;
+use std::path::{Path, PathBuf};
 use std::time::Instant;
 use tracing::{info, warn};
 
@@ -22,6 +23,7 @@ pub enum Event {
     ConfigLaunch(String),
     BinaryLaunch(TestBinary),
     Trace(TraceEvent),
+    Marker(Option<()>),
 }
 
 #[derive(Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
@@ -53,7 +55,7 @@ pub struct TraceEvent {
 impl TraceEvent {
     #[cfg(target_os = "linux")]
     pub(crate) fn new_from_action(action: &TracerAction<ProcessInfo>) -> Self {
-        match *action {
+        match action {
             TracerAction::TryContinue(t) => TraceEvent {
                 pid: Some(t.pid.as_raw().into()),
                 signal: t.signal.map(|x| x.to_string()),
@@ -122,9 +124,15 @@ impl TraceEvent {
                     }
                     PTRACE_EVENT_FORK => {
                         event.description = "Ptrace fork".to_string();
+                        if *sig == Signal::SIGTRAP {
+                            event.child = get_event_data(*pid).ok();
+                        }
                     }
                     PTRACE_EVENT_VFORK => {
                         event.description = "Ptrace vfork".to_string();
+                        if *sig == Signal::SIGTRAP {
+                            event.child = get_event_data(*pid).ok();
+                        }
                     }
                     PTRACE_EVENT_EXEC => {
                         event.description = "Ptrace exec".to_string();
@@ -151,24 +159,20 @@ impl TraceEvent {
     }
 }
 
-#[derive(Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
+#[derive(Clone, PartialEq, Serialize, Deserialize)]
 pub struct EventLog {
     events: RefCell<Vec<EventWrapper>>,
     #[serde(skip)]
     start: Option<Instant>,
-}
-
-impl Default for EventLog {
-    fn default() -> Self {
-        Self::new()
-    }
+    manifest_paths: HashSet<PathBuf>,
 }
 
 impl EventLog {
-    pub fn new() -> Self {
+    pub fn new(manifest_paths: HashSet<PathBuf>) -> Self {
         Self {
             events: RefCell::new(vec![]),
             start: Some(Instant::now()),
+            manifest_paths,
         }
     }
 
@@ -191,11 +195,29 @@ impl EventLog {
             self.start.unwrap(),
         ));
     }
+
+    pub fn push_marker(&self) {
+        // Prevent back to back markers when we spend a lot of time waiting on events
+        if self
+            .events
+            .borrow()
+            .last()
+            .filter(|x| matches!(x.event, Event::Marker(_)))
+            .is_none()
+        {
+            self.events
+                .borrow_mut()
+                .push(EventWrapper::new(Event::Marker(None), self.start.unwrap()));
+        }
+    }
 }
 
 impl Drop for EventLog {
     fn drop(&mut self) {
-        let fname = format!("tarpaulin-run-{}.json", Local::now().to_rfc3339());
+        let fname = format!(
+            "tarpaulin_{}.json",
+            Local::now().to_rfc3339_opts(SecondsFormat::Secs, false)
+        );
         let path = Path::new(&fname);
         info!("Serializing tarpaulin debug log to {}", path.display());
         if let Ok(output) = File::create(path) {
diff --git a/src/lib.rs b/src/lib.rs
index ba074c1..8591a50 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -76,7 +76,7 @@ pub fn trace(configs: &[Config]) -> Result<TraceMap, RunError> {
     let mut tarpaulin_result = Ok(());
     let mut ret = 0i32;
     let logger = if configs.iter().any(|c| c.dump_traces) {
-        Some(EventLog::new())
+        Some(EventLog::new(configs.iter().map(|x| x.root()).collect()))
     } else {
         None
     };
diff --git a/src/main.rs b/src/main.rs
index e9ef2b1..fd8c6f6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -74,7 +74,7 @@ fn main() -> Result<(), String> {
                  --force-clean 'Adds a clean stage to work around cargo bugs that may affect coverage results'
                  --fail-under [PERCENTAGE] 'Sets a percentage threshold for failure ranging from 0-100, if coverage is below exit with a non-zero code'
                  --branch -b  'Branch coverage: NOT IMPLEMENTED'
-                 --forward -f 'Forwards unexpected signals to test. Tarpaulin will still take signals it is expecting.'
+                 --forward -f 'Forwards unexpected signals to test. This is now the default behaviour'
                  --coveralls [KEY]  'Coveralls key, either the repo token, or if you're using travis use $TRAVIS_JOB_ID and specify travis-{ci|pro} in --ciserver'
                  --report-uri [URI] 'URI to send report to, only used if the option --coveralls is used'
                  --no-default-features 'Do not include default features'
diff --git a/src/process_handling/linux.rs b/src/process_handling/linux.rs
index 25905cc..8b2c9f8 100644
--- a/src/process_handling/linux.rs
+++ b/src/process_handling/linux.rs
@@ -42,7 +42,10 @@ pub fn get_test_coverage(
         warn!("Test at {} doesn't exist", test.path().display());
         return Ok(None);
     }
-    let cpus = Some(*NUM_CPUS);
+
+    // Solves CI issue when fixing #953 and #966 in PR #962
+    let threads = if config.follow_exec { 1 } else { *NUM_CPUS };
+
     if let Err(e) = limit_affinity() {
         warn!("Failed to set processor affinity {}", e);
     }
@@ -56,7 +59,7 @@ pub fn get_test_coverage(
                     Mode::Build => "binary",
                 };
                 info!("Launching {}", bin_type);
-                execute_test(test, ignored, config, cpus)?;
+                execute_test(test, ignored, config, Some(threads))?;
                 Ok(None)
             }
             Err(err) => Err(RunError::TestCoverage(format!(
diff --git a/src/process_handling/mod.rs b/src/process_handling/mod.rs
index 3e5ae54..5648925 100644
--- a/src/process_handling/mod.rs
+++ b/src/process_handling/mod.rs
@@ -154,6 +154,8 @@ pub(crate) fn collect_coverage(
                     ret_code = i;
                 }
                 break;
+            } else if let Some(event_logger) = logger {
+                event_logger.push_marker();
             }
         }
     }
@@ -224,6 +226,7 @@ fn execute_test(
     } else {
         true
     };
+
     if no_test_env
         && test.is_test_type()
         && !config.implicit_test_threads
diff --git a/src/statemachine/instrumented.rs b/src/statemachine/instrumented.rs
index eb9f063..7b6b189 100644
--- a/src/statemachine/instrumented.rs
+++ b/src/statemachine/instrumented.rs
@@ -66,6 +66,10 @@ impl<'a> StateData for LlvmInstrumentedData<'a> {
         unreachable!();
     }
 
+    fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
+        unreachable!();
+    }
+
     fn wait(&mut self) -> Result<Option<TestState>, RunError> {
         if let Some(parent) = self.process.as_mut() {
             match parent.child.wait() {
diff --git a/src/statemachine/linux.rs b/src/statemachine/linux.rs
index 127c0d0..4d41a32 100644
--- a/src/statemachine/linux.rs
+++ b/src/statemachine/linux.rs
@@ -14,13 +14,17 @@ use nix::unistd::Pid;
 use nix::Error as NixErr;
 use procfs::process::{MMapPath, Process};
 use std::collections::{HashMap, HashSet};
+use std::ops::RangeBounds;
 use std::path::PathBuf;
-use tracing::{debug, trace, trace_span, warn};
+use tracing::{debug, info, trace, trace_span, warn};
 
 /// Handle to linux process state
 pub struct LinuxData<'a> {
     /// Recent results from waitpid to be handled by statemachine
     wait_queue: Vec<WaitStatus>,
+    /// Pending action queue, this is for actions where we need to wait one cycle before we can
+    /// apply them :sobs:
+    pending_actions: Vec<TracerAction<ProcessInfo>>,
     /// Parent pid of the test
     parent: Pid,
     /// Current Pid to process
@@ -37,6 +41,8 @@ pub struct LinuxData<'a> {
     processes: HashMap<Pid, TracedProcess>,
     /// Map from pids to their parent
     pid_map: HashMap<Pid, Pid>,
+    /// So if we have the exit code but we're also waiting for all the spawned processes to end
+    exit_code: Option<i32>,
 }
 
 #[derive(Debug)]
@@ -160,6 +166,20 @@ impl<'a> StateData for LinuxData<'a> {
         }
     }
 
+    fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
+        if let Some(ec) = self.exit_code {
+            let parent = self.parent;
+            for (_, process) in self.processes.iter().filter(|(k, _)| **k != parent) {
+                if let Some(tm) = process.traces.as_ref() {
+                    self.traces.merge(tm);
+                }
+            }
+            Ok(Some(TestState::End(ec)))
+        } else {
+            Ok(None)
+        }
+    }
+
     fn wait(&mut self) -> Result<Option<TestState>, RunError> {
         let mut result = Ok(None);
         let mut running = true;
@@ -186,6 +206,10 @@ impl<'a> StateData for LinuxData<'a> {
                     self.wait_queue.push(s);
                     result = Ok(Some(TestState::Stopped));
                 }
+                Err(_) if self.exit_code.is_some() => {
+                    running = false;
+                    result = self.last_wait_attempt();
+                }
                 Err(e) => {
                     running = false;
                     result = Err(RunError::TestRuntime(format!(
@@ -197,6 +221,8 @@ impl<'a> StateData for LinuxData<'a> {
         }
         if !self.wait_queue.is_empty() {
             trace!("Result queue is {:?}", self.wait_queue);
+        } else {
+            self.apply_pending_actions(..);
         }
         result
     }
@@ -206,6 +232,7 @@ impl<'a> StateData for LinuxData<'a> {
         let mut pcs = HashMap::new();
         let mut result = Ok(TestState::wait_state());
         let pending = self.wait_queue.clone();
+        let pending_action_len = self.pending_actions.len();
         self.wait_queue.clear();
         for status in &pending {
             let span = trace_span!("pending", event=?status.pid());
@@ -216,7 +243,11 @@ impl<'a> StateData for LinuxData<'a> {
                 } else {
                     0
                 };
-                let event = TraceEvent::new_from_wait(status, offset, self.traces);
+                let traces = match self.get_active_trace_map(self.current) {
+                    Some(tm) => tm,
+                    None => &self.traces,
+                };
+                let event = TraceEvent::new_from_wait(status, offset, traces);
                 log.push_trace(event);
             }
             let state = match status {
@@ -291,13 +322,24 @@ impl<'a> StateData for LinuxData<'a> {
                     }
                     trace!("Exited {:?} parent {:?}", child, self.parent);
                     if child == &self.parent {
-                        Ok((TestState::End(*ec), TracerAction::Nothing))
+                        if self.processes.is_empty() || !self.config.follow_exec {
+                            Ok((TestState::End(*ec), TracerAction::Nothing))
+                        } else {
+                            self.exit_code = Some(*ec);
+                            info!("Test process exited, but spawned processes still running. Continuing tracing");
+                            Ok((TestState::wait_state(), TracerAction::Nothing))
+                        }
                     } else {
-                        // Process may have already been destroyed. This is just incase
-                        Ok((
-                            TestState::wait_state(),
-                            TracerAction::TryContinue(self.parent.into()),
-                        ))
+                        match self.exit_code {
+                            Some(ec) if self.processes.is_empty() => return Ok(TestState::End(ec)),
+                            _ => {
+                                // Process may have already been destroyed. This is just incase
+                                Ok((
+                                    TestState::wait_state(),
+                                    TracerAction::TryContinue(self.parent.into()),
+                                ))
+                            }
+                        }
                     }
                 }
                 _ => Err(RunError::TestRuntime(
@@ -317,6 +359,7 @@ impl<'a> StateData for LinuxData<'a> {
         }
         let mut continued = false;
         let mut actioned_pids = HashSet::new();
+
         for a in &actions {
             if let Some(d) = a.get_data() {
                 if actioned_pids.contains(&d.pid) {
@@ -330,7 +373,7 @@ impl<'a> StateData for LinuxData<'a> {
             }
             trace!("Action: {:?}", a);
             if let Some(log) = self.event_log.as_ref() {
-                let event = TraceEvent::new_from_action(a);
+                let event = TraceEvent::new_from_action(&a);
                 log.push_trace(event);
             }
             match a {
@@ -352,12 +395,19 @@ impl<'a> StateData for LinuxData<'a> {
                 TracerAction::Detach(t) => {
                     continued = true;
                     actioned_pids.insert(t.pid);
-                    detach_child(t.pid)?;
+                    let _ = detach_child(t.pid);
                 }
-                _ => {}
+                TracerAction::Nothing => {}
             }
         }
-        if !continued {
+        // Here we assume that no pending actions will exist for things that have currently
+        // signaled as stopped in this iteration. Currently, the pending actions are just fork
+        // parents that ptrace will stall until child returns so this will hold true. But if that
+        // behaviour changes in future the pending action list may need to be pruned more
+        // thoroughly
+        self.apply_pending_actions(..pending_action_len);
+
+        if !continued && self.exit_code.is_none() {
             trace!("No action suggested to continue tracee. Attempting a continue");
             let _ = continue_exec(self.parent, None);
         }
@@ -374,6 +424,7 @@ impl<'a> LinuxData<'a> {
     ) -> LinuxData<'a> {
         LinuxData {
             wait_queue: Vec::new(),
+            pending_actions: Vec::new(),
             processes: HashMap::new(),
             current: Pid::from_raw(0),
             parent: Pid::from_raw(0),
@@ -382,6 +433,7 @@ impl<'a> LinuxData<'a> {
             config,
             event_log,
             pid_map: HashMap::new(),
+            exit_code: None,
         }
     }
 
@@ -408,7 +460,7 @@ impl<'a> LinuxData<'a> {
         self.processes.get_mut(&parent)
     }
 
-    fn get_active_trace_map(&mut self, pid: Pid) -> Option<&mut TraceMap> {
+    fn get_active_trace_map_mut(&mut self, pid: Pid) -> Option<&mut TraceMap> {
         let parent = self.get_parent(pid)?;
         let process = self.processes.get_mut(&parent)?;
         if process.traces.is_some() {
@@ -418,6 +470,12 @@ impl<'a> LinuxData<'a> {
         }
     }
 
+    fn get_active_trace_map(&mut self, pid: Pid) -> Option<&TraceMap> {
+        let parent = self.get_parent(pid)?;
+        let process = self.processes.get(&parent)?;
+        Some(process.traces.as_ref().unwrap_or(&self.traces))
+    }
+
     fn init_process(
         &mut self,
         pid: Pid,
@@ -483,7 +541,12 @@ impl<'a> LinuxData<'a> {
             }
         }
         // a processes pid is it's own parent
-        self.pid_map.insert(pid, pid);
+        match self.pid_map.insert(pid, pid) {
+            Some(old) if old != pid => {
+                debug!("{} being promoted to parent. Old parent {}", pid, old)
+            }
+            _ => {}
+        }
         Ok(TracedProcess {
             parent: pid,
             breakpoints,
@@ -566,7 +629,7 @@ impl<'a> LinuxData<'a> {
                         ))
                     }
                 },
-                PTRACE_EVENT_FORK | PTRACE_EVENT_VFORK => {
+                PTRACE_EVENT_FORK => {
                     if let Ok(fork_child) = get_event_data(child) {
                         trace!("Caught fork event. Child {}", fork_child);
                         let parent = if let Some(process) = self.get_traced_process_mut(child) {
@@ -587,6 +650,38 @@ impl<'a> LinuxData<'a> {
                         TracerAction::Continue(child.into()),
                     ))
                 }
+                PTRACE_EVENT_VFORK => {
+                    // So VFORK used to be handled the same place as FORK however, from the man
+                    // page for posix_spawn:
+                    //
+                    // > [The] posix_spawn() function commences by calling clone with CLONE_VM and CLONE_VFORK flags.
+                    //
+                    // This suggests that Command::new().spawn() will result in a
+                    // `PTRACE_EVENT_VFORK` not `PTRACE_EVENT_EXEC`
+                    if let Ok(fork_child) = get_event_data(child) {
+                        let fork_child = Pid::from_raw(fork_child as _);
+                        if self.config.follow_exec {
+                            // So I've seen some recursive bin calls with vforks... Maybe just assume
+                            // every vfork is an exec :thinking:
+                            let (state, action) = self.handle_exec(fork_child)?;
+                            if self.config.forward_signals {
+                                self.pending_actions
+                                    .push(TracerAction::Continue(child.into()));
+                            }
+                            Ok((state, action))
+                        } else {
+                            Ok((
+                                TestState::wait_state(),
+                                TracerAction::Continue(child.into()),
+                            ))
+                        }
+                    } else {
+                        Ok((
+                            TestState::wait_state(),
+                            TracerAction::Continue(child.into()),
+                        ))
+                    }
+                }
                 PTRACE_EVENT_EXEC => {
                     if self.config.follow_exec {
                         self.handle_exec(child)
@@ -658,7 +753,7 @@ impl<'a> LinuxData<'a> {
         } else {
             warn!("Failed to find process for pid: {}", current);
         }
-        if let Some(traces) = self.get_active_trace_map(current) {
+        if let Some(traces) = self.get_active_trace_map_mut(current) {
             for addr in &hits_to_increment {
                 traces.increment_hit(*addr);
             }
@@ -692,7 +787,31 @@ impl<'a> LinuxData<'a> {
             (Signal::SIGCHLD, _) => {
                 Ok((TestState::wait_state(), TracerAction::Continue(pid.into())))
             }
+            (Signal::SIGTERM, _) => {
+                let info = ProcessInfo {
+                    pid: *pid,
+                    signal: Some(Signal::SIGTERM),
+                };
+                Ok((TestState::wait_state(), TracerAction::TryContinue(info)))
+            }
             _ => Err(RunError::StateMachine("Unexpected stop".to_string())),
         }
     }
+
+    fn apply_pending_actions(&mut self, range: impl RangeBounds<usize>) {
+        for a in self.pending_actions.drain(range) {
+            if let Some(log) = self.event_log.as_ref() {
+                let event = TraceEvent::new_from_action(&a);
+                log.push_trace(event);
+            }
+            match a {
+                TracerAction::Continue(t) | TracerAction::TryContinue(t) => {
+                    let _ = continue_exec(t.pid, t.signal);
+                }
+                e => {
+                    error!("Pending actions should only be continues: {:?}", e);
+                }
+            }
+        }
+    }
 }
diff --git a/src/statemachine/mod.rs b/src/statemachine/mod.rs
index 53291a6..6f8d68c 100644
--- a/src/statemachine/mod.rs
+++ b/src/statemachine/mod.rs
@@ -69,7 +69,7 @@ pub enum TestState {
 /// This enum represents a generic action for the process tracing API to take
 /// along with any form of ID or handle to the underlying thread or process
 /// i.e. a PID in Unix.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[derive(Debug, Clone, Eq, PartialEq)]
 pub enum TracerAction<T> {
     /// Try continue is for times when you don't know if there is something
     /// paused but if there is you want it to move on.
@@ -106,6 +106,10 @@ pub trait StateData {
     /// something to do. Selects the next appropriate state if there's
     /// something to do otherwise None
     fn wait(&mut self) -> Result<Option<TestState>, RunError>;
+    /// This is here for the times when we're about to mark the attempted coverage collection as a
+    /// failure i.e. timeout, but there's an alternative to that which can see if we're actually in
+    /// a "finished" state but are still waiting on resource cleanup so we don't lose the results.
+    fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError>;
     /// Handle a stop in the test executable. Coverage data will
     /// be collected here as well as other OS specific functions
     fn stop(&mut self) -> Result<TestState, RunError>;
@@ -127,6 +131,11 @@ impl StateData for () {
             "No valid coverage collector".to_string(),
         ))
     }
+    fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
+        Err(RunError::StateMachine(
+            "No valid coverage collector".to_string(),
+        ))
+    }
     fn stop(&mut self) -> Result<TestState, RunError> {
         Err(RunError::StateMachine(
             "No valid coverage collector".to_string(),
@@ -147,6 +156,10 @@ impl<'a> StateData for Box<dyn StateData + 'a> {
         self.as_mut().wait()
     }
 
+    fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
+        self.as_mut().last_wait_attempt()
+    }
+
     fn stop(&mut self) -> Result<TestState, RunError> {
         self.as_mut().stop()
     }
@@ -191,9 +204,13 @@ impl TestState {
                 if let Some(s) = data.wait()? {
                     Ok(s)
                 } else if start_time.elapsed() >= config.test_timeout {
-                    Err(RunError::TestRuntime(
-                        "Error: Timed out waiting for test response".to_string(),
-                    ))
+                    if let Some(s) = data.last_wait_attempt()? {
+                        Ok(s)
+                    } else {
+                        Err(RunError::TestRuntime(
+                            "Error: Timed out waiting for test response".to_string(),
+                        ))
+                    }
                 } else {
                     Ok(TestState::Waiting { start_time })
                 }


Test patch:
diff --git a/tests/data/follow_exe/log b/tests/data/follow_exe/log
deleted file mode 100644
index 76a2663..0000000
--- a/tests/data/follow_exe/log
+++ /dev/null
@@ -1,26 +0,0 @@
-[INFO tarpaulin] Creating config
-[INFO tarpaulin] Running Tarpaulin
-[INFO tarpaulin] Building project
-    Finished test [unoptimized + debuginfo] target(s) in 0.01s
-[INFO tarpaulin] Launching test
-[INFO tarpaulin] running /home/xd009642/code/rust/tarpaulin/tests/data/follow_exe/target/debug/deps/launcher-c3357ac07e176a4f
-
-running 2 tests
-test launch_ls ... ok
-test launch_hello_world ... ok
-
-test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
-
-[INFO tarpaulin] Launching test
-[INFO tarpaulin] running /home/xd009642/code/rust/tarpaulin/tests/data/follow_exe/target/debug/deps/follow_exe-20b826fa2b7c2647
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
-
-[INFO tarpaulin] Coverage Results:
-|| Tested/Total Lines:
-|| src/main.rs: 0/3 +0%
-|| tests/launcher.rs: 3/3 +0%
-|| 
-50.00% coverage, 3/6 lines covered, +0% change in coverage
diff --git a/tests/data/follow_exec_issue966/.gitignore b/tests/data/follow_exec_issue966/.gitignore
new file mode 100644
index 0000000..96ef6c0
--- /dev/null
+++ b/tests/data/follow_exec_issue966/.gitignore
@@ -0,0 +1,2 @@
+/target
+Cargo.lock
diff --git a/tests/data/follow_exec_issue966/.tarpaulin.toml b/tests/data/follow_exec_issue966/.tarpaulin.toml
new file mode 100644
index 0000000..418af92
--- /dev/null
+++ b/tests/data/follow_exec_issue966/.tarpaulin.toml
@@ -0,0 +1,7 @@
+[examples]
+command = "Build"
+run-types = ["Examples"]
+follow-exec = true
+
+#[tests]
+#run-types = ["Tests"]
diff --git a/tests/data/follow_exec_issue966/Cargo.toml b/tests/data/follow_exec_issue966/Cargo.toml
new file mode 100644
index 0000000..7217e81
--- /dev/null
+++ b/tests/data/follow_exec_issue966/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "follow_exec_issue966"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/tests/data/follow_exec_issue966/examples/test.rs b/tests/data/follow_exec_issue966/examples/test.rs
new file mode 100644
index 0000000..d795b0f
--- /dev/null
+++ b/tests/data/follow_exec_issue966/examples/test.rs
@@ -0,0 +1,16 @@
+use follow_exec_issue966::*;
+
+fn main() {
+    let args: Vec<_> = std::env::args().into_iter().collect();
+    if args.contains(&"do_the_thing".into()) {
+        do_the_thing();
+        return;
+    }
+
+    let exe_path = std::env::current_exe().unwrap();
+
+    std::process::Command::new(exe_path)
+        .args(&["call_main", "--", "do_the_thing"])
+        .spawn()
+        .unwrap();
+}
diff --git a/tests/data/follow_exec_issue966/src/lib.rs b/tests/data/follow_exec_issue966/src/lib.rs
new file mode 100644
index 0000000..cab51ed
--- /dev/null
+++ b/tests/data/follow_exec_issue966/src/lib.rs
@@ -0,0 +1,6 @@
+use std::io::Write;
+
+pub fn do_the_thing() {
+    let mut f = std::fs::File::create("/tmp/whatever").unwrap();
+    f.write("hello\n".as_bytes()).unwrap();
+}
diff --git a/tests/data/kill_proc/Cargo.lock b/tests/data/kill_proc/Cargo.lock
new file mode 100644
index 0000000..60edbf0
--- /dev/null
+++ b/tests/data/kill_proc/Cargo.lock
@@ -0,0 +1,1063 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "buf_redux"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
+dependencies = [
+ "memchr",
+ "safemem",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+
+[[package]]
+name = "cc"
+version = "1.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
+
+[[package]]
+name = "futures-task"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
+
+[[package]]
+name = "futures-util"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "h2"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "headers"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c84c647447a07ca16f5fbd05b633e535cc41a08d2d74ab1e08648df53be9cb89"
+dependencies = [
+ "base64",
+ "bitflags",
+ "bytes",
+ "headers-core",
+ "http",
+ "httpdate",
+ "mime",
+ "sha-1",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+dependencies = [
+ "http",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "http"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa 1.0.1",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4"
+
+[[package]]
+name = "httpdate"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+
+[[package]]
+name = "hyper"
+version = "0.14.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa 0.4.8",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "itoa"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
+
+[[package]]
+name = "kill_proc"
+version = "0.1.0"
+dependencies = [
+ "http",
+ "hyper",
+ "nix",
+ "test_bin",
+ "tokio",
+ "warp",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
+
+[[package]]
+name = "lock_api"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "mio"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
+dependencies = [
+ "libc",
+ "log",
+ "miow",
+ "ntapi",
+ "winapi",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "multipart"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182"
+dependencies = [
+ "buf_redux",
+ "httparse",
+ "log",
+ "mime",
+ "mime_guess",
+ "quick-error",
+ "rand",
+ "safemem",
+ "tempfile",
+ "twoway",
+]
+
+[[package]]
+name = "nix"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pin-project"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quote"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
+
+[[package]]
+name = "safemem"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "serde"
+version = "1.0.136"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
+
+[[package]]
+name = "serde_json"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085"
+dependencies = [
+ "itoa 1.0.1",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa 1.0.1",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
+dependencies = [
+ "block-buffer",
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
+
+[[package]]
+name = "smallvec"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
+
+[[package]]
+name = "socket2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "libc",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "test_bin"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e7a7de15468c6e65dd7db81cf3822c1ec94c71b2a3c1a976ea8e4696c91115c"
+
+[[package]]
+name = "thiserror"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "1.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a"
+dependencies = [
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "once_cell",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "tokio-macros",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8"
+dependencies = [
+ "futures-util",
+ "log",
+ "pin-project",
+ "tokio",
+ "tungstenite",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
+[[package]]
+name = "tracing"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9"
+dependencies = [
+ "cfg-if",
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "tungstenite"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5"
+dependencies = [
+ "base64",
+ "byteorder",
+ "bytes",
+ "http",
+ "httparse",
+ "log",
+ "rand",
+ "sha-1",
+ "thiserror",
+ "url",
+ "utf-8",
+]
+
+[[package]]
+name = "twoway"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "typenum"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "warp"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "headers",
+ "http",
+ "hyper",
+ "log",
+ "mime",
+ "mime_guess",
+ "multipart",
+ "percent-encoding",
+ "pin-project",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-stream",
+ "tokio-tungstenite",
+ "tokio-util",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/tests/data/kill_proc/Cargo.toml b/tests/data/kill_proc/Cargo.toml
new file mode 100644
index 0000000..a92ef18
--- /dev/null
+++ b/tests/data/kill_proc/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "kill_proc"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+http = "0.2.6"
+hyper = "0.14.16"
+nix = "0.23.1"
+test_bin = "0.4.0"
+tokio = { version = "1", features = ["full"] }
+warp = "0.3"
+
diff --git a/tests/data/kill_proc/src/main.rs b/tests/data/kill_proc/src/main.rs
new file mode 100644
index 0000000..24f4175
--- /dev/null
+++ b/tests/data/kill_proc/src/main.rs
@@ -0,0 +1,12 @@
+use warp::Filter;
+
+#[tokio::main]
+async fn main() {
+    let hello = warp::path("hello")
+        .and(warp::get())
+        .map(|| String::from("Hello"));
+
+    warp::serve(hello)
+        .run(([127, 0, 0, 1], 18080))
+        .await;
+}
diff --git a/tests/data/kill_proc/tests/kill.rs b/tests/data/kill_proc/tests/kill.rs
new file mode 100644
index 0000000..04f1701
--- /dev/null
+++ b/tests/data/kill_proc/tests/kill.rs
@@ -0,0 +1,64 @@
+#[test]
+fn spawn_test_and_kill() {
+    let _child: ChildWrapper = test_bin::get_test_bin("kill_proc")
+    .spawn()
+    .unwrap()
+    .into();
+    
+    run_test().unwrap();
+}
+
+
+fn run_test() -> Result<(), tokio::time::error::Elapsed> {
+    tokio::runtime::Runtime::new()
+        .unwrap()
+        .block_on(healthy_or_timeout())
+}
+
+async fn healthy_or_timeout() -> Result<(), tokio::time::error::Elapsed> {
+    tokio::time::timeout(std::time::Duration::from_secs(5), wait_for_healthy()).await
+}
+
+async fn wait_for_healthy() {
+    loop {
+        if let Ok(response) = http_call().await {
+            if response.status() == http::StatusCode::OK {
+                return;
+            }
+        }
+
+        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
+    }
+}
+
+async fn http_call() -> Result<hyper::Response<hyper::Body>, hyper::Error> {
+    let url = "http://localhost:18080/hello".parse().unwrap();
+    let client = hyper::client::Client::new();
+    client.get(url).await
+}
+
+struct ChildWrapper {
+    child: std::process::Child,
+}
+
+impl ChildWrapper {
+    fn new(child: std::process::Child) -> Self {
+        Self {
+            child,
+        }
+    }
+}
+
+impl Drop for ChildWrapper {
+    fn drop(&mut self) {
+        let pid = self.child.id();
+        let pid = nix::unistd::Pid::from_raw(pid.try_into().unwrap());
+        let _ = nix::sys::signal::kill(pid, nix::sys::signal::Signal::SIGTERM);
+    }
+}
+
+impl From<std::process::Child> for ChildWrapper {
+    fn from(child: std::process::Child) -> Self {
+        ChildWrapper::new(child)
+    }
+}
diff --git a/tests/data/proc_macro/Cargo.lock b/tests/data/proc_macro/Cargo.lock
index d503153..3cb4a57 100644
--- a/tests/data/proc_macro/Cargo.lock
+++ b/tests/data/proc_macro/Cargo.lock
@@ -1,56 +1,56 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+version = 3
+
 [[package]]
 name = "proc-macro2"
 version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "effdb53b25cdad54f8f48843d67398f7ef2e14f12c1b4cb4effc549a6462a4d6"
 dependencies = [
- "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid",
 ]
 
 [[package]]
 name = "quote"
 version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035"
 dependencies = [
- "proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
 ]
 
 [[package]]
 name = "serde_derive"
 version = "1.0.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a90213fa7e0f5eac3f7afe2d5ff6b088af515052cc7303bd68c7e3b91a3fb79"
 dependencies = [
- "proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
 name = "simple_project"
 version = "0.1.0"
 dependencies = [
- "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive",
 ]
 
 [[package]]
 name = "syn"
 version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c67da57e61ebc7b7b6fff56bb34440ca3a83db037320b0507af4c10368deda7d"
 dependencies = [
- "proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
 ]
 
 [[package]]
 name = "unicode-xid"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[metadata]
-"checksum proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "effdb53b25cdad54f8f48843d67398f7ef2e14f12c1b4cb4effc549a6462a4d6"
-"checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035"
-"checksum serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "0a90213fa7e0f5eac3f7afe2d5ff6b088af515052cc7303bd68c7e3b91a3fb79"
-"checksum syn 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c67da57e61ebc7b7b6fff56bb34440ca3a83db037320b0507af4c10368deda7d"
-"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
+checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
diff --git a/tests/mod.rs b/tests/mod.rs
index 240f4e3..a10a225 100644
--- a/tests/mod.rs
+++ b/tests/mod.rs
@@ -1,10 +1,12 @@
 use crate::utils::get_test_path;
 use cargo_tarpaulin::config::{Config, ConfigWrapper, Mode, RunType};
+use cargo_tarpaulin::event_log::EventLog;
 use cargo_tarpaulin::launch_tarpaulin;
 use cargo_tarpaulin::path_utils::*;
 use cargo_tarpaulin::traces::TraceMap;
 use clap::App;
 use rusty_fork::rusty_fork_test;
+use std::collections::HashSet;
 use std::env;
 use std::fs;
 use std::path::Path;
@@ -68,8 +70,15 @@ pub fn check_percentage_with_config(
     // Note to contributors. If an integration test fails, uncomment this to be able to see the
     // tarpaulin logs
     //cargo_tarpaulin::setup_logging(true, true);
+    let event_log = if config.dump_traces {
+        let mut paths = HashSet::new();
+        paths.insert(config.manifest.clone());
+        Some(EventLog::new(paths))
+    } else {
+        None
+    };
 
-    let (res, ret) = launch_tarpaulin(&config, &None).unwrap();
+    let (res, ret) = launch_tarpaulin(&config, &event_log).unwrap();
     assert_eq!(ret, 0);
 
     env::set_current_dir(restore_dir).unwrap();
@@ -199,6 +208,17 @@ fn config_file_coverage() {
     check_percentage_with_cli_args(0.0f64, true, &args);
 }
 
+#[test]
+fn issue_966_follow_exec() {
+    let test_dir = get_test_path("follow_exec_issue966");
+    let args = vec![
+        "tarpaulin".to_string(),
+        "--root".to_string(),
+        test_dir.display().to_string(),
+    ];
+    check_percentage_with_cli_args(1.0f64, true, &args);
+}
+
 #[test]
 fn rustflags_config_coverage() {
     let test_dir = get_test_path("multiple_rustflags");
@@ -335,6 +355,7 @@ fn rustflags_handling() {
 fn follow_exes_down() {
     let mut config = Config::default();
     config.follow_exec = true;
+    config.dump_traces = true;
     config.set_clean(false);
     check_percentage_with_config("follow_exe", 1.0f64, true, config);
 }
@@ -384,4 +405,14 @@ fn dot_rs_in_dir_name() {
     }
 }
 
+#[test]
+fn kill_used_in_test() {
+    let mut config = Config::default();
+    config.follow_exec = true;
+    config.set_clean(false);
+    // Currently 2 false negatives, but if it was only covering the integration test max coverage
+    // is 75% so this is high enough to prove it works
+    check_percentage_with_config("kill_proc", 0.9f64, true, config);
+}
+
 }