Merge branch 'master' into syscalls-2.0
diff --git a/test_runner/Cargo.toml b/test_runner/Cargo.toml
index 66a407a..9471138 100644
--- a/test_runner/Cargo.toml
+++ b/test_runner/Cargo.toml
@@ -3,20 +3,3 @@
 version = "0.1.0"
 authors = ["torfmaster <briefe@kebes.de>"]
 edition = "2018"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-structopt = { version = "0.3", default-features = false }
-futures = "0.3.4"
-
-[dependencies.async-std]
-# async-std 1.7 pulls in crossbeam_utils 1.8, which does not work with the
-# nightly Rust toolchain we use. Temporarily block async-std 1.7 until we can
-# update our Rust toolchain.
-version = "1.5.0, <1.7"
-features = ["attributes"]
-
-[dependencies.tokio]
-version = "0.2.12"
-features = ["process", "rt-threaded", "macros", "io-util", "time"]
diff --git a/test_runner/src/main.rs b/test_runner/src/main.rs
index 0bea1cf..b21b945 100644
--- a/test_runner/src/main.rs
+++ b/test_runner/src/main.rs
@@ -1,20 +1,10 @@
 use std::fmt;
-use std::process::Stdio;
+use std::io::{BufRead, BufReader};
+use std::process::{ChildStdout, Command, Stdio};
 use std::time::Duration;
-use tokio::io::AsyncBufReadExt;
-use tokio::io::BufReader;
-use tokio::process::Command;
-use tokio::time::timeout;
 
-#[tokio::main]
-async fn main() -> Result<(), Box<dyn std::error::Error>> {
-    timeout(Duration::from_secs(10), perform_tests()).await?
-}
-
-async fn perform_tests() -> Result<(), Box<dyn std::error::Error>> {
-    let mut failed_tests = Vec::new();
-
-    let tests = Command::new("tock/tools/qemu-build/riscv32-softmmu/qemu-system-riscv32")
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let mut tests = Command::new("tock/tools/qemu-build/riscv32-softmmu/qemu-system-riscv32")
         .arg("-M")
         .arg("sifive_e,revb=true")
         .arg("-kernel")
@@ -24,15 +14,32 @@
         .arg("-nographic")
         .stdin(Stdio::null())
         .stdout(Stdio::piped())
-        .kill_on_drop(true)
         .spawn()?;
+    let stdout = tests.stdout.take().unwrap();
+    let child_handle = std::sync::Arc::new(std::sync::Mutex::new(tests));
+    let timeout_handle = child_handle.clone();
+    std::thread::spawn(move || {
+        std::thread::sleep(Duration::from_secs(10));
+        let _ = timeout_handle
+            .lock()
+            .unwrap_or_else(std::sync::PoisonError::into_inner)
+            .kill();
+    });
+    let result = process_output(stdout);
+    let _ = child_handle
+        .lock()
+        .unwrap_or_else(std::sync::PoisonError::into_inner)
+        .kill();
+    result
+}
 
-    let stdout = tests.stdout.unwrap();
-
+fn process_output(stdout: ChildStdout) -> Result<(), Box<dyn std::error::Error>> {
+    let mut failed_tests = Vec::new();
     let stdout_reader = BufReader::new(stdout);
-    let mut stdout_lines = stdout_reader.lines();
+    let stdout_lines = stdout_reader.lines();
 
-    while let Some(line) = stdout_lines.next_line().await? {
+    for line in stdout_lines {
+        let line = line?;
         println!("UART: {}", line);
         let test_result = test_succeeded(line, &mut failed_tests);
         if let Some(true) = test_result {