rust-osdev / bootloader

An experimental pure-Rust x86 bootloader
Apache License 2.0
1.37k stars 208 forks source link

How to test after migration from 0.9.x #330

Open muellerpeter-pms opened 1 year ago

muellerpeter-pms commented 1 year ago

I migrated from 0.9.x to 0.11.

The build works fine but tests are broken the old way. When testing teh kernel there is no executable, what I can understand. But when testing the parent directory only the crate itself will be tested. When testing the parent directory with --workspace option there is no target transferred to the kernel build, which leads to the error:

"error: language item required, but not found: `eh_personality`
 |
  = note: this can occur when a binary crate with `#![no_std]` is compiled for a target where `eh_personality` is defined in the standard library
  = help: you may be able to compile for a target that doesn't need `eh_personality`, specify a target with `--target` or in `.cargo/config`

I guess there is an option to pass the target even in test build mode. But I didn't find yet.

Thanks in advane!

Benabik commented 1 year ago

I got the tests to build by leaving a kernel/.cargo/config.toml file:

[build]
target = "x86_64-unknown-none"

Actually running the tests runs into further issues: namely that it tries to run the ELF file as a binary. This was done in 0.9 by also having the following in the cargo config:

[target.'cfg(target_os = "none")']
runner = "bootimage runner"

What bootimage runner seems to do is build a boot image from an executable passed as an argument and then execute qemu. The code to do that is now in build.rs and src/main.rs from the disk image template. I'm guessing I could create a new bin crate in the workspace that does what both of those files do but getting the kernel from std::env::args_os() instead of std::env::var_os("CARGO_BIN_FILE_KERNEL_kernel").

Benabik commented 1 year ago

Yup! I added the following runner to my OS workspace:

// runner/src/main.rs

use std::path::PathBuf;

fn main() {
    let mut args = std::env::args_os().skip(1); // Skip runner name

    let mut cmd = std::process::Command::new("qemu-system-x86_64");
    cmd.args([
        "-device",
        "isa-debug-exit,iobase=0xf4,iosize=0x04",
        "-serial",
        "stdio",
    ]);
    while args.len() > 1 {
        cmd.arg(args.next().unwrap());
    }

    let kernel = PathBuf::from(args.next().unwrap());

    // choose whether to start the UEFI or BIOS image
    let uefi = false;

    let mut image_path = std::env::temp_dir().join(kernel.file_name().unwrap());
    image_path.set_extension("img");

    if uefi {
        todo!("Replace PIC with APIC before using UEFI");
        /*
        bootloader::UefiBoot::new(&kernel).create_disk_image(&image_path).unwrap();

        cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi());
        */
    } else {
        // create a BIOS disk image
        bootloader::BiosBoot::new(&kernel)
            .create_disk_image(&image_path)
            .unwrap();
    }

    let mut drive: std::ffi::OsString = "format=raw,file=".into();
    drive.push(&image_path);
    cmd.arg("-drive").arg(drive);

    // Start QEMU and wait
    let mut child = cmd.spawn().unwrap();
    child.wait().unwrap();
    std::fs::remove_file(&image_path).unwrap();
}

Edit: Above code used to use OUT_DIR, but that is apparently unreliable for a test runner. Now creates a file in temp_dir and removes after QEMU exits.

And my kernel/.cargo/config.toml is

[build]
target = "x86_64-unknown-none"

[target.'cfg(target_os = "none")']
runner = ["../target/debug/runner", "-display", "none"]

Options to the runner are passed to QEMU, and I highly recommend removing -display none until you get it to pass once (since the bootloader panics are shown on the framebuffer). Also note that you need to build the runner manually after you make changes to it.

emanuele-em commented 1 year ago

I'm interested in this but I don't understand @Benabik fix, can you link the repo?

jasondyoungberg commented 6 months ago

I was able to get my tests to run by first compiling the kernel with

cargo test -p kernel --no-run --target x86_64-unknown-none

Then replacing the kernel in build.rs and running it like normal.

I don't yet know how to get cargo test to work, but I'm watching https://github.com/rust-lang/cargo/issues/11680 for a solution