use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
use testing_conventions::e2e::{attest, verify, Verification};
use testing_conventions::run;
struct TempRepo(PathBuf);
impl TempRepo {
fn new() -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let root = std::env::temp_dir().join(format!(
"tc-e2e-verify-{}-{}",
std::process::id(),
COUNTER.fetch_add(1, Ordering::Relaxed),
));
std::fs::create_dir_all(&root).unwrap();
git(&root, &["init", "-q"]);
git(&root, &["config", "user.email", "test@example.com"]);
git(&root, &["config", "user.name", "Test"]);
git(&root, &["config", "commit.gpgsign", "false"]);
std::fs::write(root.join("README.md"), "seed\n").unwrap();
git(&root, &["add", "."]);
git(
&root,
&["-c", "commit.gpgsign=false", "commit", "-q", "-m", "seed"],
);
TempRepo(root)
}
fn commit_code(&self, name: &str, contents: &str) {
std::fs::write(self.0.join(name), contents).unwrap();
git(&self.0, &["add", name]);
git(
&self.0,
&["-c", "commit.gpgsign=false", "commit", "-q", "-m", "code"],
);
}
}
impl Drop for TempRepo {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn git(dir: &Path, args: &[&str]) {
let status = Command::new("git")
.args(args)
.current_dir(dir)
.status()
.expect("git should run");
assert!(status.success(), "git {args:?} failed");
}
fn rev_parse(dir: &Path, rev: &str) -> String {
let out = Command::new("git")
.args(["rev-parse", rev])
.current_dir(dir)
.output()
.expect("git rev-parse should run");
assert!(out.status.success(), "git rev-parse {rev} failed");
String::from_utf8(out.stdout).unwrap().trim().to_string()
}
#[test]
fn verify_passes_when_the_attestation_names_the_latest_code_commit() {
let repo = TempRepo::new();
attest(&repo.0, "true").expect("attest should succeed");
assert_eq!(
verify(&repo.0).expect("verify should succeed"),
Verification::Fresh,
);
}
#[test]
fn verify_fails_when_no_attestation_is_present() {
let repo = TempRepo::new();
assert_eq!(
verify(&repo.0).expect("verify should succeed"),
Verification::Missing,
);
}
#[test]
fn verify_fails_when_code_changed_since_the_attestation() {
let repo = TempRepo::new();
attest(&repo.0, "true").expect("attest should succeed");
let attested = rev_parse(&repo.0, "HEAD^");
repo.commit_code("widget.rs", "pub fn widget() {}\n");
let latest = rev_parse(&repo.0, "HEAD");
assert_eq!(
verify(&repo.0).expect("verify should succeed"),
Verification::Stale { attested, latest },
);
}
#[test]
fn verify_scopes_fresh_to_a_package_subdirectory() {
let repo = TempRepo::new();
let package = repo.0.join("packages/widget");
std::fs::create_dir_all(&package).unwrap();
repo.commit_code("packages/widget/widget.rs", "pub fn widget() {}\n");
attest(&package, "true").expect("attest should succeed");
assert_eq!(
verify(&package).expect("verify should succeed"),
Verification::Fresh,
);
assert_eq!(
verify(&repo.0).expect("verify should succeed"),
Verification::Missing,
);
}
#[test]
fn verify_scopes_stale_to_a_package_subdirectory() {
let repo = TempRepo::new();
let package = repo.0.join("packages/widget");
std::fs::create_dir_all(&package).unwrap();
repo.commit_code("packages/widget/widget.rs", "pub fn widget() {}\n");
attest(&package, "true").expect("attest should succeed");
let attested = rev_parse(&repo.0, "HEAD^");
repo.commit_code("packages/widget/widget2.rs", "pub fn widget2() {}\n");
let latest = rev_parse(&repo.0, "HEAD");
assert_eq!(
verify(&package).expect("verify should succeed"),
Verification::Stale { attested, latest },
);
}
#[test]
fn verify_scopes_missing_to_a_package_subdirectory() {
let repo = TempRepo::new();
let package = repo.0.join("packages/widget");
std::fs::create_dir_all(&package).unwrap();
assert_eq!(
verify(&package).expect("verify should succeed"),
Verification::Missing,
);
}
fn e2e_verify_run(path: &Path) -> anyhow::Result<i32> {
let argv: Vec<OsString> = vec![
"testing-conventions".into(),
"e2e".into(),
"verify".into(),
path.as_os_str().to_owned(),
];
run(argv)
}
#[test]
fn cli_verify_with_path_argument_passes_when_fresh() {
let repo = TempRepo::new();
let package = repo.0.join("packages/widget");
std::fs::create_dir_all(&package).unwrap();
repo.commit_code("packages/widget/widget.rs", "pub fn widget() {}\n");
attest(&package, "true").expect("attest should succeed");
assert_eq!(
e2e_verify_run(&package).expect("dispatch should succeed"),
0,
"a fresh attestation at the given path should pass",
);
}
#[test]
fn cli_verify_with_path_argument_fails_when_missing() {
let repo = TempRepo::new();
let package = repo.0.join("packages/widget");
std::fs::create_dir_all(&package).unwrap();
assert_eq!(
e2e_verify_run(&package).expect("dispatch should succeed"),
1,
"no attestation at the given path should fail",
);
}
#[test]
fn cli_verify_with_path_argument_fails_when_stale() {
let repo = TempRepo::new();
let package = repo.0.join("packages/widget");
std::fs::create_dir_all(&package).unwrap();
repo.commit_code("packages/widget/widget.rs", "pub fn widget() {}\n");
attest(&package, "true").expect("attest should succeed");
repo.commit_code("packages/widget/widget2.rs", "pub fn widget2() {}\n");
assert_eq!(
e2e_verify_run(&package).expect("dispatch should succeed"),
1,
"a stale attestation at the given path should fail",
);
}
#[test]
fn cli_verify_with_no_argument_defaults_to_the_current_directory() {
let argv: Vec<OsString> = vec!["testing-conventions".into(), "e2e".into(), "verify".into()];
let code = run(argv).expect("`e2e verify` with no argument should still dispatch");
assert_eq!(code, 1);
}