use std::fs;
use guild_cli::run_target;
use tempfile::tempdir;
#[allow(clippy::type_complexity)]
fn create_workspace(dir: &std::path::Path, projects: &[(&str, &str, &[(&str, &str, &[&str])])]) {
let workspace_toml = r#"[workspace]
name = "test-workspace"
projects = ["*"]
"#;
fs::write(dir.join("guild.toml"), workspace_toml).unwrap();
for (name, deps, targets) in projects {
let project_dir = dir.join(name);
fs::create_dir_all(&project_dir).unwrap();
let deps_str = if deps.is_empty() {
String::new()
} else {
let dep_list: Vec<String> = deps.split(',').map(|d| format!("\"{d}\"")).collect();
format!("depends_on = [{}]\n", dep_list.join(", "))
};
let targets_str: String = targets
.iter()
.map(|(target_name, cmd, target_deps)| {
let target_deps_str = if target_deps.is_empty() {
String::new()
} else {
let dep_list: Vec<String> =
target_deps.iter().map(|d| format!("\"{d}\"")).collect();
format!("depends_on = [{}]\n", dep_list.join(", "))
};
format!("[targets.{target_name}]\ncommand = \"{cmd}\"\n{target_deps_str}\n")
})
.collect();
let project_toml = format!("[project]\nname = \"{name}\"\n{deps_str}\n{targets_str}");
fs::write(project_dir.join("guild.toml"), project_toml).unwrap();
}
}
#[tokio::test]
async fn test_run_single_project() {
let dir = tempdir().unwrap();
create_workspace(
dir.path(),
&[("my-app", "", &[("build", "echo building", &[])])],
);
let result = run_target(dir.path(), "build", None).await.unwrap();
assert_eq!(result.success_count, 1);
assert_eq!(result.failure_count, 0);
assert!(result.is_success());
}
#[tokio::test]
async fn test_run_multiple_projects() {
let dir = tempdir().unwrap();
create_workspace(
dir.path(),
&[
("app-a", "", &[("build", "echo a", &[])]),
("app-b", "", &[("build", "echo b", &[])]),
("lib", "", &[("build", "echo lib", &[])]),
],
);
let result = run_target(dir.path(), "build", None).await.unwrap();
assert_eq!(result.success_count, 3);
assert_eq!(result.failure_count, 0);
assert!(result.is_success());
}
#[tokio::test]
async fn test_run_with_dependencies() {
let dir = tempdir().unwrap();
create_workspace(
dir.path(),
&[
("my-app", "my-lib", &[("build", "echo app", &["^build"])]),
("my-lib", "", &[("build", "echo lib", &[])]),
],
);
let result = run_target(dir.path(), "build", None).await.unwrap();
assert_eq!(result.success_count, 2);
assert_eq!(result.failure_count, 0);
let lib_idx = result
.task_results
.iter()
.position(|r| r.task_id.project().as_str() == "my-lib")
.unwrap();
let app_idx = result
.task_results
.iter()
.position(|r| r.task_id.project().as_str() == "my-app")
.unwrap();
assert!(lib_idx < app_idx, "lib should complete before app");
}
#[tokio::test]
async fn test_run_scoped_to_project() {
let dir = tempdir().unwrap();
create_workspace(
dir.path(),
&[
("app-a", "", &[("build", "echo a", &[])]),
("app-b", "", &[("build", "echo b", &[])]),
],
);
let result = run_target(dir.path(), "build", Some("app-a"))
.await
.unwrap();
assert_eq!(result.success_count, 1);
assert_eq!(result.failure_count, 0);
assert_eq!(result.task_results[0].task_id.project().as_str(), "app-a");
}
#[tokio::test]
async fn test_run_scoped_includes_upstream_deps() {
let dir = tempdir().unwrap();
create_workspace(
dir.path(),
&[
("my-app", "my-lib", &[("build", "echo app", &["^build"])]),
("my-lib", "", &[("build", "echo lib", &[])]),
("other", "", &[("build", "echo other", &[])]),
],
);
let result = run_target(dir.path(), "build", Some("my-app"))
.await
.unwrap();
assert_eq!(result.success_count, 2);
assert_eq!(result.failure_count, 0);
let projects: Vec<&str> = result
.task_results
.iter()
.map(|r| r.task_id.project().as_str())
.collect();
assert!(projects.contains(&"my-app"));
assert!(projects.contains(&"my-lib"));
assert!(!projects.contains(&"other"));
}
#[tokio::test]
async fn test_run_failing_task() {
let dir = tempdir().unwrap();
create_workspace(dir.path(), &[("bad-app", "", &[("build", "exit 1", &[])])]);
let result = run_target(dir.path(), "build", None).await.unwrap();
assert_eq!(result.success_count, 0);
assert_eq!(result.failure_count, 1);
assert!(!result.is_success());
}
#[tokio::test]
async fn test_run_nonexistent_target() {
let dir = tempdir().unwrap();
create_workspace(
dir.path(),
&[("my-app", "", &[("build", "echo build", &[])])],
);
let result = run_target(dir.path(), "test", None).await.unwrap();
assert_eq!(result.success_count, 0);
assert_eq!(result.failure_count, 0);
assert!(result.is_success());
}
#[tokio::test]
async fn test_run_nonexistent_project() {
let dir = tempdir().unwrap();
create_workspace(
dir.path(),
&[("my-app", "", &[("build", "echo build", &[])])],
);
let result = run_target(dir.path(), "build", Some("nonexistent")).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_run_from_subdirectory() {
let dir = tempdir().unwrap();
create_workspace(
dir.path(),
&[("my-app", "", &[("build", "echo build", &[])])],
);
let subdir = dir.path().join("my-app");
let result = run_target(&subdir, "build", None).await.unwrap();
assert_eq!(result.success_count, 1);
assert!(result.is_success());
}
#[tokio::test]
async fn test_run_diamond_dependency() {
let dir = tempdir().unwrap();
create_workspace(
dir.path(),
&[
("app", "lib-a,lib-b", &[("build", "echo app", &["^build"])]),
("lib-a", "core", &[("build", "echo lib-a", &["^build"])]),
("lib-b", "core", &[("build", "echo lib-b", &["^build"])]),
("core", "", &[("build", "echo core", &[])]),
],
);
let result = run_target(dir.path(), "build", None).await.unwrap();
assert_eq!(result.success_count, 4);
assert_eq!(result.failure_count, 0);
let core_idx = result
.task_results
.iter()
.position(|r| r.task_id.project().as_str() == "core")
.unwrap();
let app_idx = result
.task_results
.iter()
.position(|r| r.task_id.project().as_str() == "app")
.unwrap();
assert_eq!(core_idx, 0, "core should complete first");
assert_eq!(app_idx, 3, "app should complete last");
}
#[tokio::test]
async fn test_run_with_local_target_dependencies() {
let dir = tempdir().unwrap();
create_workspace(
dir.path(),
&[(
"my-app",
"",
&[
("build", "echo build", &[]),
("test", "echo test", &["build"]),
],
)],
);
let result = run_target(dir.path(), "test", None).await.unwrap();
assert_eq!(result.success_count, 2);
assert_eq!(result.failure_count, 0);
let build_idx = result
.task_results
.iter()
.position(|r| r.task_id.target().as_str() == "build")
.unwrap();
let test_idx = result
.task_results
.iter()
.position(|r| r.task_id.target().as_str() == "test")
.unwrap();
assert!(build_idx < test_idx, "build should complete before test");
}