#[cfg(feature = "debian")]
use std::path::Path;
mod common;
use ferroday_cage::DirectMapper;
use ferroday_cage::provision::{Provision, ProvisionEvent, ProvisionObserver, Remove};
#[cfg(feature = "tarball")]
use ferroday_cage::IdentityMap;
#[cfg(feature = "tarball")]
use ferroday_cage::provision::Export;
#[cfg(feature = "debian")]
use ferroday_cage::provision::debian::{Debian, Pool, Repository};
#[cfg(feature = "debian")]
const ANCHOR: &[u8] = b"NOT-A-REAL-KEYRING-BUT-DISTINCTIVE-ENOUGH-TO-FIND";
#[cfg(feature = "debian")]
fn anchor_file(tag: &str) -> Anchor {
let dir = common::scratch_dir(&format!("debug-{tag}"));
let file = dir.join("keyring.gpg");
std::fs::write(&file, ANCHOR).expect("the anchor is writable");
Anchor { dir, file }
}
#[cfg(feature = "debian")]
struct Anchor {
dir: std::path::PathBuf,
file: std::path::PathBuf,
}
#[cfg(feature = "debian")]
impl std::ops::Deref for Anchor {
type Target = std::path::Path;
fn deref(&self) -> &std::path::Path {
&self.file
}
}
#[cfg(feature = "debian")]
impl AsRef<std::path::Path> for Anchor {
fn as_ref(&self) -> &std::path::Path {
&self.file
}
}
#[cfg(feature = "debian")]
impl Drop for Anchor {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.dir);
}
}
struct Silent;
impl ProvisionObserver for Silent {
fn progress(&mut self, _: ProvisionEvent<'_>) {}
}
#[cfg(feature = "debian")]
#[test]
fn a_builder_shows_its_settings_and_names_its_delegate() {
let mut sink = |_event: ferroday_cage::provision::debian::DebianEvent<'_>| {};
let builder = Debian::builder("trixie")
.architecture("arm64")
.mirror("https://example.invalid/debian")
.include(["curl"])
.exclude(["systemd"])
.cache_dir("/var/cache/build")
.trust_unsigned(true);
let rendering = format!("{builder:?}");
for expected in [
"mirror: Some(\"https://example.invalid/debian\")",
"trust_unsigned: true",
"fetcher: None",
] {
assert!(
rendering.contains(expected),
"the builder's rendering does not carry {expected:?}: {rendering}",
);
}
let mut debian = builder.build().expect("the builder validates");
let rendering = format!("{debian:?}");
for expected in [
"trixie",
"arm64",
"curl",
"systemd",
"/var/cache/build",
"fetcher: <dyn Fetch>",
] {
assert!(
rendering.contains(expected),
"the rendering does not carry {expected:?}: {rendering}",
);
}
let observed = debian.observe(&mut sink);
let rendering = format!("{observed:?}");
assert!(
rendering.contains("sink: <dyn DebianObserver>"),
"{rendering}"
);
assert!(rendering.contains("trixie"), "{rendering}");
}
#[test]
fn an_absent_delegate_renders_as_absent_and_a_present_one_as_present() {
let mapper = DirectMapper::new();
let bare = Remove::new("/srv/rootfs");
assert!(format!("{bare:?}").contains("mapper: None"), "{bare:?}");
let removal = Remove::new("/srv/rootfs").mapper(&mapper);
assert!(
format!("{removal:?}").contains("mapper: Some(<dyn IdMapper>)"),
"{removal:?}",
);
let mut observer = Silent;
let quiet = format!("{:?}", Provision::new("/srv/rootfs"));
assert!(quiet.contains("observer: None"), "{quiet}");
let watched = Provision::new("/srv/rootfs").observe(&mut observer);
assert!(
format!("{watched:?}").contains("observer: Some(<dyn ProvisionObserver>)"),
"{watched:?}",
);
}
#[test]
fn a_removal_shows_whether_the_publication_lock_goes_with_the_tree() {
let bare = format!("{:?}", Remove::new("/srv/rootfs"));
assert!(bare.contains("remove_lock: true"), "{bare}");
let kept = format!("{:?}", Remove::new("/srv/rootfs").remove_lock(false));
assert!(kept.contains("remove_lock: false"), "{kept}");
}
#[cfg(feature = "tarball")]
#[test]
fn an_export_shows_whether_it_has_a_mapper() {
let without = format!("{:?}", Export::new("/srv/rootfs"));
assert!(without.contains("mapper: None"), "{without}");
let mapper = DirectMapper::new();
let with = format!("{:?}", Export::new("/srv/rootfs").mapper(&mapper));
assert!(with.contains("mapper: Some(<dyn IdMapper>)"), "{with}");
}
#[cfg(feature = "tarball")]
#[test]
fn an_export_carries_the_settings_that_decide_its_output() {
let mapper = DirectMapper::new();
let export = Export::new("/srv/rootfs")
.map(IdentityMap::Subordinate)
.mapper(&mapper)
.clamp_mtime(1_700_000_000);
let rendering = format!("{export:?}");
for expected in [
"/srv/rootfs",
"Subordinate",
"clamp_mtime: Some(1700000000)",
] {
assert!(
rendering.contains(expected),
"{expected:?} missing: {rendering}"
);
}
}
#[cfg(feature = "debian")]
#[test]
fn no_trust_anchor_bytes_reach_a_rendering() {
let path = anchor_file("anchor");
let repository = Repository::builder("trixie")
.mirror("https://example.invalid/debian")
.keyring(&path)
.name("extra")
.build()
.expect("the repository validates");
let rendering = format!("{repository:?}");
assert!(
!rendering.contains("NOT-A-REAL-KEYRING"),
"the anchor bytes reached the rendering: {rendering}",
);
assert!(
rendering.contains(&format!("Signed({} bytes)", ANCHOR.len())),
"the rendering does not name the anchor: {rendering}",
);
let unbuilt = Repository::builder("trixie")
.mirror("https://example.invalid/debian")
.keyring(&path);
let rendering = format!("{unbuilt:?}");
assert!(
!rendering.contains("NOT-A-REAL-KEYRING"),
"the builder read the anchor into its rendering: {rendering}",
);
assert!(
rendering.contains("keyring.gpg"),
"the builder's rendering does not name the anchor's path: {rendering}",
);
let unsigned = Repository::builder("trixie")
.mirror("file:///srv/pool")
.trust_unsigned(true)
.build()
.expect("the repository validates");
assert!(format!("{unsigned:?}").contains("Unsigned"), "{unsigned:?}");
let default = Debian::builder("trixie")
.build()
.expect("the builder validates");
let rendering = format!("{default:?}");
assert!(
rendering.len() < 2000,
"a bootstrap rendered {} characters, which is a dumped keyring: {rendering}",
rendering.len(),
);
assert!(rendering.contains("Signed("), "{rendering}");
}
#[cfg(feature = "debian")]
#[test]
fn a_pool_names_the_layout_it_would_write() {
let pool = Pool::at("/srv/pool")
.suite("trixie")
.component("contrib")
.architecture("riscv64")
.date(1_700_000_000);
let rendering = format!("{pool:?}");
for expected in ["/srv/pool", "trixie", "contrib", "riscv64", "1700000000"] {
assert!(
rendering.contains(expected),
"{expected:?} missing: {rendering}"
);
}
let bare = format!("{:?}", Pool::at(Path::new("/srv/pool")));
assert!(bare.contains("architecture: None"), "{bare}");
assert!(bare.contains("date: None"), "{bare}");
}