use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
mod format_transition_support;
use format_transition_support::{
ActiveFixture, MAINTAINER_KEY_ID, MAINTAINER_SEED_HEX, StrictFailure,
build_format2_strict_wal_fixture, build_legacy_fixture,
};
type TestResult<T = ()> = Result<T, Box<dyn std::error::Error>>;
fn prikk(root: &Path) -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_prikk"));
command.current_dir(root);
command
}
fn run(root: &Path, args: &[&str]) -> TestResult<Output> {
Ok(prikk(root).args(args).output()?)
}
fn unique_root() -> TestResult<PathBuf> {
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_nanos();
let root = std::env::temp_dir().join(format!("prikk-format-transition-{nonce}"));
std::fs::create_dir_all(&root)?;
Ok(root)
}
fn assert_legacy_warning(output: &Output) {
assert!(
String::from_utf8_lossy(&output.stderr).contains("format-1 repository opened"),
"missing legacy warning: {}",
String::from_utf8_lossy(&output.stderr)
);
}
fn run_owned(root: &Path, args: &[String]) -> TestResult<Output> {
Ok(prikk(root)
.env("PRIKK_AUTHOR_KEY_ID", "legacy-author")
.env(
"PRIKK_AUTHOR_SEED",
"3636363636363636363636363636363636363636363636363636363636363636",
)
.env("PRIKK_MAINTAINER_KEY_ID", MAINTAINER_KEY_ID)
.env("PRIKK_MAINTAINER_SEED", MAINTAINER_SEED_HEX)
.args(args)
.output()?)
}
fn snapshot_tree(root: &Path) -> TestResult<BTreeMap<PathBuf, Vec<u8>>> {
fn walk(root: &Path, current: &Path, snapshot: &mut BTreeMap<PathBuf, Vec<u8>>) -> TestResult {
let mut entries = std::fs::read_dir(current)?.collect::<Result<Vec<_>, _>>()?;
entries.sort_by_key(std::fs::DirEntry::file_name);
for entry in entries {
let path = entry.path();
let relative = path.strip_prefix(root)?.to_path_buf();
let metadata = std::fs::symlink_metadata(&path)?;
if metadata.is_dir() {
snapshot.insert(relative.clone(), b"directory".to_vec());
walk(root, &path, snapshot)?;
} else if metadata.is_file() {
snapshot.insert(relative, std::fs::read(path)?);
} else {
snapshot.insert(
relative,
std::fs::read_link(path)?
.as_os_str()
.as_encoded_bytes()
.to_vec(),
);
}
}
Ok(())
}
let mut snapshot = BTreeMap::new();
walk(root, root, &mut snapshot)?;
Ok(snapshot)
}
#[test]
fn format1_empty_repository_is_bounded_read_only() -> TestResult {
let root = unique_root()?;
let init = run(&root, &["init"])?;
assert!(init.status.success());
assert_eq!(std::fs::read(root.join(".prikk/FORMAT"))?, b"2\n");
std::fs::write(root.join(".prikk/FORMAT"), b"1\n")?;
let status = run(&root, &["status"])?;
assert!(status.status.success());
assert_legacy_warning(&status);
let verify = run(&root, &["verify"])?;
assert!(!verify.status.success());
assert_legacy_warning(&verify);
assert!(String::from_utf8_lossy(&verify.stderr).contains("not verifiable"));
let doctor = run(&root, &["doctor"])?;
assert!(doctor.status.success());
assert_legacy_warning(&doctor);
assert!(String::from_utf8_lossy(&doctor.stdout).contains("PRIKK-DOCTOR-LEGACY-FORMAT"));
let commit = run(&root, &["commit", "-m", "must refuse"])?;
assert!(!commit.status.success());
assert!(String::from_utf8_lossy(&commit.stderr).contains("unsupported format version: 1"));
let trust = run(
&root,
&[
"trust",
"maintainer",
"add",
"--key-id",
"legacy-refused",
"--public-key",
"0000000000000000000000000000000000000000000000000000000000000000",
],
)?;
assert!(!trust.status.success());
assert!(String::from_utf8_lossy(&trust.stderr).contains("unsupported format version: 1"));
let reinit = run(&root, &["init"])?;
assert!(!reinit.status.success());
assert_eq!(std::fs::read(root.join(".prikk/FORMAT"))?, b"1\n");
assert!(!root.join(".prikk/active/default/queue.wal").exists());
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[path = "format_transition/matrix.rs"]
mod matrix;