use crate::common;
use common::{OutputAssertions, TestRepo};
#[test]
fn test_stack_test_true_passes() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
repo.create_stack(&["test-a", "test-b"]);
let output = repo.run_stax(&["test", "true"]);
output.assert_success();
let stdout = TestRepo::stdout(&output);
assert!(
stdout.contains("SUCCESS"),
"Expected SUCCESS in output, got: {}",
stdout
);
assert!(
stdout.contains("succeeded"),
"Expected 'succeeded' summary, got: {}",
stdout
);
}
#[test]
fn test_stack_test_false_fails() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
repo.create_stack(&["fail-a", "fail-b"]);
let output = repo.run_stax(&["test", "false"]);
output.assert_failure();
let stdout = TestRepo::stdout(&output);
assert!(
stdout.contains("FAIL"),
"Expected FAIL in output, got: {}",
stdout
);
assert!(
stdout.contains("failed"),
"Expected 'failed' summary, got: {}",
stdout
);
}
#[test]
fn test_stack_test_returns_to_original_branch() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
let branches = repo.create_stack(&["orig-a", "orig-b"]);
repo.run_stax(&["checkout", &branches[0]]);
let original = repo.current_branch();
let _ = repo.run_stax(&["test", "true"]);
assert_eq!(
repo.current_branch(),
original,
"Should return to original branch after stack test"
);
}
#[test]
fn test_stack_test_fail_fast() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
repo.create_stack(&["ff-a", "ff-b", "ff-c"]);
let output = repo.run_stax(&["test", "--fail-fast", "false"]);
output.assert_failure();
let stdout = TestRepo::stdout(&output);
assert!(
stdout.contains("fail-fast") || stdout.contains("Stopping"),
"Expected fail-fast message, got: {}",
stdout
);
}
#[test]
fn test_stack_test_with_command() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
repo.create_stack(&["cmd-a"]);
let output = repo.run_stax(&["test", "test", "-f", "README.md"]);
output.assert_success();
}
#[test]
fn test_stack_test_displays_command_output() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
repo.create_stack(&["out-a"]);
let output = repo.run_stax(&["test", "echo", "hello-from-command"]);
output.assert_success();
let stdout = TestRepo::stdout(&output);
assert!(
stdout.contains("hello-from-command"),
"Expected command output to be visible, got: {}",
stdout
);
}
#[test]
fn test_stack_run_alias_works() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
repo.create_stack(&["run-a"]);
let output = repo.run_stax(&["run", "true"]);
output.assert_success();
}
#[test]
fn test_stack_run_stack_filter_limits_scope() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
let stack_a = repo.create_stack(&["scope-a1", "scope-a2"]);
repo.run_stax(&["trunk"]).assert_success();
let stack_b = repo.create_stack(&["scope-b1"]);
let output = repo.run_stax(&["run", &format!("--stack={}", stack_a[0]), "true"]);
output.assert_success();
let stdout = TestRepo::stdout(&output);
assert!(
stdout.contains(&format!("{}:", stack_a[0])),
"Expected stack root '{}' to run, got: {}",
stack_a[0],
stdout
);
assert!(
stdout.contains(&format!("{}:", stack_a[1])),
"Expected stack descendant '{}' to run, got: {}",
stack_a[1],
stdout
);
assert!(
!stdout.contains(&format!("{}:", stack_b[0])),
"Did not expect other stack branch '{}' to run, got: {}",
stack_b[0],
stdout
);
}
#[test]
fn test_stack_run_stack_filter_unknown_branch_fails() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
repo.create_stack(&["unknown-a"]);
let output = repo.run_stax(&["run", "--stack=does-not-exist", "true"]);
output
.assert_failure()
.assert_stderr_contains("not tracked in the stack");
}
#[test]
fn test_stack_run_stack_flag_without_value_uses_current_stack() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
let stack_a = repo.create_stack(&["current-a1", "current-a2"]);
repo.run_stax(&["trunk"]).assert_success();
let stack_b = repo.create_stack(&["current-b1"]);
repo.run_stax(&["checkout", &stack_a[1]]).assert_success();
let output = repo.run_stax(&["run", "--stack", "true"]);
output.assert_success();
let stdout = TestRepo::stdout(&output);
assert!(
stdout.contains(&format!("{}:", stack_a[0])),
"Expected current stack branch '{}' to run, got: {}",
stack_a[0],
stdout
);
assert!(
stdout.contains(&format!("{}:", stack_a[1])),
"Expected current stack branch '{}' to run, got: {}",
stack_a[1],
stdout
);
assert!(
!stdout.contains(&format!("{}:", stack_b[0])),
"Did not expect different stack branch '{}' to run, got: {}",
stack_b[0],
stdout
);
}
#[test]
fn test_stack_run_all_excludes_trunk() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
let branches = repo.create_stack(&["all-a"]);
let output = repo.run_stax(&["run", "--all", "true"]);
output.assert_success();
let stdout = TestRepo::stdout(&output);
assert!(
stdout.contains(&format!("{}:", branches[0])),
"Expected tracked branch '{}' to run, got: {}",
branches[0],
stdout
);
assert!(
!stdout.contains("main:"),
"Trunk branch should not be executed under --all, got: {}",
stdout
);
}
#[test]
fn test_stack_run_parallel_uses_isolated_worktrees_and_restores_current_branch() {
let repo = TestRepo::new();
let branches = repo.create_stack(&["parallel-a", "parallel-b"]);
let original = repo.current_branch();
let output = repo.run_stax(&["run", "--parallel", "--jobs", "2", "pwd"]);
output.assert_success();
output.assert_stdout_contains("parallel worktree");
assert_eq!(repo.current_branch(), original);
for branch in branches {
output.assert_stdout_contains(&branch);
}
let worktrees = TestRepo::stdout(&repo.git(&["worktree", "list", "--porcelain"]));
assert_eq!(worktrees.matches("worktree ").count(), 1);
}
#[test]
fn test_stack_run_parallel_exposes_original_branch_name() {
let repo = TestRepo::new();
let branches = repo.create_stack(&["parallel-context-a", "parallel-context-b"]);
let output = repo.run_stax(&[
"run",
"--parallel",
"--jobs",
"2",
"printf 'context=%s\\n' \"$STAX_RUN_BRANCH\"",
]);
output.assert_success();
for branch in branches {
output.assert_stdout_contains(&format!("context={branch}"));
}
}
#[test]
fn test_stack_run_parallel_propagates_failures_and_cleans_worktrees() {
let repo = TestRepo::new();
repo.create_stack(&["parallel-fail-a", "parallel-fail-b"]);
let output = repo.run_stax(&["run", "--parallel", "false"]);
output.assert_failure();
output.assert_stdout_contains("2 failed");
let worktrees = TestRepo::stdout(&repo.git(&["worktree", "list", "--porcelain"]));
assert_eq!(worktrees.matches("worktree ").count(), 1);
}
#[test]
fn test_stack_run_parallel_rejects_zero_jobs() {
let repo = TestRepo::new();
repo.create_stack(&["parallel-jobs"]);
let output = repo.run_stax(&["run", "--parallel", "--jobs", "0", "true"]);
output.assert_failure();
output.assert_stderr_contains("invalid value");
}