bootimage 0.5.0-alpha-00

Tool to create a bootable OS image from a kernel binary.
use args::Args;
use build;
use failure::{Error, ResultExt};
use std::io::Write;
use std::path::Path;
use std::time::Duration;
use std::{fs, io, process};
use wait_timeout::ChildExt;

pub(crate) fn test(args: Args) -> Result<(), Error> {
    let (args, config, metadata, root_dir, out_dir) = build::common_setup(args)?;

    let test_args = args.clone();
    let test_run_command = vec![
        "qemu-system-x86_64".into(),
        "-drive".into(),
        "format=raw,file={}".into(),
        "-device".into(),
        "isa-debug-exit,iobase=0xf4,iosize=0x04".into(),
        "-display".into(),
        "none".into(),
        "-serial".into(),
        "file:{}-output.txt".into(),
    ];
    let test_config = {
        let mut test_config = config.clone();
        test_config.output = None;
        test_config.run_command = test_run_command;
        test_config
    };

    let mut tests = Vec::new();

    let crate_metadata = metadata
        .packages
        .iter()
        .find(|p| Path::new(&p.manifest_path) == config.manifest_path)
        .expect("Could not read crate name from cargo metadata");
    let target_iter = crate_metadata.targets.iter();
    for target in target_iter.filter(|t| t.kind == ["bin"] && t.name.starts_with("test-")) {
        println!("{}", target.name);

        let mut target_args = test_args.clone();
        target_args.set_bin_name(target.name.clone());
        let test_path = build::build_impl(
            &target_args,
            &test_config,
            &metadata,
            &root_dir,
            &out_dir,
            false,
        )?;

        let test_result;
        let output_file = format!("{}-output.txt", test_path.display());

        let mut command = process::Command::new("qemu-system-x86_64");
        command.arg("-drive");
        command.arg(format!("format=raw,file={}", test_path.display()));
        command.arg("-device");
        command.arg("isa-debug-exit,iobase=0xf4,iosize=0x04");
        command.arg("-display");
        command.arg("none");
        command.arg("-serial");
        command.arg(format!("file:{}", output_file));
        command.stderr(process::Stdio::null());
        let mut child = command
            .spawn()
            .context(format_err!("Failed to launch QEMU: {:?}", command))?;
        let timeout = Duration::from_secs(60);
        match child
            .wait_timeout(timeout)
            .context("Failed to wait with timeout")?
        {
            None => {
                child.kill().context("Failed to kill QEMU")?;
                child.wait().context("Failed to wait for QEMU process")?;
                test_result = TestResult::TimedOut;
                writeln!(io::stderr(), "Timed Out")?;
            }
            Some(_) => {
                let output = fs::read_to_string(&output_file).context(format_err!(
                    "Failed to read test output file {}",
                    output_file
                ))?;
                if output.starts_with("ok\n") {
                    test_result = TestResult::Ok;
                    println!("Ok");
                } else if output.starts_with("failed\n") {
                    test_result = TestResult::Failed;
                    writeln!(io::stderr(), "Failed:")?;
                    for line in output[7..].lines() {
                        writeln!(io::stderr(), "    {}", line)?;
                    }
                } else {
                    test_result = TestResult::Invalid;
                    writeln!(io::stderr(), "Failed: Invalid Output:")?;
                    for line in output.lines() {
                        writeln!(io::stderr(), "    {}", line)?;
                    }
                }
            }
        }
        println!("");

        tests.push((target.name.clone(), test_result))
    }

    if tests.iter().all(|t| t.1 == TestResult::Ok) {
        println!("All tests succeeded.");
        Ok(())
    } else {
        writeln!(io::stderr(), "The following tests failed:")?;
        for test in tests.iter().filter(|t| t.1 != TestResult::Ok) {
            writeln!(io::stderr(), "    {}: {:?}", test.0, test.1)?;
        }
        process::exit(1);
    }
}

#[derive(Debug, PartialEq, Eq)]
enum TestResult {
    Ok,
    Failed,
    TimedOut,
    Invalid,
}