//! `kernel test`, driven through the built binary against a fixture tree. //! //! Nothing here boots a kernel: qemu is taken off `PATH` deliberately, so the //! assertions are about the paths around the boot rather than about a machine //! that can run one. The real boot is the plan's P6 manual step. mod common; use common::Fixture; /// A fixture tree with something at the path qemu would be handed. fn fixture_with_image() -> Fixture { let fixture = Fixture::kernel_tree(); fixture.write("arch/x86/boot/bzImage", "not really a kernel\n"); fixture.commit_all("fixture: add an image"); fixture } #[test] fn a_missing_qemu_is_named_rather_than_panicked_on() { let fixture = fixture_with_image(); // A `PATH` with nothing on it: the binary itself is found by its full // path, and qemu cannot be. let bare = fixture.restricted_path(&[]); let output = fixture.cli_env( &["kernel", "test", "--no-build"], &[("PATH", bare.to_str().expect("the restricted path"))], ); assert_eq!(output.status.code(), Some(1)); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("could not run `qemu-system-x86_64`"), "{stderr}" ); assert!( !String::from_utf8_lossy(&output.stdout).contains("exited cleanly"), "a boot that never started was reported as a success" ); } #[test] fn a_tree_with_no_image_is_named_rather_than_a_build_being_attempted() { let fixture = Fixture::kernel_tree(); let output = fixture.cli(&["kernel", "test", "--no-build"]); assert_eq!(output.status.code(), Some(1)); let stderr = String::from_utf8_lossy(&output.stderr); assert!(stderr.contains("no bootable image at"), "{stderr}"); assert!(stderr.contains("arch/x86/boot/bzImage"), "{stderr}"); } #[test] fn build_runs_make_in_the_tree_and_finds_the_image_it_wrote() { // A Makefile that writes the image where a real build leaves it, and // records that it ran. `sh` is the only tool it needs, so the restricted // `PATH` can hold make and sh and still leave qemu out. let fixture = Fixture::kernel_tree(); fixture.write("arch/x86/boot/.keep", ""); fixture.write( "Makefile", "all:\n\tprintf '' > arch/x86/boot/bzImage\n\tprintf 'built\\n' > .make-call\n", ); fixture.commit_all("fixture: add a Makefile"); let bin = fixture.restricted_path(&["make", "sh"]); let output = fixture.cli_env( &["kernel", "test"], &[("PATH", bin.to_str().expect("the restricted path"))], ); assert_eq!( fixture.read(".make-call"), "built\n", "make did not run in the tree" ); // The image check passed, which is why the failure is qemu's rather than // the image's. assert_eq!(output.status.code(), Some(1)); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("could not run `qemu-system-x86_64`"), "the build did not get as far as qemu: {stderr}" ); } #[test] fn qemu_runs_with_the_arguments_the_plan_describes() { // What qemu was handed, captured without needing a real qemu: the stub is // reached through `PATH`, so the binary under test cannot tell. let fixture = fixture_with_image(); fixture.write_script( "bin/qemu-system-x86_64", "#!/bin/sh\necho \"argv: $*\"\nexit 1\n", ); let bin = fixture.join("bin"); let output = fixture.cli_env( &[ "kernel", "test", "--no-build", "--qemu-arg", "-m", "--qemu-arg", "1G", ], &[("PATH", bin.to_str().expect("the stub bin directory"))], ); assert_eq!( output.status.code(), Some(1), "a failing qemu must not look clean: {}", String::from_utf8_lossy(&output.stdout) ); let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.contains("-kernel ") && stdout.contains("arch/x86/boot/bzImage"), "{stdout}" ); assert!( stdout.contains("-append console=ttyS0"), "the console is what makes a boot readable: {stdout}" ); assert!(stdout.contains("-nographic"), "{stdout}"); assert!(stdout.contains("-m 1G"), "the pass-through args: {stdout}"); assert!( stdout.contains("qemu exited with status 1"), "the exit status was flattened: {stdout}" ); }