diff options
| author | Hermes Agent <hermes@localhost> | 2026-08-12 07:48:21 +0000 |
|---|---|---|
| committer | Hermes Agent <hermes@localhost> | 2026-08-12 07:48:21 +0000 |
| commit | f967d698efd44473a9285e02d0109f3bb0641622 (patch) | |
| tree | c94975571db9426cf71b15c9e6d16346e438c7c6 | |
| parent | 5e6cae20331f8e7cfd551b4656f0de852a4ad44c (diff) | |
Format native builder and satisfy clippy
| -rw-r--r-- | src/archive.rs | 34 | ||||
| -rw-r--r-- | src/build.rs | 34 | ||||
| -rw-r--r-- | src/cli.rs | 13 | ||||
| -rw-r--r-- | src/compare.rs | 105 | ||||
| -rw-r--r-- | src/files.rs | 7 | ||||
| -rw-r--r-- | src/hasher.rs | 2 | ||||
| -rw-r--r-- | src/initramfs.rs | 45 | ||||
| -rw-r--r-- | src/lib.rs | 4 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/manifest.rs | 202 | ||||
| -rw-r--r-- | src/model.rs | 8 | ||||
| -rw-r--r-- | src/package_installer.rs | 7 | ||||
| -rw-r--r-- | src/rootfs.rs | 34 | ||||
| -rw-r--r-- | tests/archive.rs | 47 | ||||
| -rw-r--r-- | tests/build.rs | 71 | ||||
| -rw-r--r-- | tests/cli.rs | 37 | ||||
| -rw-r--r-- | tests/compare.rs | 306 | ||||
| -rw-r--r-- | tests/initramfs.rs | 28 | ||||
| -rw-r--r-- | tests/model_validation.rs | 5 | ||||
| -rw-r--r-- | tests/package_installer.rs | 34 | ||||
| -rw-r--r-- | tests/rootfs_finalization.rs | 63 | ||||
| -rw-r--r-- | tests/stage_graph.rs | 5 |
22 files changed, 808 insertions, 285 deletions
diff --git a/src/archive.rs b/src/archive.rs index c0a2195..fd4d899 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -3,7 +3,7 @@ use std::fs::{self, File, OpenOptions}; use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use tar::{Builder, EntryType, Header}; /// Writes a deterministic rootfs tar archive using numeric ownership metadata. @@ -28,7 +28,10 @@ impl NativeTarWriter { let output = output.as_ref(); reject_output_inside_rootfs(rootfs, output)?; if output.exists() { - bail!("archive output already exists; refusing to overwrite it: {}", output.display()); + bail!( + "archive output already exists; refusing to overwrite it: {}", + output.display() + ); } let temporary = temporary_output_path(output)?; let result = (|| { @@ -67,9 +70,12 @@ fn reject_output_inside_rootfs(rootfs: &Path, output: &Path) -> Result<()> { .canonicalize() .with_context(|| format!("canonicalize rootfs {}", rootfs.display()))?; let output_parent = output.parent().unwrap_or_else(|| Path::new(".")); - let output_parent = output_parent - .canonicalize() - .with_context(|| format!("canonicalize archive output parent {}", output_parent.display()))?; + let output_parent = output_parent.canonicalize().with_context(|| { + format!( + "canonicalize archive output parent {}", + output_parent.display() + ) + })?; if output_parent.starts_with(&rootfs) { bail!( "archive output must not be inside rootfs: {} is below {}", @@ -81,9 +87,9 @@ fn reject_output_inside_rootfs(rootfs: &Path, output: &Path) -> Result<()> { } fn temporary_output_path(output: &Path) -> Result<PathBuf> { - let name = output - .file_name() - .ok_or_else(|| anyhow::anyhow!("archive output path has no filename: {}", output.display()))?; + let name = output.file_name().ok_or_else(|| { + anyhow::anyhow!("archive output path has no filename: {}", output.display()) + })?; let mut temporary_name = name.to_os_string(); temporary_name.push(".partial"); Ok(output.with_file_name(temporary_name)) @@ -139,7 +145,11 @@ fn deterministic_header(metadata: &fs::Metadata, entry_type: EntryType, size: u6 header } -fn append_directory(archive: &mut Builder<File>, path: &Path, metadata: &fs::Metadata) -> Result<()> { +fn append_directory( + archive: &mut Builder<File>, + path: &Path, + metadata: &fs::Metadata, +) -> Result<()> { let mut header = deterministic_header(metadata, EntryType::Directory, 0); archive .append_data(&mut header, path, std::io::empty()) @@ -158,7 +168,8 @@ fn append_file( return append_hardlink(archive, path, target, metadata); } let mut header = deterministic_header(metadata, EntryType::Regular, metadata.len()); - let input = File::open(source).with_context(|| format!("open rootfs file {}", source.display()))?; + let input = + File::open(source).with_context(|| format!("open rootfs file {}", source.display()))?; archive .append_data(&mut header, path, input) .with_context(|| format!("append file {}", path.display()))?; @@ -184,7 +195,8 @@ fn append_symlink( path: &Path, metadata: &fs::Metadata, ) -> Result<()> { - let target = fs::read_link(source).with_context(|| format!("read rootfs symlink {}", source.display()))?; + let target = fs::read_link(source) + .with_context(|| format!("read rootfs symlink {}", source.display()))?; let mut header = deterministic_header(metadata, EntryType::Symlink, 0); archive .append_link(&mut header, path, target) diff --git a/src/build.rs b/src/build.rs index 1fd7720..7fd3989 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,13 +1,13 @@ use std::path::{Path, PathBuf}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; +use crate::BuildPlan; use crate::archive::NativeTarWriter; use crate::initramfs::{InitramfsBuilder, InitramfsRequest, InitramfsResult}; use crate::manifest::{ArtifactManifest, InitrdRecord}; use crate::package_installer::PackageInstaller; use crate::rootfs::{InitrdOem, RootfsFinalization}; -use crate::BuildPlan; /// Executes the native stages after a plan has been validated. /// @@ -22,7 +22,11 @@ pub struct BuildExecutor<I, B> { impl<I, B> BuildExecutor<I, B> { pub fn new(installer: I, initramfs_builder: B) -> Self { - Self { installer, initramfs_builder, tar_writer: NativeTarWriter::new() } + Self { + installer, + initramfs_builder, + tar_writer: NativeTarWriter::new(), + } } } @@ -36,20 +40,31 @@ impl<I: PackageInstaller, B: InitramfsBuilder> BuildExecutor<I, B> { ) -> Result<BuildResult> { let workspace = workspace.as_ref(); if workspace.exists() { - bail!("workspace already exists; refusing to reuse it: {}", workspace.display()); + bail!( + "workspace already exists; refusing to reuse it: {}", + workspace.display() + ); } let artifact = artifact.as_ref(); if artifact.exists() { - bail!("output artifact already exists; refusing to overwrite it: {}", artifact.display()); + bail!( + "output artifact already exists; refusing to overwrite it: {}", + artifact.display() + ); } let manifest = ArtifactManifest::path_beside(artifact)?; if manifest.exists() { - bail!("output manifest already exists; refusing to overwrite it: {}", manifest.display()); + bail!( + "output manifest already exists; refusing to overwrite it: {}", + manifest.display() + ); } let parent = artifact .parent() .filter(|parent| !parent.as_os_str().is_empty()) - .ok_or_else(|| anyhow::anyhow!("artifact path has no parent: {}", artifact.display()))?; + .ok_or_else(|| { + anyhow::anyhow!("artifact path has no parent: {}", artifact.display()) + })?; if parent.exists() && !parent.is_dir() { bail!("artifact parent is not a directory: {}", parent.display()); } @@ -67,7 +82,10 @@ impl<I: PackageInstaller, B: InitramfsBuilder> BuildExecutor<I, B> { if !rootfs.is_dir() { return Self::fail_and_remove_workspace( workspace, - anyhow::anyhow!("package installer did not create rootfs: {}", rootfs.display()), + anyhow::anyhow!( + "package installer did not create rootfs: {}", + rootfs.display() + ), ); } if let Err(error) = RootfsFinalization::new( @@ -1,6 +1,6 @@ use std::path::PathBuf; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use clap::{Parser, Subcommand}; use crate::build::BuildExecutor; @@ -77,7 +77,11 @@ pub fn run(cli: Cli) -> Result<()> { println!("artifact: {}", result.artifact().display()); println!("manifest: {}", result.manifest().display()); if let Some(initrd) = result.initrd() { - println!("initrd: {} sha256:{}", initrd.initrd_path().display(), initrd.sha256()); + println!( + "initrd: {} sha256:{}", + initrd.initrd_path().display(), + initrd.sha256() + ); } Ok(()) } @@ -101,7 +105,10 @@ pub fn run(cli: Cli) -> Result<()> { } fn load_artifact(path: &std::path::Path) -> Result<ArtifactManifest> { - if matches!(path.extension().and_then(|extension| extension.to_str()), Some("toml")) { + if matches!( + path.extension().and_then(|extension| extension.to_str()), + Some("toml") + ) { return ArtifactManifest::load(path); } diff --git a/src/compare.rs b/src/compare.rs index 8b59c1c..3382d95 100644 --- a/src/compare.rs +++ b/src/compare.rs @@ -50,11 +50,21 @@ impl ComparisonReport { let mut output = String::new(); for difference in &self.differences { match difference { - SemanticDifference::Package { name, change } => writeln!(output, "{} package {name}", change.verb()), - SemanticDifference::File { path, change } => writeln!(output, "{} file {path}", change.verb()), - SemanticDifference::Initrd { change } => writeln!(output, "{} initrd", change.verb()), - SemanticDifference::Service { name, change } => writeln!(output, "{} service {name}", change.verb()), - SemanticDifference::ArchiveMember { path, change } => writeln!(output, "{} archive member {path}", change.verb()), + SemanticDifference::Package { name, change } => { + writeln!(output, "{} package {name}", change.verb()) + } + SemanticDifference::File { path, change } => { + writeln!(output, "{} file {path}", change.verb()) + } + SemanticDifference::Initrd { change } => { + writeln!(output, "{} initrd", change.verb()) + } + SemanticDifference::Service { name, change } => { + writeln!(output, "{} service {name}", change.verb()) + } + SemanticDifference::ArchiveMember { path, change } => { + writeln!(output, "{} archive member {path}", change.verb()) + } } .expect("writing to String cannot fail"); } @@ -64,26 +74,60 @@ impl ComparisonReport { pub fn compare(left: &ArtifactManifest, right: &ArtifactManifest) -> ComparisonReport { let mut differences = Vec::new(); - compare_records(&left.packages, &right.packages, |record| &record.name, |name, change| { - SemanticDifference::Package { name: name.to_owned(), change } - }, &mut differences); - compare_records(&left.files, &right.files, |record| &record.path, |path, change| { - SemanticDifference::File { path: path.to_owned(), change } - }, &mut differences); + compare_records( + &left.packages, + &right.packages, + |record| &record.name, + |name, change| SemanticDifference::Package { + name: name.to_owned(), + change, + }, + &mut differences, + ); + compare_records( + &left.files, + &right.files, + |record| &record.path, + |path, change| SemanticDifference::File { + path: path.to_owned(), + change, + }, + &mut differences, + ); match (&left.initrd, &right.initrd) { - (None, Some(_)) => differences.push(SemanticDifference::Initrd { change: Change::Added }), - (Some(_), None) => differences.push(SemanticDifference::Initrd { change: Change::Removed }), + (None, Some(_)) => differences.push(SemanticDifference::Initrd { + change: Change::Added, + }), + (Some(_), None) => differences.push(SemanticDifference::Initrd { + change: Change::Removed, + }), (Some(left), Some(right)) if left != right => { - differences.push(SemanticDifference::Initrd { change: Change::Changed }); + differences.push(SemanticDifference::Initrd { + change: Change::Changed, + }); } _ => {} } - compare_records(&left.services, &right.services, |record| &record.name, |name, change| { - SemanticDifference::Service { name: name.to_owned(), change } - }, &mut differences); - compare_records(&left.archive_members, &right.archive_members, |record| &record.path, |path, change| { - SemanticDifference::ArchiveMember { path: path.to_owned(), change } - }, &mut differences); + compare_records( + &left.services, + &right.services, + |record| &record.name, + |name, change| SemanticDifference::Service { + name: name.to_owned(), + change, + }, + &mut differences, + ); + compare_records( + &left.archive_members, + &right.archive_members, + |record| &record.path, + |path, change| SemanticDifference::ArchiveMember { + path: path.to_owned(), + change, + }, + &mut differences, + ); differences.sort(); ComparisonReport { differences } } @@ -95,13 +139,26 @@ fn compare_records<T: PartialEq>( difference: impl Fn(&str, Change) -> SemanticDifference, output: &mut Vec<SemanticDifference>, ) { - let left = left.iter().map(|record| (key(record), record)).collect::<BTreeMap<_, _>>(); - let right = right.iter().map(|record| (key(record), record)).collect::<BTreeMap<_, _>>(); - for name in left.keys().chain(right.keys()).copied().collect::<std::collections::BTreeSet<_>>() { + let left = left + .iter() + .map(|record| (key(record), record)) + .collect::<BTreeMap<_, _>>(); + let right = right + .iter() + .map(|record| (key(record), record)) + .collect::<BTreeMap<_, _>>(); + for name in left + .keys() + .chain(right.keys()) + .copied() + .collect::<std::collections::BTreeSet<_>>() + { match (left.get(name), right.get(name)) { (None, Some(_)) => output.push(difference(name, Change::Added)), (Some(_), None) => output.push(difference(name, Change::Removed)), - (Some(left), Some(right)) if left != right => output.push(difference(name, Change::Changed)), + (Some(left), Some(right)) if left != right => { + output.push(difference(name, Change::Changed)) + } _ => {} } } diff --git a/src/files.rs b/src/files.rs index f49bd5e..97521f8 100644 --- a/src/files.rs +++ b/src/files.rs @@ -1,6 +1,6 @@ use std::path::{Component, Path, PathBuf}; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct RootfsPath(PathBuf); @@ -9,7 +9,10 @@ impl RootfsPath { pub fn new(path: impl AsRef<Path>) -> Result<Self> { let path = path.as_ref(); if path.as_os_str().is_empty() || path.is_absolute() { - bail!("rootfs path must be a non-empty relative path: {}", path.display()); + bail!( + "rootfs path must be a non-empty relative path: {}", + path.display() + ); } let mut normalized = PathBuf::new(); diff --git a/src/hasher.rs b/src/hasher.rs index 0be1808..da00778 100644 --- a/src/hasher.rs +++ b/src/hasher.rs @@ -1,7 +1,7 @@ use std::ffi::OsString; use std::process::Command; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use crate::package_installer::{PackageInstaller, PackageRequest}; diff --git a/src/initramfs.rs b/src/initramfs.rs index 4475aed..a108b85 100644 --- a/src/initramfs.rs +++ b/src/initramfs.rs @@ -4,7 +4,7 @@ use std::os::unix::fs::symlink; use std::path::{Path, PathBuf}; use std::process::Command; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use sha2::{Digest, Sha256}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -32,7 +32,11 @@ impl KernelVersion { match kernels.as_slice() { [] => bail!("no RT kernel image found in {}", boot.display()), [version] => Ok(Self(version.clone())), - _ => bail!("multiple RT kernel images found in {}: {}", boot.display(), kernels.join(", ")), + _ => bail!( + "multiple RT kernel images found in {}: {}", + boot.display(), + kernels.join(", ") + ), } } @@ -70,7 +74,10 @@ impl InitramfsRecipe { } } -fn normalize(kind: &str, values: impl IntoIterator<Item = impl Into<String>>) -> Result<Vec<String>> { +fn normalize( + kind: &str, + values: impl IntoIterator<Item = impl Into<String>>, +) -> Result<Vec<String>> { values .into_iter() .map(Into::into) @@ -117,8 +124,14 @@ pub struct Invocation { } impl Invocation { - pub fn new(program: impl Into<OsString>, arguments: impl IntoIterator<Item = impl Into<OsString>>) -> Self { - Self { program: program.into(), arguments: arguments.into_iter().map(Into::into).collect() } + pub fn new( + program: impl Into<OsString>, + arguments: impl IntoIterator<Item = impl Into<OsString>>, + ) -> Self { + Self { + program: program.into(), + arguments: arguments.into_iter().map(Into::into).collect(), + } } } @@ -136,7 +149,10 @@ impl CommandRunner for ProcessRunner { .status() .with_context(|| format!("run {}", invocation.program.to_string_lossy()))?; if !status.success() { - bail!("{} exited with {status}", invocation.program.to_string_lossy()); + bail!( + "{} exited with {status}", + invocation.program.to_string_lossy() + ); } Ok(()) } @@ -193,11 +209,17 @@ fn write_boot_alias(rootfs: &Path, alias: &str, target: &Path) -> Result<()> { let path = rootfs.join("boot").join(alias); match fs::symlink_metadata(&path) { Ok(metadata) if metadata.file_type().is_symlink() => { - fs::remove_file(&path).with_context(|| format!("remove existing boot alias {}", path.display()))?; + fs::remove_file(&path) + .with_context(|| format!("remove existing boot alias {}", path.display()))?; } - Ok(_) => bail!("refusing to replace non-symlink boot alias: {}", path.display()), + Ok(_) => bail!( + "refusing to replace non-symlink boot alias: {}", + path.display() + ), Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} - Err(error) => return Err(error).with_context(|| format!("inspect boot alias {}", path.display())), + Err(error) => { + return Err(error).with_context(|| format!("inspect boot alias {}", path.display())); + } } symlink(target, &path).with_context(|| format!("create boot alias {}", path.display())) } @@ -214,7 +236,10 @@ impl InitramfsResult { let initrd_path = PathBuf::from(format!("boot/initrd-{}.img", kernel.as_str())); let contents = fs::read(rootfs.join(&initrd_path)) .with_context(|| format!("read generated initrd {}", initrd_path.display()))?; - Ok(Self { initrd_path, sha256: format!("{:x}", Sha256::digest(contents)) }) + Ok(Self { + initrd_path, + sha256: format!("{:x}", Sha256::digest(contents)), + }) } pub fn initrd_path(&self) -> &Path { @@ -12,6 +12,8 @@ pub mod plan; pub mod rootfs; pub mod stage; -pub use model::{Architecture, BootSpec, ImageSpec, OutputFormat, PackageSelector, PackageSpec, Target}; +pub use model::{ + Architecture, BootSpec, ImageSpec, OutputFormat, PackageSelector, PackageSpec, Target, +}; pub use plan::BuildPlan; pub use stage::Stage; diff --git a/src/main.rs b/src/main.rs index 1e06263..3ae6b3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use alt_controller_image::cli::{run, Cli}; +use alt_controller_image::cli::{Cli, run}; use clap::Parser; fn main() { diff --git a/src/manifest.rs b/src/manifest.rs index 067dc9a..0821fab 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -4,7 +4,7 @@ use std::io::{Read, Write}; use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use tar::{Archive, EntryType}; @@ -34,18 +34,41 @@ impl ArtifactManifest { validate_file_records(&files)?; validate_initrd_record(initrd.as_ref())?; validate_manifest_paths(&files, initrd.as_ref(), &archive_members)?; - ensure_unique("package", packages.iter().map(|record| record.name.as_str()))?; + ensure_unique( + "package", + packages.iter().map(|record| record.name.as_str()), + )?; ensure_unique("file", files.iter().map(|record| record.path.as_str()))?; - ensure_unique("service", services.iter().map(|record| record.name.as_str()))?; - ensure_unique("archive member", archive_members.iter().map(|record| record.path.as_str()))?; - Ok(Self { packages, files, initrd, services, archive_members }) + ensure_unique( + "service", + services.iter().map(|record| record.name.as_str()), + )?; + ensure_unique( + "archive member", + archive_members.iter().map(|record| record.path.as_str()), + )?; + Ok(Self { + packages, + files, + initrd, + services, + archive_members, + }) } pub fn load(path: impl AsRef<Path>) -> Result<Self> { let path = path.as_ref(); - let text = fs::read_to_string(path).with_context(|| format!("read artifact manifest {}", path.display()))?; - let manifest: Self = toml::from_str(&text).with_context(|| format!("parse artifact manifest {}", path.display()))?; - Self::new(manifest.packages, manifest.files, manifest.initrd, manifest.services, manifest.archive_members) + let text = fs::read_to_string(path) + .with_context(|| format!("read artifact manifest {}", path.display()))?; + let manifest: Self = toml::from_str(&text) + .with_context(|| format!("parse artifact manifest {}", path.display()))?; + Self::new( + manifest.packages, + manifest.files, + manifest.initrd, + manifest.services, + manifest.archive_members, + ) } pub fn write_beside(&self, artifact: impl AsRef<Path>) -> Result<PathBuf> { @@ -67,9 +90,9 @@ impl ArtifactManifest { /// directory. pub fn path_beside(artifact: impl AsRef<Path>) -> Result<PathBuf> { let artifact = artifact.as_ref(); - let name = artifact - .file_name() - .ok_or_else(|| anyhow::anyhow!("artifact path has no filename: {}", artifact.display()))?; + let name = artifact.file_name().ok_or_else(|| { + anyhow::anyhow!("artifact path has no filename: {}", artifact.display()) + })?; let mut manifest_name = name.to_os_string(); manifest_name.push(".manifest.toml"); Ok(artifact.with_file_name(manifest_name)) @@ -134,7 +157,10 @@ impl ArtifactManifest { EntryType::Directory => "directory", EntryType::Symlink => "symlink", EntryType::Link => "hardlink", - other => bail!("unsupported archive member type {other:?} in {}", archive_path.display()), + other => bail!( + "unsupported archive member type {other:?} in {}", + archive_path.display() + ), }; archive_members.push(ArchiveMemberRecord::new(path.clone(), kind)); @@ -144,7 +170,10 @@ impl ArtifactManifest { if path.starts_with("boot/initrd-") && path.ends_with(".img") { let record = InitrdRecord::new(path.clone(), digest.clone()); if initrd.replace(record).is_some() { - bail!("multiple initrd images found in archive {}", archive_path.display()); + bail!( + "multiple initrd images found in archive {}", + archive_path.display() + ); } } if !path.starts_with("boot/initrd-") || !path.ends_with(".img") { @@ -194,7 +223,10 @@ fn collect_files(rootfs: &Path, initrd: Option<&InitrdRecord>) -> Result<Vec<Fil entries.sort_by_key(|entry| entry.path().to_path_buf()); let mut hardlink_targets: BTreeMap<(u64, u64), String> = BTreeMap::new(); for entry in entries { - let relative = entry.path().strip_prefix(rootfs).expect("walk entry is below rootfs"); + let relative = entry + .path() + .strip_prefix(rootfs) + .expect("walk entry is below rootfs"); if is_internal_metadata_path(&portable_path(relative)?) { continue; } @@ -204,7 +236,9 @@ fn collect_files(rootfs: &Path, initrd: Option<&InitrdRecord>) -> Result<Vec<Fil } let file_type = entry.file_type(); if file_type.is_file() { - let metadata = entry.metadata().with_context(|| format!("inspect rootfs file {}", entry.path().display()))?; + let metadata = entry + .metadata() + .with_context(|| format!("inspect rootfs file {}", entry.path().display()))?; let key = (metadata.dev(), metadata.ino()); if let Some(target) = hardlink_targets.get(&key) { files.push(FileRecord::hardlink(path, target.clone())); @@ -230,7 +264,8 @@ fn collect_services(rootfs: &Path) -> Result<Vec<ServiceRecord>> { } let mut services = Vec::new(); for entry in WalkDir::new(&system).follow_links(false).min_depth(2) { - let entry = entry.with_context(|| format!("walk service state directory {}", system.display()))?; + let entry = + entry.with_context(|| format!("walk service state directory {}", system.display()))?; let parent_is_wants_directory = entry .path() .parent() @@ -239,10 +274,9 @@ fn collect_services(rootfs: &Path) -> Result<Vec<ServiceRecord>> { if !parent_is_wants_directory || !entry.file_type().is_symlink() { continue; } - let name = entry - .file_name() - .to_str() - .ok_or_else(|| anyhow::anyhow!("non-UTF-8 service name in {}", entry.path().display()))?; + let name = entry.file_name().to_str().ok_or_else(|| { + anyhow::anyhow!("non-UTF-8 service name in {}", entry.path().display()) + })?; if name.ends_with(".service") { services.push(ServiceRecord::new(name, true)); } @@ -257,11 +291,16 @@ fn coalesce_enabled_services(mut services: Vec<ServiceRecord>) -> Vec<ServiceRec } fn collect_archive_members(archive_path: &Path) -> Result<Vec<ArchiveMemberRecord>> { - let file = fs::File::open(archive_path).with_context(|| format!("open archive {}", archive_path.display()))?; + let file = fs::File::open(archive_path) + .with_context(|| format!("open archive {}", archive_path.display()))?; let mut archive = Archive::new(file); let mut members = Vec::new(); - for entry in archive.entries().with_context(|| format!("read archive {}", archive_path.display()))? { - let entry = entry.with_context(|| format!("read archive member from {}", archive_path.display()))?; + for entry in archive + .entries() + .with_context(|| format!("read archive {}", archive_path.display()))? + { + let entry = entry + .with_context(|| format!("read archive member from {}", archive_path.display()))?; let path = portable_path(&entry.path()?)?; if is_archive_root_path(&path) || is_internal_metadata_path(&path) { continue; @@ -271,7 +310,10 @@ fn collect_archive_members(archive_path: &Path) -> Result<Vec<ArchiveMemberRecor EntryType::Directory => "directory", EntryType::Symlink => "symlink", EntryType::Link => "hardlink", - other => bail!("unsupported archive member type {other:?} in {}", archive_path.display()), + other => bail!( + "unsupported archive member type {other:?} in {}", + archive_path.display() + ), }; members.push(ArchiveMemberRecord::new(path, kind)); } @@ -279,7 +321,8 @@ fn collect_archive_members(archive_path: &Path) -> Result<Vec<ArchiveMemberRecor } fn sha256_file(path: &Path) -> Result<String> { - let mut file = fs::File::open(path).with_context(|| format!("open rootfs file {}", path.display()))?; + let mut file = + fs::File::open(path).with_context(|| format!("open rootfs file {}", path.display()))?; sha256_reader(&mut file, path) } @@ -287,7 +330,9 @@ fn sha256_reader(reader: &mut impl Read, source: &Path) -> Result<String> { let mut hasher = Sha256::new(); let mut buffer = [0; 8192]; loop { - let read = reader.read(&mut buffer).with_context(|| format!("read {}", source.display()))?; + let read = reader + .read(&mut buffer) + .with_context(|| format!("read {}", source.display()))?; if read == 0 { break; } @@ -298,9 +343,17 @@ fn sha256_reader(reader: &mut impl Read, source: &Path) -> Result<String> { fn portable_path(path: &Path) -> Result<String> { if path.is_absolute() { - bail!("absolute path is not valid in an artifact manifest: {}", path.display()); + bail!( + "absolute path is not valid in an artifact manifest: {}", + path.display() + ); } - let value = path.to_str().ok_or_else(|| anyhow::anyhow!("non-UTF-8 path is not valid in an artifact manifest: {}", path.display()))?; + let value = path.to_str().ok_or_else(|| { + anyhow::anyhow!( + "non-UTF-8 path is not valid in an artifact manifest: {}", + path.display() + ) + })?; if value.is_empty() { bail!("empty path is not valid in an artifact manifest"); } @@ -310,19 +363,28 @@ fn portable_path(path: &Path) -> Result<String> { std::path::Component::Normal(part) => normalized.push(part), std::path::Component::CurDir => {} std::path::Component::ParentDir => { - bail!("parent path is not valid in an artifact manifest: {}", path.display()); + bail!( + "parent path is not valid in an artifact manifest: {}", + path.display() + ); } std::path::Component::RootDir | std::path::Component::Prefix(_) => { - bail!("absolute path is not valid in an artifact manifest: {}", path.display()); + bail!( + "absolute path is not valid in an artifact manifest: {}", + path.display() + ); } } } if normalized.as_os_str().is_empty() { return Ok(".".into()); } - let normalized = normalized - .to_str() - .ok_or_else(|| anyhow::anyhow!("non-UTF-8 path is not valid in an artifact manifest: {}", path.display()))?; + let normalized = normalized.to_str().ok_or_else(|| { + anyhow::anyhow!( + "non-UTF-8 path is not valid in an artifact manifest: {}", + path.display() + ) + })?; Ok(normalized.to_owned()) } @@ -349,7 +411,8 @@ fn is_enabled_service(path: &str) -> bool { } fn is_internal_metadata_path(path: &str) -> bool { - path.split('/').any(|component| matches!(component, ".host" | ".fakedata")) + path.split('/') + .any(|component| matches!(component, ".host" | ".fakedata")) } fn is_archive_root_path(path: &str) -> bool { @@ -364,7 +427,12 @@ fn service_name(path: &str) -> Result<&str> { } fn link_target(path: &Path) -> Result<String> { - let value = path.to_str().ok_or_else(|| anyhow::anyhow!("non-UTF-8 symlink target is not valid in an artifact manifest: {}", path.display()))?; + let value = path.to_str().ok_or_else(|| { + anyhow::anyhow!( + "non-UTF-8 symlink target is not valid in an artifact manifest: {}", + path.display() + ) + })?; if value.is_empty() { bail!("empty symlink target is not valid in an artifact manifest"); } @@ -376,7 +444,11 @@ fn validate_manifest_paths( initrd: Option<&InitrdRecord>, archive_members: &[ArchiveMemberRecord], ) -> Result<()> { - for path in files.iter().map(|record| record.path.as_str()).chain(archive_members.iter().map(|record| record.path.as_str())) { + for path in files + .iter() + .map(|record| record.path.as_str()) + .chain(archive_members.iter().map(|record| record.path.as_str())) + { portable_path(Path::new(path))?; } if let Some(initrd) = initrd { @@ -386,10 +458,11 @@ fn validate_manifest_paths( } fn validate_initrd_record(record: Option<&InitrdRecord>) -> Result<()> { - if let Some(record) = record { - if record.sha256.is_empty() { + match record { + Some(record) if record.sha256.is_empty() => { bail!("initrd record requires a sha256 digest: {}", record.path); } + _ => {} } Ok(()) } @@ -407,13 +480,21 @@ fn validate_file_records(records: &[FileRecord]) -> Result<()> { } "symlink" | "hardlink" => { if record.digest.is_some() { - bail!("{} record cannot carry a digest: {}", record.kind, record.path); + bail!( + "{} record cannot carry a digest: {}", + record.kind, + record.path + ); } if record.target.as_deref().is_none_or(str::is_empty) { bail!("{} record requires a target: {}", record.kind, record.path); } } - _ => bail!("unsupported file record kind {}: {}", record.kind, record.path), + _ => bail!( + "unsupported file record kind {}: {}", + record.kind, + record.path + ), } } Ok(()) @@ -437,7 +518,10 @@ pub struct PackageRecord { impl PackageRecord { pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self { - Self { name: name.into(), version: version.into() } + Self { + name: name.into(), + version: version.into(), + } } } @@ -451,15 +535,30 @@ pub struct FileRecord { impl FileRecord { pub fn file(path: impl Into<String>, digest: impl Into<String>) -> Self { - Self { path: path.into(), kind: "file".into(), digest: Some(digest.into()), target: None } + Self { + path: path.into(), + kind: "file".into(), + digest: Some(digest.into()), + target: None, + } } pub fn symlink(path: impl Into<String>, target: impl Into<String>) -> Self { - Self { path: path.into(), kind: "symlink".into(), digest: None, target: Some(target.into()) } + Self { + path: path.into(), + kind: "symlink".into(), + digest: None, + target: Some(target.into()), + } } pub fn hardlink(path: impl Into<String>, target: impl Into<String>) -> Self { - Self { path: path.into(), kind: "hardlink".into(), digest: None, target: Some(target.into()) } + Self { + path: path.into(), + kind: "hardlink".into(), + digest: None, + target: Some(target.into()), + } } } @@ -471,7 +570,10 @@ pub struct InitrdRecord { impl InitrdRecord { pub fn new(path: impl Into<String>, sha256: impl Into<String>) -> Self { - Self { path: path.into(), sha256: sha256.into() } + Self { + path: path.into(), + sha256: sha256.into(), + } } } @@ -483,7 +585,10 @@ pub struct ServiceRecord { impl ServiceRecord { pub fn new(name: impl Into<String>, enabled: bool) -> Self { - Self { name: name.into(), enabled } + Self { + name: name.into(), + enabled, + } } } @@ -495,6 +600,9 @@ pub struct ArchiveMemberRecord { impl ArchiveMemberRecord { pub fn new(path: impl Into<String>, kind: impl Into<String>) -> Self { - Self { path: path.into(), kind: kind.into() } + Self { + path: path.into(), + kind: kind.into(), + } } } diff --git a/src/model.rs b/src/model.rs index 13fa5e8..a0bc51c 100644 --- a/src/model.rs +++ b/src/model.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use std::fs; use std::path::Path; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use serde::Deserialize; #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] @@ -130,9 +130,9 @@ impl ImageSpec { for module in &self.boot.initrd_modules { if module.is_empty() || module.contains('/') - || !module - .chars() - .all(|character| character.is_ascii_alphanumeric() || matches!(character, '.' | '_' | '-')) + || !module.chars().all(|character| { + character.is_ascii_alphanumeric() || matches!(character, '.' | '_' | '-') + }) { bail!("invalid initrd module: {module}"); } diff --git a/src/package_installer.rs b/src/package_installer.rs index 03e429f..ec84add 100644 --- a/src/package_installer.rs +++ b/src/package_installer.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct AptConfig(PathBuf); @@ -12,7 +12,10 @@ impl AptConfig { bail!("APT configuration must be a file path: {}", path.display()); } if !path.is_file() { - bail!("APT configuration does not exist or is not a regular file: {}", path.display()); + bail!( + "APT configuration does not exist or is not a regular file: {}", + path.display() + ); } Ok(Self(path.to_path_buf())) } diff --git a/src/rootfs.rs b/src/rootfs.rs index ea7d87b..79406a7 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -3,9 +3,9 @@ use std::fs; use std::os::unix::fs::symlink; use std::path::{Path, PathBuf}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; -use crate::files::{relative_symlink_is_safe, RootfsPath}; +use crate::files::{RootfsPath, relative_symlink_is_safe}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct CopyTree { @@ -127,7 +127,13 @@ fn copy_tree(rootfs: &Path, copy: &CopyTree, manifest: &mut MutationManifest) -> if !copy.source.is_dir() { bail!("copy source is not a directory: {}", copy.source.display()); } - copy_entry(rootfs, ©.source, copy.destination.as_path(), Path::new(""), manifest) + copy_entry( + rootfs, + ©.source, + copy.destination.as_path(), + Path::new(""), + manifest, + ) } fn copy_entry( @@ -196,7 +202,11 @@ fn write_file( Ok(()) } -fn enable_service(rootfs: &Path, service: &ServiceName, manifest: &mut MutationManifest) -> Result<()> { +fn enable_service( + rootfs: &Path, + service: &ServiceName, + manifest: &mut MutationManifest, +) -> Result<()> { let destination = RootfsPath::new(format!( "etc/systemd/system/multi-user.target.wants/{}", service.0 @@ -217,20 +227,28 @@ fn replace_with_file(source: &Path, output: &Path) -> Result<()> { Ok(metadata) if metadata.file_type().is_dir() => { bail!("cannot replace directory with file: {}", output.display()); } - Ok(_) => fs::remove_file(output).with_context(|| format!("replace {}", output.display()))?, + Ok(_) => { + fs::remove_file(output).with_context(|| format!("replace {}", output.display()))? + } Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} Err(error) => return Err(error).with_context(|| format!("inspect {}", output.display())), } - fs::copy(source, output).with_context(|| format!("copy {} to {}", source.display(), output.display()))?; + fs::copy(source, output) + .with_context(|| format!("copy {} to {}", source.display(), output.display()))?; Ok(()) } fn replace_with_symlink(target: &Path, output: &Path) -> Result<()> { match fs::symlink_metadata(output) { Ok(metadata) if metadata.file_type().is_dir() => { - bail!("cannot replace directory with symlink: {}", output.display()); + bail!( + "cannot replace directory with symlink: {}", + output.display() + ); + } + Ok(_) => { + fs::remove_file(output).with_context(|| format!("replace {}", output.display()))? } - Ok(_) => fs::remove_file(output).with_context(|| format!("replace {}", output.display()))?, Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} Err(error) => return Err(error).with_context(|| format!("inspect {}", output.display())), } diff --git a/tests/archive.rs b/tests/archive.rs index 6e59837..83f6722 100644 --- a/tests/archive.rs +++ b/tests/archive.rs @@ -20,10 +20,17 @@ fn writes_deterministic_tar_with_files_directories_and_symlinks() { let first = fixture.path().join("first.tar"); let second = fixture.path().join("second.tar"); - NativeTarWriter::new().write(&rootfs, &first).expect("write first archive"); - NativeTarWriter::new().write(&rootfs, &second).expect("write second archive"); + NativeTarWriter::new() + .write(&rootfs, &first) + .expect("write first archive"); + NativeTarWriter::new() + .write(&rootfs, &second) + .expect("write second archive"); - assert_eq!(fs::read(&first).expect("read first"), fs::read(&second).expect("read second")); + assert_eq!( + fs::read(&first).expect("read first"), + fs::read(&second).expect("read second") + ); let mut archive = Archive::new(fs::File::open(first).expect("open archive")); let entries = archive @@ -32,7 +39,13 @@ fn writes_deterministic_tar_with_files_directories_and_symlinks() { .map(|entry| { let entry = entry.expect("read entry"); let path = entry.path().expect("entry path").into_owned(); - (path, entry.header().entry_type(), entry.header().mtime().expect("mtime"), entry.header().uid().expect("uid"), entry.header().gid().expect("gid")) + ( + path, + entry.header().entry_type(), + entry.header().mtime().expect("mtime"), + entry.header().uid().expect("uid"), + entry.header().gid().expect("gid"), + ) }) .collect::<Vec<_>>(); @@ -78,7 +91,10 @@ fn preserves_hardlinks_in_the_archive() { ( entry.path().expect("entry path").into_owned(), entry.header().entry_type(), - entry.link_name().expect("entry link name").map(|path| path.into_owned()), + entry + .link_name() + .expect("entry link name") + .map(|path| path.into_owned()), ) }) .collect::<Vec<_>>(); @@ -102,7 +118,10 @@ fn preserves_hardlinks_in_the_archive() { fn rejects_a_missing_rootfs() { let fixture = tempdir().expect("fixture directory"); let error = NativeTarWriter::new() - .write(fixture.path().join("missing"), fixture.path().join("image.tar")) + .write( + fixture.path().join("missing"), + fixture.path().join("image.tar"), + ) .expect_err("missing rootfs must fail"); assert!(error.to_string().contains("rootfs is not a directory")); } @@ -119,7 +138,11 @@ fn rejects_an_archive_output_inside_the_rootfs() { .write(&rootfs, &artifact) .expect_err("archive output inside rootfs must be rejected"); - assert!(error.to_string().contains("archive output must not be inside rootfs")); + assert!( + error + .to_string() + .contains("archive output must not be inside rootfs") + ); assert!(!artifact.exists()); assert!(!rootfs.join("controller.tar.partial").exists()); } @@ -138,7 +161,10 @@ fn refuses_to_replace_an_existing_archive() { .expect_err("an existing archive must not be replaced"); assert!(error.to_string().contains("archive output already exists")); - assert_eq!(fs::read_to_string(&artifact).expect("read existing artifact"), "existing artifact"); + assert_eq!( + fs::read_to_string(&artifact).expect("read existing artifact"), + "existing artifact" + ); assert!(!fixture.path().join("controller.tar.partial").exists()); } @@ -155,5 +181,8 @@ fn removes_partial_output_when_an_unsupported_entry_stops_packaging() { .expect_err("socket entries must be rejected"); assert!(error.to_string().contains("unsupported rootfs entry type")); - assert!(!artifact.exists(), "failed packaging must not publish a partial archive"); + assert!( + !artifact.exists(), + "failed packaging must not publish a partial archive" + ); } diff --git a/tests/build.rs b/tests/build.rs index a0a75b8..7d67b29 100644 --- a/tests/build.rs +++ b/tests/build.rs @@ -119,7 +119,12 @@ fn refuses_to_reuse_an_existing_workspace() { fs::create_dir(&workspace).expect("create existing workspace"); let error = executor - .execute(&plan, &workspace, "profiles/apt.conf", fixture.path().join("image.tar")) + .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")); @@ -143,7 +148,10 @@ fn rejects_an_existing_artifact_before_creating_the_workspace_or_installing() { .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_eq!( + fs::read_to_string(&artifact).expect("read artifact"), + "do not replace" + ); assert!(!workspace.exists()); assert!(installer.requests.borrow().is_empty()); } @@ -166,7 +174,10 @@ fn rejects_an_existing_companion_manifest_before_creating_the_workspace_or_insta .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_eq!( + fs::read_to_string(&companion).expect("read companion manifest"), + "do not replace" + ); assert!(!workspace.exists()); assert!(installer.requests.borrow().is_empty()); } @@ -183,10 +194,19 @@ fn rejects_a_missing_apt_configuration_before_creating_the_workspace() { let missing_apt_config = fixture.path().join("missing-apt.conf"); let error = executor - .execute(&plan, &workspace, &missing_apt_config, fixture.path().join("image.tar")) + .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!( + error + .to_string() + .contains("APT configuration does not exist") + ); assert!(!workspace.exists()); assert!(installer.requests.borrow().is_empty()); } @@ -205,8 +225,15 @@ fn removes_the_new_workspace_when_package_installation_fails() { .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!( + error + .to_string() + .contains("simulated package installation failure") + ); + assert!( + !workspace.exists(), + "failed build must not leave a workspace" + ); assert!(!artifact.exists()); } @@ -224,8 +251,15 @@ fn removes_the_new_workspace_when_the_installer_does_not_create_a_rootfs() { .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!( + error + .to_string() + .contains("package installer did not create rootfs") + ); + assert!( + !workspace.exists(), + "failed build must not leave a workspace" + ); assert!(!artifact.exists()); } @@ -235,7 +269,8 @@ fn removes_the_new_workspace_when_rootfs_finalization_fails() { 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(RootfsFinalizationFailingInstaller, FixtureInitramfsBuilder); + let mut executor = + BuildExecutor::new(RootfsFinalizationFailingInstaller, FixtureInitramfsBuilder); let workspace = fixture.path().join("work"); let artifact = fixture.path().join("image.tar"); @@ -243,7 +278,10 @@ fn removes_the_new_workspace_when_rootfs_finalization_fails() { .execute(&plan, &workspace, "profiles/apt.conf", &artifact) .expect_err("failed rootfs finalization must fail the build"); - assert!(!workspace.exists(), "failed build must not leave a workspace"); + assert!( + !workspace.exists(), + "failed build must not leave a workspace" + ); assert!(!artifact.exists()); } @@ -261,7 +299,14 @@ fn removes_the_new_workspace_when_initramfs_build_fails() { .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!( + error + .to_string() + .contains("simulated initramfs build failure") + ); + assert!( + !workspace.exists(), + "failed build must not leave a workspace" + ); assert!(!artifact.exists()); } diff --git a/tests/cli.rs b/tests/cli.rs index 648962b..dfaf140 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -2,7 +2,7 @@ use std::fs; use std::path::PathBuf; use alt_controller_image::archive::NativeTarWriter; -use alt_controller_image::cli::{run, Cli, Command}; +use alt_controller_image::cli::{Cli, Command, run}; use alt_controller_image::manifest::{ArtifactManifest, PackageRecord}; use clap::Parser; use tempfile::tempdir; @@ -46,8 +46,13 @@ fn parses_build_with_explicit_workspace_and_output_paths() { #[test] fn parses_inspect_with_an_explicit_artifact_path() { - let cli = Cli::try_parse_from(["alt-controller-image", "inspect", "--artifact", "out/image.tar"]) - .expect("inspect command parses"); + let cli = Cli::try_parse_from([ + "alt-controller-image", + "inspect", + "--artifact", + "out/image.tar", + ]) + .expect("inspect command parses"); assert!(matches!( cli.command, @@ -81,23 +86,37 @@ fn compare_uses_companion_manifests_for_tar_artifacts() { fs::create_dir_all(&rootfs).expect("create rootfs"); let left = fixture.path().join("left.tar"); let right = fixture.path().join("right.tar"); - NativeTarWriter::new().write(&rootfs, &left).expect("write left archive"); - NativeTarWriter::new().write(&rootfs, &right).expect("write right archive"); + NativeTarWriter::new() + .write(&rootfs, &left) + .expect("write left archive"); + NativeTarWriter::new() + .write(&rootfs, &right) + .expect("write right archive"); ArtifactManifest::new( - vec![PackageRecord::new("controller", "1.0")], vec![], None, vec![], vec![], + vec![PackageRecord::new("controller", "1.0")], + vec![], + None, + vec![], + vec![], ) .expect("left manifest") .write_beside(&left) .expect("write left companion manifest"); ArtifactManifest::new( - vec![PackageRecord::new("controller", "2.0")], vec![], None, vec![], vec![], + vec![PackageRecord::new("controller", "2.0")], + vec![], + None, + vec![], + vec![], ) .expect("right manifest") .write_beside(&right) .expect("write right companion manifest"); - let error = run(Cli { command: Command::Compare { left, right } }) - .expect_err("companion package difference must be reported"); + let error = run(Cli { + command: Command::Compare { left, right }, + }) + .expect_err("companion package difference must be reported"); assert!(error.to_string().contains("artifacts differ semantically")); } diff --git a/tests/compare.rs b/tests/compare.rs index ab63f9a..8ea9fdb 100644 --- a/tests/compare.rs +++ b/tests/compare.rs @@ -1,5 +1,5 @@ -use alt_controller_image::compare::{Change, SemanticDifference, compare}; use alt_controller_image::archive::NativeTarWriter; +use alt_controller_image::compare::{Change, SemanticDifference, compare}; use alt_controller_image::manifest::{ ArchiveMemberRecord, ArtifactManifest, FileRecord, InitrdRecord, PackageRecord, ServiceRecord, }; @@ -27,7 +27,10 @@ fn compares_added_removed_and_changed_semantic_records() { PackageRecord::new("kernel-image-rt", "2.0"), PackageRecord::new("controller-agent", "1.0"), ], - vec![FileRecord::symlink("etc/controller.conf", "controller.conf.real")], + vec![FileRecord::symlink( + "etc/controller.conf", + "controller.conf.real", + )], Some(InitrdRecord::new("boot/initrd-rt.img", "new-initrd")), vec![ServiceRecord::new("controller.service", false)], vec![ArchiveMemberRecord::new("usr/bin/controller", "file")], @@ -39,13 +42,33 @@ fn compares_added_removed_and_changed_semantic_records() { assert_eq!( report.differences(), &[ - SemanticDifference::Package { name: "controller-agent".into(), change: Change::Added }, - SemanticDifference::Package { name: "kernel-image-rt".into(), change: Change::Changed }, - SemanticDifference::File { path: "etc/controller.conf".into(), change: Change::Changed }, - SemanticDifference::Initrd { change: Change::Changed }, - SemanticDifference::Service { name: "controller.service".into(), change: Change::Changed }, - SemanticDifference::ArchiveMember { path: "etc/controller.conf".into(), change: Change::Removed }, - SemanticDifference::ArchiveMember { path: "usr/bin/controller".into(), change: Change::Added }, + SemanticDifference::Package { + name: "controller-agent".into(), + change: Change::Added + }, + SemanticDifference::Package { + name: "kernel-image-rt".into(), + change: Change::Changed + }, + SemanticDifference::File { + path: "etc/controller.conf".into(), + change: Change::Changed + }, + SemanticDifference::Initrd { + change: Change::Changed + }, + SemanticDifference::Service { + name: "controller.service".into(), + change: Change::Changed + }, + SemanticDifference::ArchiveMember { + path: "etc/controller.conf".into(), + change: Change::Removed + }, + SemanticDifference::ArchiveMember { + path: "usr/bin/controller".into(), + change: Change::Added + }, ] ); assert!(report.render().contains("changed initrd")); @@ -98,10 +121,12 @@ fn archive_collection_records_an_initrd_once_as_a_dedicated_boot_fact() { "8f7ed204b9dfaa20aa484445f54233c4b407cb80ec0f8c07f1f0a59675fb44cf", )) ); - assert!(manifest - .files - .iter() - .all(|record| record.path != "boot/initrd-6.12-rt1.img")); + assert!( + manifest + .files + .iter() + .all(|record| record.path != "boot/initrd-6.12-rt1.img") + ); } #[test] @@ -123,10 +148,12 @@ fn rootfs_collection_records_a_supplied_initrd_once_as_a_dedicated_boot_fact() { .expect("collect rootfs facts"); assert_eq!(manifest.initrd, Some(initrd)); - assert!(manifest - .files - .iter() - .all(|record| record.path != "boot/initrd-6.12-rt1.img")); + assert!( + manifest + .files + .iter() + .all(|record| record.path != "boot/initrd-6.12-rt1.img") + ); } #[test] @@ -136,8 +163,14 @@ fn reads_and_writes_a_toml_manifest_beside_an_artifact() { let manifest = baseline(); let manifest_path = manifest.write_beside(&artifact).expect("write manifest"); - assert_eq!(manifest_path, fixture.path().join("controller.tar.manifest.toml")); - assert_eq!(ArtifactManifest::load(&manifest_path).expect("load manifest"), manifest); + assert_eq!( + manifest_path, + fixture.path().join("controller.tar.manifest.toml") + ); + assert_eq!( + ArtifactManifest::load(&manifest_path).expect("load manifest"), + manifest + ); } #[test] @@ -146,16 +179,26 @@ fn writes_distinct_manifests_for_artifacts_in_the_same_directory() { let legacy = fixture.path().join("legacy.tar"); let native = fixture.path().join("native.tar"); let legacy_manifest = baseline(); - let native_manifest = ArtifactManifest::new(vec![], vec![], None, vec![], vec![]) - .expect("valid native manifest"); + let native_manifest = + ArtifactManifest::new(vec![], vec![], None, vec![], vec![]).expect("valid native manifest"); - let legacy_path = legacy_manifest.write_beside(&legacy).expect("write legacy manifest"); - let native_path = native_manifest.write_beside(&native).expect("write native manifest"); + let legacy_path = legacy_manifest + .write_beside(&legacy) + .expect("write legacy manifest"); + let native_path = native_manifest + .write_beside(&native) + .expect("write native manifest"); assert_eq!(legacy_path, fixture.path().join("legacy.tar.manifest.toml")); assert_eq!(native_path, fixture.path().join("native.tar.manifest.toml")); - assert_eq!(ArtifactManifest::load(&legacy_path).expect("load legacy manifest"), legacy_manifest); - assert_eq!(ArtifactManifest::load(&native_path).expect("load native manifest"), native_manifest); + assert_eq!( + ArtifactManifest::load(&legacy_path).expect("load legacy manifest"), + legacy_manifest + ); + assert_eq!( + ArtifactManifest::load(&native_path).expect("load native manifest"), + native_manifest + ); } #[test] @@ -170,13 +213,19 @@ fn refuses_to_overwrite_an_existing_companion_manifest() { .expect_err("existing companion manifests must not be overwritten"); assert!(error.to_string().contains("create artifact manifest")); - assert_eq!(fs::read_to_string(&manifest_path).expect("read existing manifest"), "preserve this manifest"); + assert_eq!( + fs::read_to_string(&manifest_path).expect("read existing manifest"), + "preserve this manifest" + ); } #[test] fn rejects_duplicate_semantic_keys() { let error = ArtifactManifest::new( - vec![PackageRecord::new("controller", "1"), PackageRecord::new("controller", "2")], + vec![ + PackageRecord::new("controller", "1"), + PackageRecord::new("controller", "2"), + ], vec![], None, vec![], @@ -202,7 +251,11 @@ fn rejects_malformed_file_records_before_comparison() { vec![], ) .expect_err("regular files require a digest"); - assert!(error.to_string().contains("regular file record requires a digest")); + assert!( + error + .to_string() + .contains("regular file record requires a digest") + ); let error = ArtifactManifest::new( vec![], @@ -217,7 +270,11 @@ fn rejects_malformed_file_records_before_comparison() { vec![], ) .expect_err("symlinks cannot carry a digest"); - assert!(error.to_string().contains("symlink record cannot carry a digest")); + assert!( + error + .to_string() + .contains("symlink record cannot carry a digest") + ); } #[test] @@ -231,7 +288,11 @@ fn rejects_malformed_initrd_records_before_comparison() { ) .expect_err("initrd records require a digest"); - assert!(error.to_string().contains("initrd record requires a sha256 digest")); + assert!( + error + .to_string() + .contains("initrd record requires a sha256 digest") + ); } #[test] @@ -283,24 +344,37 @@ fn collects_semantic_facts_from_native_rootfs_and_tar_artifact() { ) .expect("collect semantic facts"); - assert_eq!(manifest.packages, vec![PackageRecord::new("controller", "1.0")]); - assert!(manifest.files.iter().any(|record| record == &FileRecord::file( - "etc/controller.conf", - "2d5c759b2b539229e09d362e8dbe0ae410ff8c9ece6038624458724520683f5b", - ))); - assert!(manifest - .files - .iter() - .any(|record| record == &FileRecord::symlink("etc/controller-link", "controller.conf"))); - assert_eq!(manifest.services, vec![ServiceRecord::new("controller.service", true)]); - assert!(manifest - .archive_members - .iter() - .any(|record| record == &ArchiveMemberRecord::new("etc/controller.conf", "file"))); - assert!(manifest - .archive_members - .iter() - .any(|record| record == &ArchiveMemberRecord::new("etc/controller-link", "symlink"))); + assert_eq!( + manifest.packages, + vec![PackageRecord::new("controller", "1.0")] + ); + assert!(manifest.files.iter().any(|record| record + == &FileRecord::file( + "etc/controller.conf", + "2d5c759b2b539229e09d362e8dbe0ae410ff8c9ece6038624458724520683f5b", + ))); + assert!( + manifest + .files + .iter() + .any(|record| record == &FileRecord::symlink("etc/controller-link", "controller.conf")) + ); + assert_eq!( + manifest.services, + vec![ServiceRecord::new("controller.service", true)] + ); + assert!( + manifest + .archive_members + .iter() + .any(|record| record == &ArchiveMemberRecord::new("etc/controller.conf", "file")) + ); + assert!( + manifest + .archive_members + .iter() + .any(|record| record == &ArchiveMemberRecord::new("etc/controller-link", "symlink")) + ); } #[test] @@ -325,10 +399,11 @@ fn collects_comparable_semantic_facts_directly_from_a_tar_artifact() { let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect archive facts"); assert_eq!(manifest.packages, Vec::<PackageRecord>::new()); - assert!(manifest.files.iter().any(|record| record == &FileRecord::file( - "etc/controller.conf", - "2d5c759b2b539229e09d362e8dbe0ae410ff8c9ece6038624458724520683f5b", - ))); + assert!(manifest.files.iter().any(|record| record + == &FileRecord::file( + "etc/controller.conf", + "2d5c759b2b539229e09d362e8dbe0ae410ff8c9ece6038624458724520683f5b", + ))); assert_eq!( manifest.initrd, Some(InitrdRecord::new( @@ -336,7 +411,10 @@ fn collects_comparable_semantic_facts_directly_from_a_tar_artifact() { "8f7ed204b9dfaa20aa484445f54233c4b407cb80ec0f8c07f1f0a59675fb44cf", )) ); - assert_eq!(manifest.services, vec![ServiceRecord::new("controller.service", true)]); + assert_eq!( + manifest.services, + vec![ServiceRecord::new("controller.service", true)] + ); } #[test] @@ -350,11 +428,16 @@ fn normalizes_legacy_dot_prefixed_archive_paths_before_comparison() { header.set_mode(0o644); header.set_cksum(); archive - .append_data(&mut header, "./etc/controller.conf", Cursor::new(b"controller\n")) + .append_data( + &mut header, + "./etc/controller.conf", + Cursor::new(b"controller\n"), + ) .expect("append legacy-style member"); archive.finish().expect("finish legacy archive"); - let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect legacy archive facts"); + let manifest = + ArtifactManifest::collect_archive(&artifact).expect("collect legacy archive facts"); let expected = ArtifactManifest::new( vec![], vec![FileRecord::file( @@ -391,7 +474,11 @@ fn rejects_duplicate_archive_member_paths_before_semantic_comparison() { let error = ArtifactManifest::collect_archive(&artifact) .expect_err("ambiguous archive paths cannot be compared semantically"); - assert!(error.to_string().contains("duplicate archive member record: etc/controller.conf")); + assert!( + error + .to_string() + .contains("duplicate archive member record: etc/controller.conf") + ); } #[test] @@ -405,7 +492,11 @@ fn collects_hardlink_targets_from_an_archive_for_semantic_comparison() { header.set_mode(0o644); header.set_cksum(); archive - .append_data(&mut header, "./usr/bin/controller", Cursor::new(b"controller\n")) + .append_data( + &mut header, + "./usr/bin/controller", + Cursor::new(b"controller\n"), + ) .expect("append regular member"); let mut link_header = tar::Header::new_gnu(); link_header.set_entry_type(tar::EntryType::Link); @@ -447,9 +538,10 @@ fn rootfs_collection_preserves_hardlink_facts_like_archive_collection() { .write(&rootfs, &artifact) .expect("write native archive"); - let rootfs_manifest = ArtifactManifest::collect(&rootfs, vec![], None, &artifact) - .expect("collect rootfs facts"); - let archive_manifest = ArtifactManifest::collect_archive(&artifact).expect("collect archive facts"); + let rootfs_manifest = + ArtifactManifest::collect(&rootfs, vec![], None, &artifact).expect("collect rootfs facts"); + let archive_manifest = + ArtifactManifest::collect_archive(&artifact).expect("collect archive facts"); assert_eq!(rootfs_manifest.files, archive_manifest.files); assert!(rootfs_manifest.files.iter().any(|record| { @@ -464,17 +556,28 @@ fn normalizes_dot_prefixed_paths_from_a_legacy_tree_archive() { fs::create_dir_all(rootfs.join("etc")).expect("create rootfs directory"); fs::write(rootfs.join("etc/controller.conf"), "controller\n").expect("write rootfs file"); let artifact = fixture.path().join("legacy.tar"); - let mut archive = tar::Builder::new(fs::File::create(&artifact).expect("create legacy archive")); - archive.append_dir_all(".", &rootfs).expect("write legacy tree archive"); + let mut archive = + tar::Builder::new(fs::File::create(&artifact).expect("create legacy archive")); + archive + .append_dir_all(".", &rootfs) + .expect("write legacy tree archive"); archive.finish().expect("finish legacy archive"); - let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect legacy archive facts"); + let manifest = + ArtifactManifest::collect_archive(&artifact).expect("collect legacy archive facts"); - assert!(manifest.files.iter().all(|record| !record.path.starts_with("./"))); - assert!(manifest - .archive_members - .iter() - .all(|record| !record.path.starts_with("./"))); + assert!( + manifest + .files + .iter() + .all(|record| !record.path.starts_with("./")) + ); + assert!( + manifest + .archive_members + .iter() + .all(|record| !record.path.starts_with("./")) + ); } #[test] @@ -489,20 +592,26 @@ fn excludes_legacy_internal_host_and_fakedata_archive_members() { fs::write(rootfs.join("etc/controller.conf"), "controller\n").expect("write visible file"); let legacy = fixture.path().join("legacy.tar"); let native = fixture.path().join("native.tar"); - let mut legacy_writer = tar::Builder::new(fs::File::create(&legacy).expect("create legacy archive")); + let mut legacy_writer = + tar::Builder::new(fs::File::create(&legacy).expect("create legacy archive")); legacy_writer .append_dir_all(".", &rootfs) .expect("write legacy archive with internal metadata"); legacy_writer.finish().expect("finish legacy archive"); - NativeTarWriter::new().write(&rootfs, &native).expect("write native archive"); + NativeTarWriter::new() + .write(&rootfs, &native) + .expect("write native archive"); let legacy_manifest = ArtifactManifest::collect_archive(&legacy).expect("collect legacy facts"); let native_manifest = ArtifactManifest::collect_archive(&native).expect("collect native facts"); - assert!(legacy_manifest - .archive_members - .iter() - .all(|record| !record.path.starts_with(".host/") && !record.path.starts_with(".fakedata/"))); + assert!( + legacy_manifest + .archive_members + .iter() + .all(|record| !record.path.starts_with(".host/") + && !record.path.starts_with(".fakedata/")) + ); let report = compare(&legacy_manifest, &native_manifest); assert!(report.is_equivalent(), "{}", report.render()); } @@ -519,16 +628,21 @@ fn native_manifest_collection_excludes_internal_members_from_its_archive_facts() fs::write(rootfs.join("etc/controller.conf"), "controller\n").expect("write visible file"); let artifact = fixture.path().join("legacy.tar"); let mut writer = tar::Builder::new(fs::File::create(&artifact).expect("create legacy archive")); - writer.append_dir_all(".", &rootfs).expect("write legacy archive"); + writer + .append_dir_all(".", &rootfs) + .expect("write legacy archive"); writer.finish().expect("finish legacy archive"); let manifest = ArtifactManifest::collect(&rootfs, vec![], None, &artifact) .expect("collect native filesystem and archive facts"); - assert!(manifest - .archive_members - .iter() - .all(|record| !record.path.starts_with(".host/") && !record.path.starts_with(".fakedata/"))); + assert!( + manifest + .archive_members + .iter() + .all(|record| !record.path.starts_with(".host/") + && !record.path.starts_with(".fakedata/")) + ); } #[test] @@ -541,19 +655,25 @@ fn archive_collection_excludes_nested_internal_metadata_members() { fs::write(rootfs.join("etc/controller.conf"), "controller\n").expect("write visible file"); let artifact = fixture.path().join("legacy.tar"); let mut writer = tar::Builder::new(fs::File::create(&artifact).expect("create legacy archive")); - writer.append_dir_all(".", &rootfs).expect("write legacy archive"); + writer + .append_dir_all(".", &rootfs) + .expect("write legacy archive"); writer.finish().expect("finish legacy archive"); let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect archive facts"); - assert!(manifest - .archive_members - .iter() - .all(|record| !record.path.split('/').any(|component| component == ".fakedata"))); - assert!(manifest - .files - .iter() - .all(|record| !record.path.split('/').any(|component| component == ".fakedata"))); + assert!(manifest.archive_members.iter().all(|record| { + !record + .path + .split('/') + .any(|component| component == ".fakedata") + })); + assert!(manifest.files.iter().all(|record| { + !record + .path + .split('/') + .any(|component| component == ".fakedata") + })); } #[test] @@ -575,7 +695,10 @@ fn rootfs_collection_recognizes_services_enabled_by_non_default_targets() { let manifest = ArtifactManifest::collect(&rootfs, vec![], None, &artifact) .expect("collect native filesystem and archive facts"); - assert_eq!(manifest.services, vec![ServiceRecord::new("controller.service", true)]); + assert_eq!( + manifest.services, + vec![ServiceRecord::new("controller.service", true)] + ); } #[test] @@ -598,7 +721,10 @@ fn archive_collection_coalesces_a_service_enabled_by_multiple_targets() { let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect archive facts"); - assert_eq!(manifest.services, vec![ServiceRecord::new("controller.service", true)]); + assert_eq!( + manifest.services, + vec![ServiceRecord::new("controller.service", true)] + ); } #[test] diff --git a/tests/initramfs.rs b/tests/initramfs.rs index 429411a..5ad0b99 100644 --- a/tests/initramfs.rs +++ b/tests/initramfs.rs @@ -12,10 +12,8 @@ use tempfile::tempdir; 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"); + 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"); @@ -59,8 +57,11 @@ 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"); + 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()); @@ -104,10 +105,19 @@ fn initramfs_adapter_refuses_to_replace_a_non_symlink_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"); + 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"); + 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)] diff --git a/tests/model_validation.rs b/tests/model_validation.rs index f94a656..92a6640 100644 --- a/tests/model_validation.rs +++ b/tests/model_validation.rs @@ -56,7 +56,10 @@ fn controller_spec_preserves_the_vendored_controller_package_set() { "rsyslog", ] { assert!( - spec.packages.base.iter().any(|candidate| candidate == package), + spec.packages + .base + .iter() + .any(|candidate| candidate == package), "controller package set must contain {package}" ); } diff --git a/tests/package_installer.rs b/tests/package_installer.rs index e527f14..76d8683 100644 --- a/tests/package_installer.rs +++ b/tests/package_installer.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; use alt_controller_image::hasher::{CommandRunner, HasherInstaller, Invocation}; -use alt_controller_image::{BuildPlan, ImageSpec}; use alt_controller_image::package_installer::{AptConfig, PackageInstaller, PackageRequest}; +use alt_controller_image::{BuildPlan, ImageSpec}; #[derive(Default)] struct RecordingRunner { @@ -63,13 +63,15 @@ fn hasher_installer_passes_typed_request_to_initroot_and_install_commands() { #[test] fn package_request_rejects_empty_packages_and_unsafe_apt_config_paths() { assert!(AptConfig::new("").is_err()); - assert!(PackageRequest::new( - PathBuf::from("work"), - AptConfig::new("profiles/apt.conf").expect("valid apt config"), - ["basesystem", ""], - std::iter::empty::<&str>(), - ) - .is_err()); + assert!( + PackageRequest::new( + PathBuf::from("work"), + AptConfig::new("profiles/apt.conf").expect("valid apt config"), + ["basesystem", ""], + std::iter::empty::<&str>(), + ) + .is_err() + ); } #[test] @@ -82,11 +84,21 @@ fn build_plan_compiles_its_typed_package_request() { .package_request("work/alt-controller", "profiles/apt.conf") .expect("compile package request"); - assert_eq!(request.workdir(), std::path::Path::new("work/alt-controller")); - assert_eq!(request.apt_config().as_path(), std::path::Path::new("profiles/apt.conf")); + assert_eq!( + request.workdir(), + std::path::Path::new("work/alt-controller") + ); + assert_eq!( + request.apt_config().as_path(), + std::path::Path::new("profiles/apt.conf") + ); assert_eq!(request.packages()[0], "anacron"); assert!(request.packages().contains(&"apt".to_owned())); assert!(request.packages().contains(&"libiec61850".to_owned())); - assert!(request.packages().contains(&"make-initrd-multipath".to_owned())); + assert!( + request + .packages() + .contains(&"make-initrd-multipath".to_owned()) + ); assert_eq!(request.selectors(), ["^kernel-(image|modules-())-(rt)$"]); } diff --git a/tests/rootfs_finalization.rs b/tests/rootfs_finalization.rs index a450b7e..9e07436 100644 --- a/tests/rootfs_finalization.rs +++ b/tests/rootfs_finalization.rs @@ -10,7 +10,8 @@ fn finalization_copies_trees_generates_oem_recipe_and_enables_services() { let fixture = tempdir().expect("fixture directory"); let source = fixture.path().join("overlay"); fs::create_dir_all(source.join("nested")).expect("create source tree"); - fs::write(source.join("nested/controller.conf"), "mode = controller\n").expect("write source file"); + fs::write(source.join("nested/controller.conf"), "mode = controller\n") + .expect("write source file"); symlink("nested/controller.conf", source.join("controller.conf")) .expect("create relative source symlink"); @@ -29,7 +30,8 @@ fn finalization_copies_trees_generates_oem_recipe_and_enables_services() { "mode = controller\n" ); assert_eq!( - fs::read_link(rootfs.path().join("etc/controller/controller.conf")).expect("copied symlink"), + fs::read_link(rootfs.path().join("etc/controller/controller.conf")) + .expect("copied symlink"), Path::new("nested/controller.conf") ); assert_eq!( @@ -94,11 +96,16 @@ fn finalization_is_idempotent_for_matching_symlinks() { vec![ServiceName::new("chronyd.service").expect("valid service")], ); - finalization.apply(rootfs.path()).expect("first finalization succeeds"); - let second = finalization.apply(rootfs.path()).expect("repeat finalization succeeds"); + finalization + .apply(rootfs.path()) + .expect("first finalization succeeds"); + let second = finalization + .apply(rootfs.path()) + .expect("repeat finalization succeeds"); assert_eq!( - fs::read_link(rootfs.path().join("etc/controller/controller-link")).expect("copied symlink"), + fs::read_link(rootfs.path().join("etc/controller/controller-link")) + .expect("copied symlink"), Path::new("controller.conf") ); assert_eq!( @@ -110,10 +117,12 @@ fn finalization_is_idempotent_for_matching_symlinks() { .expect("enabled service"), Path::new("/usr/lib/systemd/system/chronyd.service") ); - assert!(second - .created_paths() - .iter() - .any(|path| path == "etc/controller/controller-link")); + assert!( + second + .created_paths() + .iter() + .any(|path| path == "etc/controller/controller-link") + ); } #[test] @@ -121,29 +130,43 @@ fn finalization_replaces_a_destination_symlink_instead_of_following_it() { let fixture = tempdir().expect("fixture directory"); let source = fixture.path().join("overlay"); fs::create_dir_all(&source).expect("create source tree"); - fs::write(source.join("controller.conf"), "new controller configuration\n") - .expect("write source file"); + fs::write( + source.join("controller.conf"), + "new controller configuration\n", + ) + .expect("write source file"); let rootfs = tempdir().expect("rootfs directory"); let outside = fixture.path().join("outside.conf"); fs::write(&outside, "must not change\n").expect("write outside file"); fs::create_dir_all(rootfs.path().join("etc/controller")).expect("create destination directory"); - symlink(&outside, rootfs.path().join("etc/controller/controller.conf")) - .expect("create destination symlink"); + symlink( + &outside, + rootfs.path().join("etc/controller/controller.conf"), + ) + .expect("create destination symlink"); let finalization = RootfsFinalization::new( vec![CopyTree::new(&source, "etc/controller").expect("valid copy destination")], InitrdOem::new(std::iter::empty::<&str>(), std::iter::empty::<&str>()), vec![], ); - finalization.apply(rootfs.path()).expect("finalization replaces symlink safely"); + finalization + .apply(rootfs.path()) + .expect("finalization replaces symlink safely"); - assert_eq!(fs::read_to_string(&outside).expect("read outside file"), "must not change\n"); - assert!(fs::symlink_metadata(rootfs.path().join("etc/controller/controller.conf")) - .expect("inspect copied file") - .file_type() - .is_file()); assert_eq!( - fs::read_to_string(rootfs.path().join("etc/controller/controller.conf")).expect("read copied file"), + fs::read_to_string(&outside).expect("read outside file"), + "must not change\n" + ); + assert!( + fs::symlink_metadata(rootfs.path().join("etc/controller/controller.conf")) + .expect("inspect copied file") + .file_type() + .is_file() + ); + assert_eq!( + fs::read_to_string(rootfs.path().join("etc/controller/controller.conf")) + .expect("read copied file"), "new controller configuration\n" ); } diff --git a/tests/stage_graph.rs b/tests/stage_graph.rs index cd6fc93..13a297d 100644 --- a/tests/stage_graph.rs +++ b/tests/stage_graph.rs @@ -34,7 +34,10 @@ fn stage_graph_orders_artifact_producers_before_consumers() { Stage::Manifest, ] ); - assert_eq!(plan.dependencies(Stage::Package), &[Stage::FinalizeRootfs, Stage::BuildInitramfs]); + assert_eq!( + plan.dependencies(Stage::Package), + &[Stage::FinalizeRootfs, Stage::BuildInitramfs] + ); assert_eq!(plan.dependencies(Stage::BuildInitramfs), &[Stage::Install]); assert_eq!(plan.dependencies(Stage::Manifest), &[Stage::Package]); } |