use assert_cmd::Command;
use serde_json::{json, Value};
use std::path::{Path, PathBuf};
pub struct CargoFixture {
pub temporary: tempfile::TempDir,
workspace_root: PathBuf,
fake_bin: PathBuf,
call_log: PathBuf,
pub cargo_manifest: PathBuf,
}
impl CargoFixture {
#[allow(clippy::needless_raw_string_hashes, clippy::too_many_lines)]
pub fn new(packages: &[(&str, &str)]) -> Self {
let temporary = tempfile::tempdir().expect("create fixture directory");
let workspace_root = temporary.path().join("workspace");
let fake_bin = temporary.path().join("bin");
std::fs::create_dir_all(workspace_root.join("nested")).unwrap();
std::fs::create_dir_all(&fake_bin).unwrap();
let metadata_packages: Vec<_> = packages
.iter()
.map(|(name, manifest)| {
let manifest_path = workspace_root.join(manifest);
std::fs::create_dir_all(manifest_path.parent().unwrap()).unwrap();
std::fs::write(
&manifest_path,
format!("[package]\nname = \"{name}\"\nversion = \"0.1.0\"\n"),
)
.unwrap();
json!({
"id": format!("path+file://fixture/{name}#0.1.0"),
"name": name,
"manifest_path": manifest_path,
"dependencies": [],
})
})
.collect();
let cargo_manifest = workspace_root.join(packages[0].1);
let metadata = json!({
"workspace_root": workspace_root,
"workspace_members": metadata_packages.iter().map(|package| package["id"].clone()).collect::<Vec<_>>(),
"packages": metadata_packages,
});
let metadata_path = temporary.path().join("metadata.json");
std::fs::write(&metadata_path, serde_json::to_vec(&metadata).unwrap()).unwrap();
let call_log = temporary.path().join("cargo-calls.log");
let helper_source = temporary.path().join("fake-cargo.rs");
std::fs::write(
&helper_source,
r##"
use std::env;
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::process::Command;
fn main() {
let arguments: Vec<_> = env::args().skip(1).collect();
let mut log = OpenOptions::new()
.create(true)
.append(true)
.open(env::var_os("FAKE_CARGO_CALL_LOG").unwrap())
.unwrap();
writeln!(log, "{}", arguments.join(" ")).unwrap();
match arguments.first().map(String::as_str) {
Some("metadata") => {
let mut metadata = fs::read_to_string(env::var_os("FAKE_CARGO_METADATA").unwrap()).unwrap();
if std::path::Path::new(&env::var_os("FAKE_CARGO_DEPENDENCY_STATE").unwrap()).exists() {
metadata = metadata.replace(
"\"dependencies\":[]",
"\"dependencies\":[{\"name\":\"provenance-sdk\",\"req\":\"=0.2.3\"}]",
);
}
print!("{metadata}");
}
Some("add") => {
let manifest_path = env::var_os("FAKE_CARGO_MANIFEST").unwrap();
let workspace_path = env::var_os("FAKE_CARGO_WORKSPACE").unwrap();
let workspace = std::path::Path::new(&workspace_path);
let mut manifest = fs::read_to_string(&manifest_path).unwrap();
if !manifest.contains("provenance-sdk") {
manifest.push_str("\n[dependencies]\nprovenance-sdk = \"=0.2.3\"\n");
fs::write(&manifest_path, manifest).unwrap();
}
fs::write(env::var_os("FAKE_CARGO_LOCK").unwrap(), "version = 4\n").unwrap();
fs::write(env::var_os("FAKE_CARGO_DEPENDENCY_STATE").unwrap(), "installed\n").unwrap();
if env::var_os("FAKE_CARGO_BREAK_LOCK_OBSERVATION").is_some() {
let lock = env::var_os("FAKE_CARGO_LOCK").unwrap();
fs::remove_file(&lock).unwrap();
fs::create_dir(&lock).unwrap();
}
if env::var_os("FAKE_CARGO_FAIL_AFTER_WRITE").is_some() {
std::process::exit(42);
}
if env::var_os("FAKE_CARGO_STALE_INIT_PLAN").is_some() {
fs::write(workspace.join("AGENTS.md"), "concurrent instructions\n").unwrap();
}
if env::var_os("FAKE_CARGO_ROLLBACK_SECOND_RESTORE_FAIL").is_some() {
fs::write(workspace.join("AGENTS.md"), "concurrent instructions\n").unwrap();
assert!(Command::new("chmod").args(["555"]).arg(workspace).status().unwrap().success());
}
if env::var_os("FAKE_CARGO_ROLLBACK_FIRST_RESTORE_FAIL").is_some() {
fs::write(workspace.join("AGENTS.md"), "concurrent instructions\n").unwrap();
let package = std::path::Path::new(&manifest_path).parent().unwrap();
assert!(Command::new("chmod").args(["555"]).arg(package).status().unwrap().success());
}
}
_ => std::process::exit(64),
}
}
"##,
)
.unwrap();
let cargo = fake_bin.join(format!("cargo{}", std::env::consts::EXE_SUFFIX));
let output =
std::process::Command::new(std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()))
.args(["--edition=2021", "-o"])
.arg(&cargo)
.arg(&helper_source)
.output()
.expect("compile fake cargo");
assert!(
output.status.success(),
"fake cargo did not compile: {}",
String::from_utf8_lossy(&output.stderr)
);
Self {
temporary,
workspace_root,
fake_bin,
call_log,
cargo_manifest,
}
}
pub fn command(&self) -> Command {
let mut command = Command::cargo_bin("cargo-provenance").unwrap();
let path = std::env::join_paths(std::iter::once(self.fake_bin.clone()).chain(
std::env::split_paths(&std::env::var_os("PATH").expect("PATH is set")),
))
.unwrap();
command
.current_dir(self.workspace_root.join("nested"))
.env("PATH", path)
.env(
"FAKE_CARGO_METADATA",
self.temporary.path().join("metadata.json"),
)
.env("FAKE_CARGO_CALL_LOG", &self.call_log)
.env("FAKE_CARGO_MANIFEST", &self.cargo_manifest)
.env("FAKE_CARGO_LOCK", self.workspace_root.join("Cargo.lock"))
.env("FAKE_CARGO_WORKSPACE", &self.workspace_root)
.env(
"FAKE_CARGO_DEPENDENCY_STATE",
self.temporary.path().join("dependency-installed"),
);
command
}
pub fn root(&self) -> &Path {
&self.workspace_root
}
pub fn manifest(&self) -> Value {
serde_json::from_slice(
&std::fs::read(self.root().join(".provenance/state/manifest.json")).unwrap(),
)
.unwrap()
}
pub fn scope_path_prefix(&self) -> String {
self.manifest()["scopes"][0]["path_prefix"]
.as_str()
.unwrap()
.to_owned()
}
pub fn gitignore(&self) -> String {
std::fs::read_to_string(self.root().join(".gitignore")).unwrap()
}
pub fn cargo_calls(&self) -> String {
std::fs::read_to_string(&self.call_log).unwrap_or_default()
}
pub fn cargo_manifest(&self) -> String {
std::fs::read_to_string(&self.cargo_manifest).unwrap()
}
pub fn cargo_lock(&self) -> String {
std::fs::read_to_string(self.root().join("Cargo.lock")).unwrap()
}
}