#![allow(dead_code)]
use std::process::Command;
use cursus::model::config::{CargoConfig, Config, GitConfig, NpmConfig, PackageManager};
use tempfile::TempDir;
pub fn test_env(dir: &std::path::Path) -> cursus::Env {
let runner = std::sync::Arc::new(cursus::command::RealCommandRunner)
as std::sync::Arc<dyn cursus::command::CommandRunner>;
let path = cursus::path::AbsolutePath::new(dir).unwrap();
cursus::Env::new(
std::sync::Arc::clone(&runner),
std::sync::Arc::new(cursus::filesystem::LocalFilesystem),
std::sync::Arc::new(cursus::git::GitWorkdir::new(runner, path)),
)
}
pub async fn run_cursus(
args: impl IntoIterator<Item = impl Into<std::ffi::OsString> + Clone>,
cwd: &std::path::Path,
) -> anyhow::Result<std::process::ExitCode> {
use std::sync::Arc;
use clap::Parser as _;
use cursus::command::{CommandRunner, DryRunCommandRunner, RealCommandRunner};
use cursus::filesystem::LocalFilesystem;
use cursus::path::AbsolutePath;
let runner: Arc<dyn CommandRunner> = Arc::new(RealCommandRunner);
let filesystem: Arc<dyn cursus::filesystem::Filesystem> = Arc::new(LocalFilesystem);
let cli = cursus::cli::Cli::try_parse_from(args)?;
let runner = if cli.global.dry_run {
Arc::new(DryRunCommandRunner::new(runner)) as Arc<dyn CommandRunner>
} else {
runner
};
let cwd_abs = AbsolutePath::new(cwd)?;
let git_workdir = cursus::git::find_workdir(&cwd_abs, &*filesystem)
.await
.ok_or_else(|| anyhow::anyhow!("No git repository found"))?;
let git = Arc::new(cursus::git::GitWorkdir::new(
Arc::clone(&runner),
git_workdir,
));
let env = cursus::Env::new(runner, filesystem, git);
let config = cursus::model::config::load(env.fs(), env.git().path()).await?;
cursus::run(cli, env, config).await
}
pub fn git_cmd(dir: &std::path::Path, args: &[&str]) {
let output = Command::new("git")
.args(args)
.current_dir(dir)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.output()
.unwrap_or_else(|e| panic!("Failed to spawn git {args:?}: {e}"));
assert!(
output.status.success(),
"git {args:?} failed with status {}:\n{}",
output.status,
String::from_utf8_lossy(&output.stderr)
);
}
pub fn temp_real_git_repo() -> TempDir {
let dir = tempfile::tempdir().expect("Failed to create temp dir");
git_cmd(dir.path(), &["init", "-b", "main"]);
git_cmd(dir.path(), &["config", "user.name", "Cursus Test"]);
git_cmd(dir.path(), &["config", "user.email", "test@cursus.local"]);
git_cmd(dir.path(), &["config", "commit.gpgsign", "false"]);
git_cmd(dir.path(), &["config", "tag.gpgsign", "false"]);
git_cmd(
dir.path(),
&["commit", "--allow-empty", "-m", "chore: initial commit"],
);
dir
}
pub async fn temp_real_git_repo_with_config(pm: PackageManager, git_config: GitConfig) -> TempDir {
let dir = temp_real_git_repo();
let env = test_env(dir.path());
let config = match pm {
PackageManager::Npm => Config::new()
.with_npm(NpmConfig::enabled())
.with_git(git_config),
PackageManager::Cargo => Config::new()
.with_cargo(CargoConfig::enabled())
.with_git(git_config),
};
config.save(env.fs(), env.git().path()).await.unwrap();
dir
}
pub async fn temp_real_git_repo_with_cargo_workspace(
members: &[(&str, &str)],
git_config: GitConfig,
) -> TempDir {
let dir = temp_real_git_repo();
let env = test_env(dir.path());
let config = Config::new()
.with_cargo(CargoConfig::enabled())
.with_git(git_config);
config.save(env.fs(), env.git().path()).await.unwrap();
let member_list = members
.iter()
.map(|(name, _)| format!("\"{name}\""))
.collect::<Vec<_>>()
.join(", ");
std::fs::write(
dir.path().join("Cargo.toml"),
format!("[workspace]\nmembers = [{member_list}]\n"),
)
.unwrap();
for (name, version) in members {
let pkg_dir = dir.path().join(name);
std::fs::create_dir_all(pkg_dir.join("src")).unwrap();
std::fs::write(
pkg_dir.join("Cargo.toml"),
format!("[package]\nname = \"{name}\"\nversion = \"{version}\"\nedition = \"2024\"\n"),
)
.unwrap();
std::fs::write(pkg_dir.join("src/lib.rs"), "").unwrap();
}
git_cmd(dir.path(), &["add", "."]);
git_cmd(dir.path(), &["commit", "-m", "chore: set up workspace"]);
dir
}
pub fn add_local_remote(working_repo: &std::path::Path) -> TempDir {
let remote_dir = tempfile::tempdir().expect("Failed to create remote dir");
git_cmd(remote_dir.path(), &["init", "--bare"]);
git_cmd(
working_repo,
&[
"remote",
"add",
"origin",
remote_dir.path().to_str().unwrap(),
],
);
remote_dir
}
pub fn git_log(dir: &std::path::Path) -> Vec<String> {
let output = Command::new("git")
.args(["log", "--format=%s"])
.current_dir(dir)
.output()
.expect("Failed to run git log");
String::from_utf8(output.stdout)
.expect("git log output is not UTF-8")
.lines()
.map(str::to_string)
.collect()
}
pub fn git_tags(dir: &std::path::Path) -> Vec<String> {
let output = Command::new("git")
.args(["tag", "--list"])
.current_dir(dir)
.output()
.expect("Failed to run git tag");
String::from_utf8(output.stdout)
.expect("git tag output is not UTF-8")
.lines()
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect()
}
pub fn git_tag_exists(dir: &std::path::Path, tag: &str) -> bool {
git_tags(dir).contains(&tag.to_string())
}
pub fn git_local_branch_exists(dir: &std::path::Path, branch: &str) -> bool {
let output = Command::new("git")
.args(["branch", "--list", branch])
.current_dir(dir)
.output()
.expect("Failed to run git branch");
!String::from_utf8(output.stdout)
.expect("git branch output is not UTF-8")
.trim()
.is_empty()
}
pub fn git_push_to_remote(working_repo: &std::path::Path) {
let branch = git_current_branch(working_repo);
let output = Command::new("git")
.args(["push", "origin", &branch])
.current_dir(working_repo)
.stderr(std::process::Stdio::piped())
.output()
.expect("Failed to run git push");
assert!(
output.status.success(),
"git push to origin failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
}
pub fn git_current_branch(dir: &std::path::Path) -> String {
let output = Command::new("git")
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.current_dir(dir)
.output()
.expect("Failed to run git rev-parse");
let branch = String::from_utf8(output.stdout)
.expect("git rev-parse output is not UTF-8")
.trim()
.to_string();
assert!(
branch != "HEAD",
"git_current_branch called in detached HEAD state"
);
branch
}
pub fn temp_git_repo() -> TempDir {
let dir = tempfile::tempdir().expect("Failed to create temp dir");
std::fs::create_dir(dir.path().join(".git")).unwrap();
dir
}
pub async fn temp_git_repo_with_config(pm: PackageManager) -> TempDir {
let dir = temp_git_repo();
let env = test_env(dir.path());
let config = match pm {
PackageManager::Npm => Config::new().with_npm(NpmConfig::enabled()),
PackageManager::Cargo => Config::new().with_cargo(CargoConfig::enabled()),
};
config.save(env.fs(), env.git().path()).await.unwrap();
dir
}
pub async fn temp_git_repo_with_project(pm: PackageManager) -> TempDir {
let dir = temp_git_repo();
let env = test_env(dir.path());
let config = match pm {
PackageManager::Npm => Config::new().with_npm(NpmConfig::enabled()),
PackageManager::Cargo => Config::new().with_cargo(CargoConfig::enabled()),
};
config.save(env.fs(), env.git().path()).await.unwrap();
match pm {
PackageManager::Npm => {
std::fs::write(
dir.path().join("package.json"),
r#"{"name": "test-project", "version": "0.1.0"}"#,
)
.unwrap();
}
PackageManager::Cargo => {
std::fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"test-project\"\nversion = \"0.1.0\"\nedition = \"2024\"\n",
)
.unwrap();
std::fs::create_dir_all(dir.path().join("src")).unwrap();
std::fs::write(dir.path().join("src/lib.rs"), "").unwrap();
}
}
dir
}
pub async fn temp_git_repo_with_cargo_workspace(members: &[(&str, &str)]) -> TempDir {
let dir = temp_git_repo();
let env = test_env(dir.path());
let config = Config::new().with_cargo(CargoConfig::enabled());
config.save(env.fs(), env.git().path()).await.unwrap();
let member_list = members
.iter()
.map(|(name, _)| format!("\"{name}\""))
.collect::<Vec<_>>()
.join(", ");
std::fs::write(
dir.path().join("Cargo.toml"),
format!("[workspace]\nmembers = [{member_list}]\n"),
)
.unwrap();
for (name, version) in members {
let pkg_dir = dir.path().join(name);
std::fs::create_dir_all(pkg_dir.join("src")).unwrap();
std::fs::write(
pkg_dir.join("Cargo.toml"),
format!("[package]\nname = \"{name}\"\nversion = \"{version}\"\nedition = \"2024\"\n"),
)
.unwrap();
std::fs::write(pkg_dir.join("src/lib.rs"), "").unwrap();
}
dir
}
pub async fn temp_git_repo_with_project_in_subfolder(
pm: PackageManager,
subfolder: &str,
) -> TempDir {
let dir = temp_git_repo();
let env = test_env(dir.path());
let mut config = match pm {
PackageManager::Npm => Config::new().with_npm(NpmConfig::enabled()),
PackageManager::Cargo => Config::new().with_cargo(CargoConfig::enabled()),
};
match pm {
PackageManager::Npm => config.npm.path = Some(subfolder.to_string()),
PackageManager::Cargo => config.cargo.path = Some(subfolder.to_string()),
}
config.save(env.fs(), env.git().path()).await.unwrap();
let sub_path = dir.path().join(subfolder);
std::fs::create_dir_all(&sub_path).unwrap();
match pm {
PackageManager::Npm => {
std::fs::write(
sub_path.join("package.json"),
r#"{"name": "test-project", "version": "0.1.0"}"#,
)
.unwrap();
}
PackageManager::Cargo => {
std::fs::write(
sub_path.join("Cargo.toml"),
"[package]\nname = \"test-project\"\nversion = \"0.1.0\"\nedition = \"2024\"\n",
)
.unwrap();
}
}
dir
}
pub fn write_changeset(dir: &std::path::Path, filename: &str, content: &str) {
let cursus_dir = dir.join(".cursus");
std::fs::create_dir_all(&cursus_dir).unwrap();
std::fs::write(cursus_dir.join(filename), content).unwrap();
}
pub fn git_enabled_config() -> GitConfig {
GitConfig::enabled_config()
}
pub fn git_set_remote_head(working_repo: &std::path::Path, branch: &str) {
git_cmd(working_repo, &["remote", "set-head", "origin", branch]);
}
pub async fn temp_real_git_repo_with_project(pm: PackageManager) -> TempDir {
let dir = temp_real_git_repo();
let env = test_env(dir.path());
let config = match pm {
PackageManager::Npm => Config::new().with_npm(NpmConfig::enabled()),
PackageManager::Cargo => Config::new().with_cargo(CargoConfig::enabled()),
};
config.save(env.fs(), env.git().path()).await.unwrap();
match pm {
PackageManager::Npm => {
std::fs::write(
dir.path().join("package.json"),
r#"{"name": "test-project", "version": "0.1.0"}"#,
)
.unwrap();
}
PackageManager::Cargo => {
std::fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"test-project\"\nversion = \"0.1.0\"\nedition = \"2024\"\n",
)
.unwrap();
std::fs::create_dir_all(dir.path().join("src")).unwrap();
std::fs::write(dir.path().join("src/lib.rs"), "").unwrap();
}
}
git_cmd(dir.path(), &["add", "."]);
git_cmd(dir.path(), &["commit", "-m", "chore: add project files"]);
dir
}