#![allow(dead_code, unreachable_pub)]
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::process::Command;
use assert_cmd::assert::OutputAssertExt;
use assert_fs::fixture::{ChildPath, FileWriteStr, PathChild, PathCreateDir};
use etcetera::BaseStrategy;
use rustc_hash::FxHashSet;
use prek_consts::PRE_COMMIT_CONFIG_YAML;
use prek_consts::env_vars::EnvVars;
pub fn git_cmd(dir: impl AsRef<Path>) -> Command {
let mut cmd = Command::new("git");
cmd.current_dir(dir)
.args(["-c", "commit.gpgsign=false"])
.args(["-c", "tag.gpgsign=false"])
.args(["-c", "core.autocrlf=false"])
.args(["-c", "user.name=Prek Test"])
.args(["-c", "user.email=test@prek.dev"]);
cmd
}
pub struct TestContext {
temp_dir: ChildPath,
home_dir: ChildPath,
filters: Vec<(String, String)>,
#[allow(dead_code)]
_root: tempfile::TempDir,
}
impl TestContext {
pub fn new() -> Self {
let bucket = Self::test_bucket_dir();
fs_err::create_dir_all(&bucket).expect("Failed to create test bucket");
let root = tempfile::TempDir::new_in(bucket).expect("Failed to create test root directory");
let temp_dir = ChildPath::new(root.path()).child("temp");
fs_err::create_dir_all(&temp_dir).expect("Failed to create test working directory");
Self::from_root(root, temp_dir)
}
pub fn new_at(path: PathBuf) -> Self {
let bucket = Self::test_bucket_dir();
fs_err::create_dir_all(&bucket).expect("Failed to create test bucket");
let root = tempfile::TempDir::new_in(bucket).expect("Failed to create test root directory");
let temp_dir = ChildPath::new(path);
fs_err::create_dir_all(&temp_dir).expect("Failed to create test working directory");
Self::from_root(root, temp_dir)
}
fn from_root(root: tempfile::TempDir, temp_dir: ChildPath) -> Self {
let home_dir = ChildPath::new(root.path()).child("home");
fs_err::create_dir_all(&home_dir).expect("Failed to create test home directory");
let mut filters = Vec::new();
filters.extend(
Self::path_patterns(&temp_dir)
.into_iter()
.map(|pattern| (pattern, "[TEMP_DIR]/".to_string())),
);
filters.extend(
Self::path_patterns(&home_dir)
.into_iter()
.map(|pattern| (pattern, "[HOME]/".to_string())),
);
if let Some(current_exe) = EnvVars::var_os("NEXTEST_BIN_EXE_prek") {
filters.extend(
Self::path_patterns(current_exe)
.into_iter()
.map(|pattern| (pattern, "[CURRENT_EXE]".to_string())),
);
}
let current_exe = assert_cmd::cargo::cargo_bin!("prek");
filters.extend(
Self::path_patterns(current_exe)
.into_iter()
.map(|pattern| (pattern, "[CURRENT_EXE]".to_string())),
);
Self {
temp_dir,
home_dir,
filters,
_root: root,
}
}
pub fn test_bucket_dir() -> PathBuf {
EnvVars::var(EnvVars::PREK_INTERNAL__TEST_DIR)
.map(PathBuf::from)
.unwrap_or_else(|_| {
etcetera::base_strategy::choose_base_strategy()
.expect("Failed to find base strategy")
.data_dir()
.join("prek")
.join("tests")
})
}
fn path_pattern(path: impl AsRef<Path>) -> String {
format!(
r"{}\\?/?",
regex::escape(&path.as_ref().display().to_string())
.replace('/', r"(\\|\/)")
.replace(r"\\", r"(\\|\/)")
)
}
pub fn path_patterns(path: impl AsRef<Path>) -> Vec<String> {
let mut patterns = Vec::new();
if path.as_ref().exists() {
patterns.push(Self::path_pattern(
path.as_ref()
.canonicalize()
.expect("Failed to create canonical path"),
));
}
patterns.push(Self::path_pattern(path));
patterns
}
pub fn read(&self, file: impl AsRef<Path>) -> String {
fs_err::read_to_string(self.temp_dir.join(&file))
.unwrap_or_else(|_| panic!("Missing file: `{}`", file.as_ref().display()))
}
pub fn command(&self) -> Command {
let mut cmd = if EnvVars::is_set(EnvVars::PREK_INTERNAL__RUN_ORIGINAL_PRE_COMMIT) {
let mut cmd = Command::new("pre-commit");
cmd.current_dir(self.work_dir());
cmd.env(EnvVars::PRE_COMMIT_HOME, &**self.home_dir());
cmd
} else {
let bin = EnvVars::var_os("NEXTEST_BIN_EXE_prek")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(assert_cmd::cargo::cargo_bin!("prek")));
let mut cmd = Command::new(bin);
cmd.current_dir(self.work_dir());
cmd.env(EnvVars::PREK_HOME, &**self.home_dir());
cmd.env(EnvVars::PREK_INTERNAL__SORT_FILENAMES, "1");
cmd
};
cmd.env("GIT_CONFIG_COUNT", "1")
.env("GIT_CONFIG_KEY_0", "core.autocrlf")
.env("GIT_CONFIG_VALUE_0", "false");
cmd
}
pub fn run(&self) -> Command {
let mut command = self.command();
command.arg("run");
command
}
pub fn validate_config(&self) -> Command {
let mut command = self.command();
command.arg("validate-config");
command
}
pub fn validate_manifest(&self) -> Command {
let mut command = self.command();
command.arg("validate-manifest");
command
}
pub fn install(&self) -> Command {
let mut command = self.command();
command.arg("install");
command
}
pub fn prepare_hooks(&self) -> Command {
let mut command = self.command();
command.arg("prepare-hooks");
command
}
pub fn uninstall(&self) -> Command {
let mut command = self.command();
command.arg("uninstall");
command
}
pub fn sample_config(&self) -> Command {
let mut command = self.command();
command.arg("sample-config");
command
}
pub fn list(&self) -> Command {
let mut command = self.command();
command.arg("list");
command
}
pub fn auto_update(&self) -> Command {
let mut cmd = self.command();
cmd.arg("auto-update");
cmd
}
pub fn try_repo(&self) -> Command {
let mut cmd = self.command();
cmd.arg("try-repo");
cmd
}
pub fn filters(&self) -> Vec<(&str, &str)> {
self.filters
.iter()
.map(|(p, r)| (p.as_str(), r.as_str()))
.chain(INSTA_FILTERS.iter().copied())
.collect()
}
pub fn work_dir(&self) -> &ChildPath {
&self.temp_dir
}
pub fn home_dir(&self) -> &ChildPath {
&self.home_dir
}
pub fn init_project(&self) {
git_cmd(&self.temp_dir)
.arg("-c")
.arg("init.defaultBranch=master")
.arg("init")
.assert()
.success();
}
pub fn git_add(&self, path: impl AsRef<OsStr>) {
git_cmd(&self.temp_dir)
.arg("add")
.arg(path)
.assert()
.success();
}
pub fn git_commit(&self, message: &str) {
git_cmd(&self.temp_dir)
.arg("commit")
.arg("-m")
.arg(message)
.env(EnvVars::PREK_HOME, &**self.home_dir())
.assert()
.success();
}
pub fn git_tag(&self, tag: &str) {
git_cmd(&self.temp_dir)
.arg("tag")
.arg(tag)
.arg("-m")
.arg(format!("Tag {tag}"))
.assert()
.success();
}
pub fn git_reset(&self, target: &str) {
git_cmd(&self.temp_dir)
.arg("reset")
.arg(target)
.assert()
.success();
}
pub fn git_rm(&self, path: &str) {
git_cmd(&self.temp_dir)
.arg("rm")
.arg("--cached")
.arg(path)
.assert()
.success();
let file_path = self.temp_dir.child(path);
if file_path.exists() {
fs_err::remove_file(file_path).unwrap();
}
}
pub fn git_clean(&self) {
git_cmd(&self.temp_dir)
.arg("clean")
.arg("-fdx")
.assert()
.success();
}
pub fn git_branch(&self, branch_name: &str) {
git_cmd(&self.temp_dir)
.arg("branch")
.arg(branch_name)
.assert()
.success();
}
pub fn git_checkout(&self, branch_name: &str) {
git_cmd(&self.temp_dir)
.arg("checkout")
.arg(branch_name)
.assert()
.success();
}
pub fn write_pre_commit_config(&self, content: &str) {
self.temp_dir
.child(PRE_COMMIT_CONFIG_YAML)
.write_str(content)
.expect("Failed to write pre-commit config");
}
pub fn setup_workspace(&self, project_paths: &[&str], config: &str) -> anyhow::Result<()> {
self.temp_dir
.child(PRE_COMMIT_CONFIG_YAML)
.write_str(config)?;
for path in project_paths {
let project_dir = self.temp_dir.child(path);
project_dir.create_dir_all()?;
project_dir
.child(PRE_COMMIT_CONFIG_YAML)
.write_str(config)?;
}
Ok(())
}
#[must_use]
pub fn with_filtered_cache_size(mut self) -> Self {
self.filters
.push((r"(?m)^\d+\n".to_string(), "[SIZE]\n".to_string()));
self.filters.push((
r"(?m)^\d+(\.\d+)? ([KMGTPE]i)?B\n".to_string(),
"[SIZE]\n".to_string(),
));
self
}
#[must_use]
pub fn with_filtered_cache_clean_summary(mut self) -> Self {
self.filters.push((
r"(?m)^Removed \d+ files? \([^)]+\)\n".to_string(),
"Removed [N] file(s) ([SIZE])\n".to_string(),
));
self
}
}
#[doc(hidden)] pub const INSTA_FILTERS: &[(&str, &str)] = &[
(r"(\s|\()(\d+\.)?\d+\s?([KMGTPE]i)?B", "$1[SIZE]"),
(r"\\([\w\d]|\.\.|\.)", "/$1"),
(
r"Caused by: .* \(os error 2\)",
"Caused by: No such file or directory (os error 2)",
),
(r"\b(\d+\.)?\d+(ms|s)\b", "[TIME]"),
(r"(?m)^warning: Waiting to acquire lock.*\n", ""),
];
#[allow(unused_macros)]
macro_rules! cmd_snapshot {
($spawnable:expr, @$snapshot:literal) => {{
cmd_snapshot!($crate::common::INSTA_FILTERS.iter().copied().collect::<Vec<_>>(), $spawnable, @$snapshot)
}};
($filters:expr, $spawnable:expr, @$snapshot:literal) => {{
let mut settings = insta::Settings::clone_current();
for (matcher, replacement) in $filters {
settings.add_filter(matcher, replacement);
}
let _guard = settings.bind_to_scope();
insta_cmd::assert_cmd_snapshot!($spawnable, @$snapshot);
}};
}
#[allow(unused_imports)]
pub(crate) use cmd_snapshot;
pub(crate) fn remove_bin_from_path(bin: &str, path: Option<OsString>) -> anyhow::Result<OsString> {
let path = path.unwrap_or(EnvVars::var_os(EnvVars::PATH).expect("Path must be set"));
let Ok(dirs) = which::which_all(bin) else {
return Ok(path);
};
let dirs: FxHashSet<_> = dirs
.filter_map(|path| path.parent().map(Path::to_path_buf))
.collect();
let new_path_entries: Vec<_> = std::env::split_paths(&path)
.filter(|path| !dirs.contains(path.as_path()))
.collect();
Ok(std::env::join_paths(new_path_entries)?)
}