use std::fs;
use insta::assert_snapshot;
use crate::common::TestEnvironment;
use crate::common::TestWorkDir;
use crate::common::create_commit_with_files;
#[test]
fn test_run_simple() {
let mut test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let fake_formatter = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(fake_formatter.is_file());
let fake_formatter_path = fake_formatter.to_string_lossy().into_owned();
test_env.add_paths_to_normalize(fake_formatter.clone(), "$FAKE_FORMATTER_PATH");
let work_dir = test_env.work_dir("repo");
work_dir.write_file("A.txt", "A");
work_dir.run_jj(&["commit", "-m", "A"]).success();
work_dir.write_file("b.txt", "b");
work_dir.run_jj(&["commit", "-m", "B"]).success();
work_dir.write_file("c.txt", "test to replace");
work_dir.run_jj(&["commit", "-m", "C"]).success();
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ zsuskulnrvyrovkzqrwmxqlsskqntxvp
â—‹ kkmpptxzrspxrzommnulwmwkkqwworplC
│
â—‹ rlvkpnrzqnoowoytxnquwvuryrwnrmlpB
│
â—‹ qpvuntsmwlqtpsluzzsnyyzlmlwvmlnuA
│
â—† zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
[EOF]
");
let stdout = work_dir
.run_jj(&[
"run",
"-r",
"..@",
"--",
&fake_formatter_path,
"--stdout",
"x",
"--tee",
"touched.txt",
])
.success()
.stdout;
insta::assert_snapshot!(stdout, @"xxxx[EOF]");
}
#[test]
fn test_run_on_immutable() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
let fake_formatter = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(fake_formatter.is_file());
let fake_formatter_path = fake_formatter.to_string_lossy();
work_dir.write_file("A.txt", "A");
work_dir.run_jj(&["commit", "-m", "A"]).success();
work_dir.write_file("b.txt", "b");
work_dir.run_jj(&["commit", "-m", "B"]).success();
work_dir.write_file("c.txt", "test to replace");
work_dir.run_jj(&["commit", "-m", "C"]).success();
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ zsuskulnrvyrovkzqrwmxqlsskqntxvp
â—‹ kkmpptxzrspxrzommnulwmwkkqwworplC
│
â—‹ rlvkpnrzqnoowoytxnquwvuryrwnrmlpB
│
â—‹ qpvuntsmwlqtpsluzzsnyyzlmlwvmlnuA
│
â—† zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
[EOF]
");
let output = work_dir.run_jj(&[
"run",
"-r",
"all()",
"--",
&fake_formatter_path,
"--uppercase",
]);
insta::assert_snapshot!(output, @r"
------- stderr -------
Error: The root commit 000000000000 is immutable
[EOF]
[exit status: 1]
");
}
#[test]
fn test_run_noop() {
let mut test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let fake_formatter = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(fake_formatter.is_file());
let fake_formatter_path = fake_formatter.to_string_lossy().into_owned();
test_env.add_paths_to_normalize(fake_formatter.clone(), "$FAKE_FORMATTER_PATH");
let work_dir = test_env.work_dir("repo");
work_dir.write_file("A.txt", "A");
work_dir.run_jj(&["commit", "-m", "A"]).success();
work_dir.write_file("b.txt", "b");
work_dir.run_jj(&["commit", "-m", "B"]).success();
work_dir.write_file("c.txt", "test to replace");
work_dir.run_jj(&["commit", "-m", "C"]).success();
insta::assert_snapshot!(get_log_output(&work_dir), @r"
@ zsuskulnrvyrovkzqrwmxqlsskqntxvp
â—‹ kkmpptxzrspxrzommnulwmwkkqwworplC
│
â—‹ rlvkpnrzqnoowoytxnquwvuryrwnrmlpB
│
â—‹ qpvuntsmwlqtpsluzzsnyyzlmlwvmlnuA
│
â—† zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
[EOF]
");
let output = work_dir
.run_jj(&[
"run",
"-r",
"..@",
"--",
&fake_formatter_path,
"--stdout",
"foo",
])
.success();
insta::assert_snapshot!(output.stdout, @"foofoofoofoo[EOF]");
insta::assert_snapshot!(output.stderr, @r"
Nothing changed.
[EOF]
");
}
#[test]
fn test_run_sets_env_vars() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("seed.txt", "seed");
work_dir.run_jj(&["commit", "-m", "seed"]).success();
let log_template = r#"change_id ++ " " ++ commit_id ++ " " ++ description ++ "\n""#;
insta::assert_snapshot!(
work_dir.run_jj(&["log", "-T", log_template]),
@r"
@ rlvkpnrzqnoowoytxnquwvuryrwnrmlp fc4c875c9bc90128cbb9e8084dd5f5f336b383d9
â—‹ qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu 5fbe90560fed1c39d46a46a672ba98abd53bdc6d seed
│
â—† zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 0000000000000000000000000000000000000000
[EOF]
"
);
let jj_args: &[&str] = if cfg!(windows) {
&[
"run",
"-r",
"@-",
"--",
"cmd",
"/c",
"echo %JJ_CHANGE_ID%>change_id.txt && echo %JJ_COMMIT_ID%>commit_id.txt",
]
} else {
&[
"run",
"-r",
"@-",
"--",
"sh",
"-c",
"echo $JJ_CHANGE_ID > change_id.txt && echo $JJ_COMMIT_ID > commit_id.txt",
]
};
work_dir.run_jj(jj_args).success();
let normalize_whitespace = |s: String| {
s.replace("\r\n", "\n")
.lines()
.map(|line| line.trim_end())
.collect::<Vec<_>>()
.join("\n")
+ "\n"
};
insta::assert_snapshot!(
work_dir
.run_jj(&["file", "show", "-r", "@-", "change_id.txt"])
.normalize_stdout_with(normalize_whitespace),
@r"
qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu
[EOF]
"
);
insta::assert_snapshot!(
work_dir
.run_jj(&["file", "show", "-r", "@-", "commit_id.txt"])
.normalize_stdout_with(normalize_whitespace),
@r"
5fbe90560fed1c39d46a46a672ba98abd53bdc6d
[EOF]
"
);
}
#[test]
fn test_run_from_subdir_skips_commits_without_it() {
let mut test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let fake_formatter = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(fake_formatter.is_file());
let fake_formatter_path = fake_formatter.to_string_lossy().into_owned();
test_env.add_paths_to_normalize(fake_formatter.clone(), "$FAKE_FORMATTER_PATH");
let work_dir = test_env.work_dir("repo");
work_dir.write_file("seed.txt", "seed");
work_dir.run_jj(&["commit", "-m", "no-sub"]).success();
work_dir.write_file("sub/file.txt", "x");
work_dir.run_jj(&["commit", "-m", "with-sub"]).success();
let sub_dir = work_dir.dir("sub");
let output = sub_dir
.run_jj(&[
"run",
"-r",
"@-|@--",
"--",
&fake_formatter_path,
"--tee",
"ran.txt",
])
.success()
.normalize_backslash();
insta::assert_snapshot!(output.stderr, @r"
Skipped commit 3bb1f1ca3c09a8e6be46ef48515803464b16b426: directory does not exist: sub
Rewrote 1 commits
Working copy (@) now at: kkmpptxz 3548431a (empty) (no description set)
Parent commit (@-) : rlvkpnrz 3aa9a235 with-sub
Added 1 files, modified 0 files, removed 0 files
[EOF]
");
insta::assert_snapshot!(
work_dir
.run_jj(&["file", "list", "-r", "@-"])
.normalize_backslash(),
@r"
seed.txt
sub/file.txt
sub/ran.txt
[EOF]
"
);
}
#[test]
fn test_run_root_flag() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let fake_formatter = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(fake_formatter.is_file());
let fake_formatter_path = fake_formatter.to_string_lossy().into_owned();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("sub/file.txt", "x");
work_dir.run_jj(&["commit", "-m", "with-sub"]).success();
let sub_dir = work_dir.dir("sub");
sub_dir
.run_jj(&[
"run",
"--root",
"-r",
"@-",
"--",
&fake_formatter_path,
"--tee",
"ran.txt",
])
.success();
insta::assert_snapshot!(
work_dir
.run_jj(&["file", "list", "-r", "@-"])
.normalize_backslash(),
@r"
ran.txt
sub/file.txt
[EOF]
"
);
}
#[test]
fn test_run_uses_revsets_run_as_default() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let fake_formatter = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(fake_formatter.is_file());
let fake_formatter_path = fake_formatter.to_string_lossy().into_owned();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("file", "foo");
work_dir
.run_jj(["bookmark", "create", "-r@", "foo"])
.success();
work_dir.run_jj(["new", "root()"]).success();
work_dir.write_file("file", "bar");
work_dir
.run_jj(["bookmark", "create", "-r@", "bar"])
.success();
work_dir.run_jj(["edit", "foo"]).success();
test_env.add_config(r#"revsets.run = "bar""#);
work_dir
.run_jj([
"--config=revsets.run=\"bar\"",
"run",
"--",
&fake_formatter_path,
"--tee",
"ran.txt",
])
.success();
insta::assert_snapshot!(
work_dir.run_jj(["file", "list", "-r", "foo"]),
@r"
file
[EOF]
"
);
insta::assert_snapshot!(
work_dir.run_jj(["file", "list", "-r", "bar"]),
@r"
file
ran.txt
[EOF]
"
);
work_dir.run_jj(["undo"]).success();
work_dir
.run_jj([
"--config=revsets.run=\"foo\"",
"run",
"--",
&fake_formatter_path,
"--tee",
"ran.txt",
])
.success();
insta::assert_snapshot!(
work_dir.run_jj(["file", "list", "-r", "foo"]),
@r"
file
ran.txt
[EOF]
"
);
insta::assert_snapshot!(
work_dir.run_jj(["file", "list", "-r", "bar"]),
@r"
file
[EOF]
"
);
}
#[test]
fn test_run_failure_rewrites_nothing() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("A.txt", "A");
work_dir.run_jj(&["commit", "-m", "A"]).success();
work_dir.write_file("b.txt", "b");
work_dir.run_jj(&["commit", "-m", "B"]).success();
let log_before = get_log_output(&work_dir);
insta::assert_snapshot!(log_before, @r"
@ kkmpptxzrspxrzommnulwmwkkqwworpl
â—‹ rlvkpnrzqnoowoytxnquwvuryrwnrmlpB
│
â—‹ qpvuntsmwlqtpsluzzsnyyzlmlwvmlnuA
│
â—† zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
[EOF]
");
let cmd = "if [ \"$JJ_CHANGE_ID\" = 'rlvkpnrzqnoowoytxnquwvuryrwnrmlp' ]; then exit 1; fi; \
touch ran.txt";
let output = work_dir.run_jj(&["run", "-r", "..@", "--", "sh", "-c", cmd]);
assert!(!output.status.success(), "expected `jj run` to fail");
assert_eq!(get_log_output(&work_dir), log_before);
}
#[test]
fn test_run_recovers_after_failure() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let fake_formatter = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(fake_formatter.is_file());
let fake_formatter_path = fake_formatter.to_string_lossy().into_owned();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("A.txt", "A");
work_dir.run_jj(&["commit", "-m", "A"]).success();
work_dir.write_file("b.txt", "b");
work_dir.run_jj(&["commit", "-m", "B"]).success();
let first = work_dir.run_jj(&["run", "-r", "..@", "--", &fake_formatter_path, "--fail"]);
assert!(!first.status.success(), "expected first `jj run` to fail");
work_dir
.run_jj(&[
"run",
"-r",
"..@",
"--",
&fake_formatter_path,
"--tee",
"ran.txt",
])
.success();
insta::assert_snapshot!(
work_dir.run_jj(&["file", "list", "-r", "@-"]),
@r"
A.txt
b.txt
ran.txt
[EOF]
"
);
insta::assert_snapshot!(
work_dir.run_jj(&["file", "list", "-r", "@--"]),
@r"
A.txt
ran.txt
[EOF]
"
);
}
#[test]
fn test_run_shell_command() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("A.txt", "A");
work_dir.run_jj(&["commit", "-m", "A"]).success();
work_dir.write_file("b.txt", "b");
work_dir.run_jj(&["commit", "-m", "B"]).success();
work_dir.write_file("c.txt", "test to replace");
work_dir.run_jj(&["commit", "-m", "C"]).success();
let log_template = r#"change_id ++ " " ++ commit_id ++ " " ++ description ++ "\n""#;
insta::assert_snapshot!(
work_dir.run_jj(&["log", "-T", log_template, "-r", "..@"]),
@r"
@ zsuskulnrvyrovkzqrwmxqlsskqntxvp 8d0cb96bac2cfefd56a8691b9301ef44cc94a368
â—‹ kkmpptxzrspxrzommnulwmwkkqwworpl 3406218c99ce8076f3a28434ebda109cbd84de9e C
│
â—‹ rlvkpnrzqnoowoytxnquwvuryrwnrmlp 9453b0f03bbda20fa849b10eb051d1e3eed1ec5d B
│
â—‹ qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu 26d8ff9bba4faa4da6735ced959c57280e49afa7 A
│
~
[EOF]
"
);
let jj_args: &[&str] = if cfg!(windows) {
&["run", "-r", "..@", "--", "cmd", "/c", "echo %JJ_COMMIT_ID%"]
} else {
&[
"run",
"-r",
"..@",
"--",
"bash",
"-c",
r#"echo "$JJ_COMMIT_ID""#,
]
};
let output = work_dir.run_jj(jj_args).success();
let mut lines: Vec<&str> = output.stdout.raw().lines().collect();
lines.sort_unstable();
let sorted_stdout = lines.join("\n");
insta::assert_snapshot!(sorted_stdout, @r"
26d8ff9bba4faa4da6735ced959c57280e49afa7
3406218c99ce8076f3a28434ebda109cbd84de9e
8d0cb96bac2cfefd56a8691b9301ef44cc94a368
9453b0f03bbda20fa849b10eb051d1e3eed1ec5d
");
}
#[test]
fn test_run_sets_workspace_root_env_var() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("seed.txt", "seed");
work_dir.run_jj(&["commit", "-m", "seed"]).success();
let jj_args: &[&str] = if cfg!(windows) {
&[
"run",
"-r",
"@-",
"--",
"cmd",
"/c",
"echo %JJ_WORKSPACE_ROOT%>workspace_root.txt",
]
} else {
&[
"run",
"-r",
"@-",
"--",
"sh",
"-c",
"echo $JJ_WORKSPACE_ROOT > workspace_root.txt",
]
};
work_dir.run_jj(jj_args).success();
let normalize_whitespace = |s: String| {
s.replace("\r\n", "\n")
.lines()
.map(|line| line.trim_end().to_string())
.collect::<Vec<_>>()
.join("\n")
+ "\n"
};
insta::assert_snapshot!(
work_dir
.run_jj(&["file", "show", "-r", "@-", "workspace_root.txt"])
.normalize_stdout_with(normalize_whitespace),
@r"
$TEST_ENV/repo/.jj/run/default/1/working_copy
[EOF]
"
);
}
#[test]
fn test_run_pool_reuses_slot_after_failure() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let fake_formatter = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(fake_formatter.is_file());
let fake_formatter_path = fake_formatter.to_string_lossy().into_owned();
let work_dir = test_env.work_dir("repo");
work_dir.write_file(".gitignore", "cache/\n");
work_dir.write_file("seed.txt", "seed");
work_dir.run_jj(&["commit", "-m", "seed"]).success();
let first = work_dir.run_jj(&[
"run",
"--config",
"run.jobs=1",
"-r",
"@-",
"--",
"sh",
"-c",
&format!(
"mkdir -p cache && echo artifact > cache/artifact.txt && touch stray.txt && \
{fake_formatter_path} --fail"
),
]);
assert!(!first.status.success(), "expected first run to fail");
let slot_state = work_dir.root().join(".jj/run/default/1/state/tree_state");
assert!(
slot_state.exists(),
"tree_state must be persisted even after failure"
);
work_dir
.run_jj(&[
"run",
"--config",
"run.jobs=1",
"-r",
"@-",
"--",
"sh",
"-c",
"if [ -f cache/artifact.txt ]; then cp cache/artifact.txt result.txt; else echo \
MISSING > result.txt; fi",
])
.success();
assert_snapshot!(
work_dir.run_jj(&["file", "show", "-r", "@-", "result.txt"]),@r"
artifact
[EOF]
"
);
assert_snapshot!(
work_dir
.run_jj(&["file", "list", "-r", "@-"]),
@r"
.gitignore
result.txt
seed.txt
[EOF]
"
);
}
#[test]
fn test_run_pool_persists_between_runs() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("seed.txt", "seed");
work_dir.run_jj(&["commit", "-m", "seed"]).success();
work_dir
.run_jj(&[
"run",
"--config",
"run.jobs=1",
"-r",
"@-",
"--",
"touch",
"ran.txt",
])
.success();
let pool_slot = work_dir.root().join(".jj/run/default/1");
assert!(
pool_slot.exists(),
"expected pool slot 1 to persist between runs at {pool_slot:?}",
);
assert!(pool_slot.join("working_copy").exists());
assert!(pool_slot.join("state").exists());
work_dir
.run_jj(&[
"run",
"--config",
"run.jobs=1",
"-r",
"@-",
"--",
"touch",
"ran2.txt",
])
.success();
assert!(pool_slot.join("working_copy").exists());
}
#[test]
fn test_run_pool_size_from_config() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("a.txt", "a");
work_dir.run_jj(&["commit", "-m", "A"]).success();
work_dir.write_file("b.txt", "b");
work_dir.run_jj(&["commit", "-m", "B"]).success();
work_dir.write_file("c.txt", "c");
work_dir.run_jj(&["commit", "-m", "C"]).success();
work_dir
.run_jj(&[
"run",
"--config",
"run.jobs=2",
"-r",
"..@",
"--",
"sh",
"-c",
"touch ran-$JJ_CHANGE_ID.txt",
])
.success();
let pool_dir = work_dir.root().join(".jj/run/default");
assert!(pool_dir.join("1").exists(), "pool/1 should exist");
assert!(pool_dir.join("2").exists(), "pool/2 should exist");
assert!(
!pool_dir.join("3").exists(),
"pool/3 should NOT exist with size=2",
);
assert_snapshot!(work_dir.run_jj(&["file", "list", "-r", "@---"]), @r"
a.txt
ran-qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu.txt
[EOF]
");
assert_snapshot!(work_dir.run_jj(&["file", "list", "-r", "@--"]), @r"
a.txt
b.txt
ran-qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu.txt
ran-rlvkpnrzqnoowoytxnquwvuryrwnrmlp.txt
[EOF]
");
assert_snapshot!(work_dir.run_jj(&["file", "list", "-r", "@-"]), @r"
a.txt
b.txt
c.txt
ran-kkmpptxzrspxrzommnulwmwkkqwworpl.txt
ran-qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu.txt
ran-rlvkpnrzqnoowoytxnquwvuryrwnrmlp.txt
[EOF]
");
}
#[test]
fn test_run_pool_size_from_jobs_flag() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("a.txt", "a");
work_dir.run_jj(&["commit", "-m", "A"]).success();
work_dir.write_file("b.txt", "b");
work_dir.run_jj(&["commit", "-m", "B"]).success();
work_dir.write_file("c.txt", "c");
work_dir.run_jj(&["commit", "-m", "C"]).success();
work_dir
.run_jj(&["run", "--jobs", "2", "-r", "..@", "--", "touch", "ran.txt"])
.success();
let pool_dir = work_dir.root().join(".jj/run/default");
assert!(pool_dir.join("1").exists(), "pool/1 should exist");
assert!(pool_dir.join("2").exists(), "pool/2 should exist");
assert!(
!pool_dir.join("3").exists(),
"pool/3 must NOT exist with --jobs 2",
);
}
#[test]
fn test_run_pool_preserves_untracked_artifacts() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file(".gitignore", "cache/\n");
work_dir.write_file("seed.txt", "seed");
work_dir.run_jj(&["commit", "-m", "seed"]).success();
work_dir
.run_jj(&[
"run",
"--config",
"run.jobs=1",
"-r",
"@-",
"--",
"sh",
"-c",
"mkdir -p cache && echo run1 > cache/marker && touch ran1.txt",
])
.success();
work_dir
.run_jj(&[
"run",
"--config",
"run.jobs=1",
"-r",
"@-",
"--",
"sh",
"-c",
"if [ -f cache/marker ]; then cp cache/marker result.txt; else echo MISSING > \
result.txt; fi",
])
.success();
assert_snapshot!(
work_dir
.run_jj(&["file", "show", "-r", "@-", "result.txt"])
.success()
.stdout,
@r"
run1
[EOF]
",
);
}
#[test]
fn test_run_pool_removes_file_absent_in_next_commit() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("only_in_a.txt", "a");
work_dir.run_jj(&["commit", "-m", "A"]).success();
std::fs::remove_file(work_dir.root().join("only_in_a.txt")).unwrap();
work_dir.run_jj(&["commit", "-m", "B"]).success();
work_dir
.run_jj(&[
"run",
"--config",
"run.jobs=1",
"-r",
"@--::@-",
"--",
"touch",
"ran.txt",
])
.success();
assert_snapshot!(
work_dir
.run_jj(&["file", "list", "-r", "@--"])
.success()
.stdout,
@r"
only_in_a.txt
ran.txt
[EOF]
",
);
assert_snapshot!(
work_dir
.run_jj(&["file", "list", "-r", "@-"])
.success()
.stdout,
@r"
ran.txt
[EOF]
",
);
}
#[test]
fn test_run_pool_removes_previously_ignored_files() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let fake_formatter = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(fake_formatter.is_file());
let fake_formatter_path = fake_formatter.to_string_lossy().into_owned();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("seed.txt", "seed");
work_dir.write_file(".gitignore", "always_ignored.txt\n");
work_dir
.run_jj(&["bookmark", "create", "-r@", "a"])
.success();
work_dir.run_jj(&["commit", "-m", "a"]).success();
work_dir.write_file(".gitignore", "always_ignored.txt\nignored.txt\n");
work_dir
.run_jj(&["bookmark", "create", "-r@", "b"])
.success();
work_dir.run_jj(&["commit", "-m", "b"]).success();
let run_wc = work_dir.root().join(".jj/run/default/1/working_copy");
work_dir
.run_jj(&[
"run",
"-r=b",
"--",
&fake_formatter_path,
"--tee=always_ignored.txt",
"--tee=ignored.txt",
])
.success();
assert_snapshot!(
work_dir.run_jj(&["file", "list", "-r", "b"]),
@r"
.gitignore
seed.txt
[EOF]
"
);
assert!(fs::exists(run_wc.join("always_ignored.txt")).unwrap());
assert!(fs::exists(run_wc.join("ignored.txt")).unwrap());
work_dir
.run_jj(&[
"run",
"-r=a",
"--",
&fake_formatter_path,
"--stdout",
"done",
])
.success();
assert_snapshot!(
work_dir.run_jj(&["file", "list", "-r", "a"]),
@r"
.gitignore
seed.txt
[EOF]
"
);
assert!(fs::exists(run_wc.join("always_ignored.txt")).unwrap());
assert!(!fs::exists(run_wc.join("ignored.txt")).unwrap());
work_dir
.run_jj(&[
"run",
"-r=a",
"--clean",
"--",
&fake_formatter_path,
"--stdout",
"done",
])
.success();
assert_snapshot!(
work_dir.run_jj(&["file", "list", "-r", "a"]),
@r"
.gitignore
seed.txt
[EOF]
"
);
assert!(!fs::exists(run_wc.join("always_ignored.txt")).unwrap());
assert!(!fs::exists(run_wc.join("ignored.txt")).unwrap());
}
#[test]
fn test_run_pool_recovers_from_missing_tree_state() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("seed.txt", "seed");
work_dir.run_jj(&["commit", "-m", "seed"]).success();
work_dir
.run_jj(&["run", "--config", "run.jobs=1", "-r", "@-", "--", "true"])
.success();
let slot = work_dir.root().join(".jj/run/default/1");
std::fs::write(slot.join("working_copy/stale.txt"), "crash leftovers").unwrap();
std::fs::remove_file(slot.join("state/tree_state")).unwrap();
work_dir
.run_jj(&[
"run",
"--config",
"run.jobs=1",
"-r",
"@-",
"--",
"touch",
"ran.txt",
])
.success();
assert_snapshot!(
work_dir.run_jj(&["file", "list", "-r", "@-"]).success().stdout,
@r"
ran.txt
seed.txt
[EOF]
",
);
}
#[test]
fn test_run_pool_failed_command_does_not_poison_slot() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("seed.txt", "seed");
work_dir.run_jj(&["commit", "-m", "seed"]).success();
drop(work_dir.run_jj(&[
"run",
"--config",
"run.jobs=1",
"-r",
"@-",
"--",
"sh",
"-c",
"touch poison.txt; exit 1",
]));
work_dir
.run_jj(&[
"run",
"--config",
"run.jobs=1",
"-r",
"@-",
"--",
"touch",
"ran.txt",
])
.success();
assert_snapshot!(
work_dir.run_jj(&["file", "list", "-r", "@-"]).success().stdout,
@r"
ran.txt
seed.txt
[EOF]
",
);
}
#[test]
fn test_run_restore_descendants_preserves_content() {
let mut test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let fake_formatter = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(fake_formatter.is_file());
let fake_formatter_path = fake_formatter.to_string_lossy().into_owned();
test_env.add_paths_to_normalize(fake_formatter.clone(), "$FAKE_FORMATTER_PATH");
let work_dir = test_env.work_dir("repo");
create_commit_with_files(&work_dir, "a", &[], &[("file", "a\n")]);
create_commit_with_files(&work_dir, "b", &["a"], &[("file", "b\n")]);
create_commit_with_files(&work_dir, "c", &["b"], &[("file", "c\n")]);
let command = if cfg!(windows) {
format!("{fake_formatter_path} --tee ran-%JJ_CHANGE_ID%.txt")
} else {
format!("{fake_formatter_path} --tee ran-$JJ_CHANGE_ID.txt")
};
let args: &[&str] = if cfg!(windows) {
&[
"run",
"-r",
"a::b",
"--restore-descendants",
"--",
"cmd",
"/c",
command.as_str(),
]
} else {
&[
"run",
"-r",
"a::b",
"--restore-descendants",
"--",
"sh",
"-c",
command.as_str(),
]
};
let output = work_dir.run_jj(args).success();
assert_snapshot!(output.stderr, @r"
Rewrote 2 commits
Rebased 1 descendant commits (while preserving their content)
Working copy (@) now at: royxmykx a741a7d3 c | c
Parent commit (@-) : zsuskuln 43c5a714 b | b
[EOF]
");
assert_snapshot!(
work_dir
.run_jj(&["file", "list", "-r", "a"])
.success()
.stdout,
@r"
file
ran-rlvkpnrzqnoowoytxnquwvuryrwnrmlp.txt
[EOF]
"
);
assert_snapshot!(
work_dir
.run_jj(&["file", "list", "-r", "b"])
.success()
.stdout,
@r"
file
ran-zsuskulnrvyrovkzqrwmxqlsskqntxvp.txt
[EOF]
"
);
assert_snapshot!(
work_dir
.run_jj(&["file", "list", "-r", "c"])
.success()
.stdout,
@r"
file
[EOF]
"
);
assert_snapshot!(
work_dir.run_jj(&["diff", "--from=a", "--to=b", "--git"]).success().stdout,
@r"
diff --git a/file b/file
index 7898192261..6178079822 100644
--- a/file
+++ b/file
@@ -1,1 +1,1 @@
-a
+b
diff --git a/ran-rlvkpnrzqnoowoytxnquwvuryrwnrmlp.txt b/ran-rlvkpnrzqnoowoytxnquwvuryrwnrmlp.txt
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/ran-zsuskulnrvyrovkzqrwmxqlsskqntxvp.txt b/ran-zsuskulnrvyrovkzqrwmxqlsskqntxvp.txt
new file mode 100644
index 0000000000..e69de29bb2
[EOF]
"
);
assert_snapshot!(
work_dir.run_jj(&["diff", "--from=b", "--to=c", "--git"]).success().stdout,
@r"
diff --git a/file b/file
index 6178079822..f2ad6c76f0 100644
--- a/file
+++ b/file
@@ -1,1 +1,1 @@
-b
+c
diff --git a/ran-zsuskulnrvyrovkzqrwmxqlsskqntxvp.txt b/ran-zsuskulnrvyrovkzqrwmxqlsskqntxvp.txt
deleted file mode 100644
index e69de29bb2..0000000000
[EOF]
"
);
}
#[test]
fn test_run_failure_shows_output() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let fake_formatter = assert_cmd::cargo::cargo_bin("fake-formatter");
assert!(fake_formatter.is_file());
let fake_formatter_path = fake_formatter.to_string_lossy().into_owned();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("A.txt", "A");
work_dir.run_jj(&["commit", "-m", "A"]).success();
let output = work_dir.run_jj(&[
"run",
"-r",
"@-",
"--",
&fake_formatter_path,
"--stdout",
"hello stdout\n",
"--stderr",
"hello stderr\n",
"--fail",
]);
assert!(!output.status.success());
insta::with_settings!({
filters => [
("exit code", "exit status"), ],
}, {
insta::assert_snapshot!(&output.normalize_stderr_with(|stderr| stderr.replace(&fake_formatter_path.clone(), "fake-formatter")), @r"
hello stdout
[EOF]
------- stderr -------
hello stderr
Error: the command 'fake-formatter --stdout hello stdout
--stderr hello stderr
--fail' failed with exit status: 1 for commit 26d8ff9bba4faa4da6735ced959c57280e49afa7
[EOF]
[exit status: 1]
");
});
}
#[test]
fn test_run_parallel_changes_propagate_to_descendants() {
let test_env = TestEnvironment::default();
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
let work_dir = test_env.work_dir("repo");
work_dir.write_file("a.txt", "a");
work_dir.run_jj(&["commit", "-m", "A"]).success();
work_dir.write_file("b.txt", "b");
work_dir.run_jj(&["commit", "-m", "B"]).success();
work_dir.write_file("c.txt", "c");
work_dir.run_jj(&["commit", "-m", "C"]).success();
work_dir
.run_jj(&[
"run",
"--jobs=3",
"-r=..@",
"--",
"sh",
"-c",
"touch newfile-$JJ_CHANGE_ID.txt",
])
.success();
assert_snapshot!(work_dir.run_jj(&["file", "list", "-r=@---"]).success().stdout,@r"
a.txt
newfile-qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu.txt
[EOF]
");
assert_snapshot!(work_dir.run_jj(&["file", "list", "-r=@--"]).success().stdout,@r"
a.txt
b.txt
newfile-qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu.txt
newfile-rlvkpnrzqnoowoytxnquwvuryrwnrmlp.txt
[EOF]
");
assert_snapshot!(work_dir.run_jj(&["file", "list", "-r=@-"]).success().stdout,@r"
a.txt
b.txt
c.txt
newfile-kkmpptxzrspxrzommnulwmwkkqwworpl.txt
newfile-qpvuntsmwlqtpsluzzsnyyzlmlwvmlnu.txt
newfile-rlvkpnrzqnoowoytxnquwvuryrwnrmlp.txt
[EOF]
");
}
fn get_log_output(work_dir: &TestWorkDir) -> String {
work_dir
.run_jj(&["log", "-T", r#"change_id ++ description ++ "\n""#])
.success()
.stdout
.to_string()
}