use std::cell::RefCell; use std::fs; use alt_controller_image::build::BuildExecutor; use alt_controller_image::initramfs::{InitramfsBuilder, InitramfsRequest, InitramfsResult}; use alt_controller_image::manifest::ArtifactManifest; use alt_controller_image::package_installer::{PackageInstaller, PackageRequest}; use alt_controller_image::{BuildPlan, ImageSpec}; use tempfile::tempdir; #[derive(Default)] struct FixtureInstaller { requests: RefCell>, } impl PackageInstaller for FixtureInstaller { fn install(&self, request: &PackageRequest) -> anyhow::Result<()> { self.requests.borrow_mut().push(request.clone()); let boot = request.workdir().join("chroot/boot"); fs::create_dir_all(&boot)?; fs::write(boot.join("vmlinuz-6.12.0-rt1"), "kernel")?; Ok(()) } } #[derive(Default)] struct FixtureInitramfsBuilder; impl InitramfsBuilder for FixtureInitramfsBuilder { fn build(&mut self, request: &InitramfsRequest) -> anyhow::Result { fs::write( request .rootfs() .join(format!("boot/initrd-{}.img", request.kernel().as_str())), "initrd", )?; InitramfsResult::from_rootfs(request.rootfs(), request.kernel()) } } #[test] fn executes_the_native_build_stages_and_writes_a_companion_manifest() { let fixture = tempdir().expect("fixture directory"); let spec = ImageSpec::load(std::path::Path::new("profiles/alt-controller.toml")) .expect("load controller spec"); let plan = BuildPlan::compile(spec).expect("compile build plan"); let installer = FixtureInstaller::default(); let mut executor = BuildExecutor::new(&installer, FixtureInitramfsBuilder); let workspace = fixture.path().join("work"); let artifact = fixture.path().join("out/controller.tar"); let result = executor .execute(&plan, &workspace, "profiles/apt.conf", &artifact) .expect("execute native build"); assert_eq!(installer.requests.borrow().len(), 1); assert_eq!(result.artifact(), artifact); assert_eq!( fs::read_to_string(workspace.join("chroot/etc/initrd.mk.oem")).expect("read OEM recipe"), "FEATURES += add-modules cleanup compress qemu rdshell rootfs usb\nMODULES += ahci.ko ahci_platform.ko ext4 sd_mod.ko virtio_blk.ko virtio_net.ko virtio_pci.ko\n" ); assert!(artifact.is_file()); let manifest = ArtifactManifest::load(result.manifest()).expect("load companion manifest"); let initrd = result.initrd().expect("initrd result"); assert_eq!( manifest.initrd, Some(alt_controller_image::manifest::InitrdRecord::new( initrd.initrd_path().display().to_string(), initrd.sha256(), )) ); } #[test] fn refuses_to_reuse_an_existing_workspace() { let fixture = tempdir().expect("fixture directory"); let spec = ImageSpec::load(std::path::Path::new("profiles/alt-controller.toml")) .expect("load controller spec"); let plan = BuildPlan::compile(spec).expect("compile build plan"); let installer = FixtureInstaller::default(); let mut executor = BuildExecutor::new(&installer, FixtureInitramfsBuilder); let workspace = fixture.path().join("existing-work"); fs::create_dir(&workspace).expect("create existing workspace"); let error = executor .execute(&plan, &workspace, "profiles/apt.conf", fixture.path().join("image.tar")) .expect_err("existing workspace must be rejected"); assert!(error.to_string().contains("workspace already exists")); assert!(installer.requests.borrow().is_empty()); } #[test] fn rejects_a_missing_apt_configuration_before_creating_the_workspace() { let fixture = tempdir().expect("fixture directory"); let spec = ImageSpec::load(std::path::Path::new("profiles/alt-controller.toml")) .expect("load controller spec"); let plan = BuildPlan::compile(spec).expect("compile build plan"); let installer = FixtureInstaller::default(); let mut executor = BuildExecutor::new(&installer, FixtureInitramfsBuilder); let workspace = fixture.path().join("work"); let missing_apt_config = fixture.path().join("missing-apt.conf"); let error = executor .execute(&plan, &workspace, &missing_apt_config, fixture.path().join("image.tar")) .expect_err("missing APT configuration must be rejected"); assert!(error.to_string().contains("APT configuration does not exist")); assert!(!workspace.exists()); assert!(installer.requests.borrow().is_empty()); }