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(()) } } struct FailingInstaller; impl PackageInstaller for FailingInstaller { fn install(&self, _request: &PackageRequest) -> anyhow::Result<()> { anyhow::bail!("simulated package installation failure") } } struct RootfslessInstaller; impl PackageInstaller for RootfslessInstaller { fn install(&self, _request: &PackageRequest) -> anyhow::Result<()> { Ok(()) } } struct FailingInitramfsBuilder; impl InitramfsBuilder for FailingInitramfsBuilder { fn build(&mut self, _request: &InitramfsRequest) -> anyhow::Result { anyhow::bail!("simulated initramfs build failure") } } #[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_an_existing_artifact_before_creating_the_workspace_or_installing() { 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("existing.tar"); fs::write(&artifact, "do not replace").expect("write pre-existing artifact"); let error = executor .execute(&plan, &workspace, "profiles/apt.conf", &artifact) .expect_err("pre-existing artifact must be rejected"); assert!(error.to_string().contains("output artifact already exists")); assert_eq!(fs::read_to_string(&artifact).expect("read artifact"), "do not replace"); assert!(!workspace.exists()); assert!(installer.requests.borrow().is_empty()); } #[test] fn rejects_an_existing_companion_manifest_before_creating_the_workspace_or_installing() { 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("controller.tar"); let companion = ArtifactManifest::path_beside(&artifact).expect("companion manifest path"); fs::write(&companion, "do not replace").expect("write pre-existing companion manifest"); let error = executor .execute(&plan, &workspace, "profiles/apt.conf", &artifact) .expect_err("pre-existing companion manifest must be rejected"); assert!(error.to_string().contains("output manifest already exists")); assert_eq!(fs::read_to_string(&companion).expect("read companion manifest"), "do not replace"); assert!(!workspace.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()); } #[test] fn removes_the_new_workspace_when_package_installation_fails() { 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 mut executor = BuildExecutor::new(FailingInstaller, FixtureInitramfsBuilder); let workspace = fixture.path().join("work"); let artifact = fixture.path().join("image.tar"); let error = executor .execute(&plan, &workspace, "profiles/apt.conf", &artifact) .expect_err("failed installation must fail the build"); assert!(error.to_string().contains("simulated package installation failure")); assert!(!workspace.exists(), "failed build must not leave a workspace"); assert!(!artifact.exists()); } #[test] fn removes_the_new_workspace_when_the_installer_does_not_create_a_rootfs() { 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 mut executor = BuildExecutor::new(RootfslessInstaller, FixtureInitramfsBuilder); let workspace = fixture.path().join("work"); let artifact = fixture.path().join("image.tar"); let error = executor .execute(&plan, &workspace, "profiles/apt.conf", &artifact) .expect_err("missing rootfs must fail the build"); assert!(error.to_string().contains("package installer did not create rootfs")); assert!(!workspace.exists(), "failed build must not leave a workspace"); assert!(!artifact.exists()); } #[test] fn removes_the_new_workspace_when_initramfs_build_fails() { 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 mut executor = BuildExecutor::new(FixtureInstaller::default(), FailingInitramfsBuilder); let workspace = fixture.path().join("work"); let artifact = fixture.path().join("image.tar"); let error = executor .execute(&plan, &workspace, "profiles/apt.conf", &artifact) .expect_err("failed initramfs build must fail the build"); assert!(error.to_string().contains("simulated initramfs build failure")); assert!(!workspace.exists(), "failed build must not leave a workspace"); assert!(!artifact.exists()); }