use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::OnceLock;
pub(crate) const PMPX: &str = env!("CARGO_BIN_EXE_pmpx");
fn workspace_target_dir() -> PathBuf {
std::env::current_exe()
.ok()
.and_then(|exe| exe.parent().map(Path::to_path_buf))
.and_then(|deps| deps.parent().map(Path::to_path_buf))
.and_then(|profile| profile.parent().map(Path::to_path_buf))
.unwrap_or_else(std::env::temp_dir)
}
pub(crate) struct Sandbox {
_tmp: tempfile::TempDir,
pub(crate) config_dir: PathBuf,
pub(crate) data_dir: PathBuf,
pub(crate) project: PathBuf,
}
impl Sandbox {
pub(crate) fn new() -> Self {
let tmp = tempfile::tempdir().expect("should be able to create a temporary directory");
let config_dir = tmp.path().join("config");
let data_dir = tmp.path().join("data");
let project = tmp.path().join("project");
for d in [&config_dir, &data_dir, &project] {
std::fs::create_dir_all(d).unwrap();
}
Self {
_tmp: tmp,
config_dir,
data_dir,
project,
}
}
pub(crate) fn file(&self, name: &str) -> &Self {
let p = self.project.join(name);
if let Some(parent) = p.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(p, "").unwrap();
self
}
pub(crate) fn global_config(&self, body: &str) -> &Self {
std::fs::write(self.config_dir.join("config.toml"), body).unwrap();
self
}
pub(crate) fn install_plugin(
&self,
crate_name: &str,
manifest: &str,
lib: Option<&Path>,
) -> &Self {
let dir = self.data_dir.join("plugins").join(crate_name);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("pmpx-plugin.toml"), manifest).unwrap();
if let Some(src) = lib {
std::fs::copy(src, dir.join(src.file_name().unwrap())).unwrap();
}
self
}
pub(crate) fn run(&self, args: &[&str]) -> Output {
Command::new(PMPX)
.args(args)
.current_dir(&self.project)
.env("PMPX_CONFIG_DIR", &self.config_dir)
.env("PMPX_DATA_DIR", &self.data_dir)
.output()
.expect("should be able to start pmpx")
}
pub(crate) fn ok(&self, args: &[&str]) -> String {
let out = self.run(args);
assert!(
out.status.success(),
"`pmpx {}` should succeed, exit code {:?}\n--- stdout ---\n{}\n--- stderr ---\n{}",
args.join(" "),
out.status.code(),
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).into_owned()
}
}
pub(crate) fn stdout_of(out: &Output) -> String {
String::from_utf8_lossy(&out.stdout).into_owned()
}
pub(crate) fn stderr_of(out: &Output) -> String {
String::from_utf8_lossy(&out.stderr).into_owned()
}
pub(crate) fn build_fake_plugin() -> PathBuf {
static BUILT: OnceLock<PathBuf> = OnceLock::new();
BUILT
.get_or_init(|| {
let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join("fake-plugin");
let target_dir = workspace_target_dir().join("fixture-fake-plugin");
let status = Command::new("cargo")
.args(["build", "--release", "--manifest-path"])
.arg(fixture.join("Cargo.toml"))
.arg("--target-dir")
.arg(&target_dir)
.status()
.expect("should be able to start cargo");
assert!(status.success(), "the fake plugin should compile");
crate_plugin_kit::find_library(&target_dir.join("release"), "pmpx_plugin_fakepm")
.expect("should find the fake plugin's cdylib")
})
.clone()
}
pub(crate) const FAKEPM_MANIFEST: &str = r#"
[plugin]
name = "fakepm"
version = "0.1.0"
abi = 3
family = "faketest"
[detect]
strong = ["fakepm.lock"]
weak = ["fakepm.json"]
[context]
files = ["fakepm.json"]
"#;
pub(crate) fn sandbox_with_plugin() -> (Sandbox, PathBuf) {
let lib = build_fake_plugin();
let sb = Sandbox::new();
sb.install_plugin("pmpx-plugin-fakepm", FAKEPM_MANIFEST, Some(&lib));
(sb, lib)
}