use std::fs; use std::os::unix::fs::symlink; use std::path::Path; use alt_controller_image::initramfs::{ CommandRunner, InitramfsBuilder, InitramfsRecipe, InitramfsRequest, Invocation, KernelVersion, MakeInitrdBuilder, }; use tempfile::tempdir; #[test] fn discovers_the_single_rt_kernel_and_renders_a_sorted_oem_recipe() { let rootfs = tempdir().expect("rootfs directory"); fs::create_dir(rootfs.path().join("boot")).expect("boot directory"); fs::write(rootfs.path().join("boot/vmlinuz-6.12.8-alt1.rt1"), "kernel") .expect("RT kernel"); fs::write(rootfs.path().join("boot/vmlinuz-6.12.8-alt1.std"), "kernel") .expect("non-RT kernel"); let kernel = KernelVersion::discover_rt(rootfs.path()).expect("discover RT kernel"); assert_eq!(kernel.as_str(), "6.12.8-alt1.rt1"); assert_eq!( InitramfsRecipe::new(["rootfs", "compress"], ["virtio_blk.ko", "ext4"]) .expect("valid recipe") .render(), "FEATURES += compress rootfs\nMODULES += ext4 virtio_blk.ko\n" ); } #[test] fn diagnoses_missing_or_ambiguous_rt_kernels() { let rootfs = tempdir().expect("rootfs directory"); fs::create_dir(rootfs.path().join("boot")).expect("boot directory"); let missing = KernelVersion::discover_rt(rootfs.path()).expect_err("missing RT kernel"); assert!(missing.to_string().contains("no RT kernel image")); fs::write(rootfs.path().join("boot/vmlinuz-6.12-rt1"), "kernel").expect("first RT kernel"); fs::write(rootfs.path().join("boot/vmlinuz-6.11-rt1"), "kernel").expect("second RT kernel"); let ambiguous = KernelVersion::discover_rt(rootfs.path()).expect_err("ambiguous RT kernel"); assert!(ambiguous.to_string().contains("multiple RT kernel images")); } #[test] fn kernel_discovery_ignores_non_regular_vmlinuz_entries() { let rootfs = tempdir().expect("rootfs directory"); let boot = rootfs.path().join("boot"); fs::create_dir(&boot).expect("boot directory"); fs::create_dir(boot.join("vmlinuz-6.12-rt-directory")).expect("misleading RT directory"); symlink("/not-a-kernel", boot.join("vmlinuz-6.12-rt-symlink")).expect("misleading RT symlink"); fs::write(boot.join("vmlinuz-6.12-rt-real"), "kernel").expect("RT kernel"); let kernel = KernelVersion::discover_rt(rootfs.path()).expect("discover regular RT kernel"); assert_eq!(kernel.as_str(), "6.12-rt-real"); } #[test] fn make_initrd_adapter_uses_typed_chroot_arguments_and_records_output_digest() { let rootfs = tempdir().expect("rootfs directory"); fs::create_dir(rootfs.path().join("boot")).expect("boot directory"); fs::write(rootfs.path().join("boot/vmlinuz-6.12-rt1"), "kernel").expect("RT kernel"); fs::write(rootfs.path().join("boot/initrd-6.12-rt1.img"), b"generated initrd") .expect("generated initrd"); let request = InitramfsRequest::discover(rootfs.path()).expect("initramfs request"); let mut builder = MakeInitrdBuilder::new(RecordingRunner::default()); let result = builder.build(&request).expect("build initramfs"); assert_eq!( builder.runner().invocations, vec![Invocation::new( "chroot", [ rootfs.path().as_os_str().to_owned(), "make-initrd".into(), "-k".into(), "6.12-rt1".into(), ], )] ); assert_eq!(result.initrd_path(), Path::new("boot/initrd-6.12-rt1.img")); assert_eq!( result.sha256(), "aec42bc86f526931d7c7f01ecba8d643dbb748f9102539a99db5d4855ee4435f" ); assert_eq!( fs::read_link(rootfs.path().join("boot/vmlinuz")).expect("kernel boot symlink"), Path::new("vmlinuz-6.12-rt1") ); assert_eq!( fs::read_link(rootfs.path().join("boot/initrd.img")).expect("initrd boot symlink"), Path::new("initrd-6.12-rt1.img") ); } #[test] fn initramfs_adapter_refuses_to_replace_a_non_symlink_boot_alias() { let rootfs = tempdir().expect("rootfs directory"); let boot = rootfs.path().join("boot"); fs::create_dir(&boot).expect("boot directory"); fs::write(boot.join("vmlinuz-6.12-rt1"), "kernel").expect("RT kernel"); fs::write(boot.join("initrd-6.12-rt1.img"), b"generated initrd").expect("generated initrd"); fs::write(boot.join("initrd.img"), "do not replace").expect("protected boot alias"); let request = InitramfsRequest::discover(rootfs.path()).expect("initramfs request"); let mut builder = MakeInitrdBuilder::new(RecordingRunner::default()); let error = builder.build(&request).expect_err("regular boot alias must be protected"); assert!(error.to_string().contains("refusing to replace non-symlink boot alias")); assert_eq!(fs::read_to_string(boot.join("initrd.img")).expect("protected alias"), "do not replace"); } #[derive(Debug, Default)] struct RecordingRunner { invocations: Vec, } impl CommandRunner for RecordingRunner { fn run(&mut self, invocation: Invocation) -> anyhow::Result<()> { self.invocations.push(invocation); Ok(()) } }