refactor: move embedded tests into dedicated test directory

This commit is contained in:
Valdemar Lorenzen
2026-10-16 16:30:07 +02:00
parent de6a57e67d
commit 2772e6878a
55 changed files with 45752 additions and 45858 deletions
+2 -506
View File
@@ -527,15 +527,6 @@ fn working_conflict_baseline(
})
}
#[cfg(test)]
fn working_file_changes(
root: &Path,
buffers: &[(PathBuf, Arc<str>)],
cancelled: &dyn Fn() -> bool,
) -> Result<Vec<WorkingFileChange>, String> {
WorkingFileReader::default().read(root, buffers, cancelled)
}
fn working_file_changes_cached(
root: &Path,
buffers: &[(PathBuf, Arc<str>)],
@@ -3032,500 +3023,5 @@ fn eof_label(file: Option<&FileData>) -> &'static str {
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
fn fixture_command(root: &Path, program: &str, args: &[&str]) -> String {
let mut command = Command::new(program);
if program == "jj" {
command.args([
"--config=user.name=\"Picker Author\"",
"--config=user.email=\"picker@example.test\"",
"--config=debug.commit-timestamp=\"2001-01-01T00:00:00Z\"",
"--config=debug.operation-timestamp=\"2001-01-01T00:00:00Z\"",
]);
}
let output = command
.args(args)
.current_dir(root)
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.env("GIT_CONFIG_SYSTEM", "/dev/null")
.env("GIT_AUTHOR_DATE", "2001-01-01T00:00:00Z")
.env("GIT_COMMITTER_DATE", "2001-01-01T00:00:00Z")
.env("JJ_CONFIG", "/dev/null")
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout).unwrap().trim().into()
}
fn picker_git_fixture() -> Temporary {
let temp = Temporary::new().unwrap();
fixture_command(&temp.0, "git", &["init", "--quiet", "--template="]);
fixture_command(&temp.0, "git", &["config", "user.name", "Picker Author"]);
fixture_command(
&temp.0,
"git",
&["config", "user.email", "picker@example.test"],
);
temp
}
fn changes_by_path(
root: &Path,
buffers: &[(PathBuf, Arc<str>)],
) -> BTreeMap<PathBuf, WorkingFileChange> {
working_file_changes(root, buffers, &|| false)
.unwrap()
.into_iter()
.map(|change| (change.path.clone(), change))
.collect()
}
#[test]
fn working_git_changes_cover_disk_index_buffers_binary_exact_renames_and_read_only_queries() {
use std::os::unix::{ffi::OsStringExt, fs::PermissionsExt};
let temp = picker_git_fixture();
let root = &temp.0;
for (name, contents) in [
(".gitignore", "ignored*\n"),
("modified", "original\r\n"),
("unchanged", "same\n"),
("removed", "deleted\n"),
("old", "rename content\n"),
("duplicate-one", "duplicate\n"),
("duplicate-two", "duplicate\n"),
("mode", "mode\n"),
("binary", "before\0binary"),
] {
fs::write(root.join(name), contents).unwrap();
}
let non_utf8 = PathBuf::from(OsString::from_vec(b"non-utf8-\xff".to_vec()));
let non_utf8 = match fs::write(root.join(&non_utf8), "before\n") {
Ok(()) => Some(non_utf8),
Err(error) if cfg!(target_os = "macos") && error.raw_os_error() == Some(92) => None,
Err(error) => panic!("cannot create byte filename: {error}"),
};
fixture_command(root, "git", &["add", "--all"]);
fixture_command(root, "git", &["commit", "--quiet", "-m", "Baseline"]);
fs::write(root.join("modified"), "staged\n").unwrap();
fixture_command(root, "git", &["add", "--", "modified"]);
fs::write(root.join("modified"), "unstaged\n").unwrap();
fs::write(root.join("binary"), "after\0binary").unwrap();
if let Some(name) = &non_utf8 {
fs::write(root.join(name), "after\n").unwrap();
}
fs::remove_file(root.join("removed")).unwrap();
let renamed = "-new [file]\n\t\".txt";
fs::rename(root.join("old"), root.join(renamed)).unwrap();
fs::remove_file(root.join("duplicate-one")).unwrap();
fs::remove_file(root.join("duplicate-two")).unwrap();
fs::write(root.join("new-duplicate-one"), "duplicate\n").unwrap();
fs::write(root.join("new-duplicate-two"), "duplicate\n").unwrap();
fs::set_permissions(root.join("mode"), fs::Permissions::from_mode(0o755)).unwrap();
fs::write(root.join("added"), "added\n").unwrap();
fs::write(root.join("ignored"), "ignored\n").unwrap();
fs::write(root.join("ignored-tracked"), "forced\n").unwrap();
fixture_command(root, "git", &["add", "-f", "--", "ignored-tracked"]);
std::os::unix::fs::symlink(root.join("modified"), root.join("link")).unwrap();
let index = fs::read(root.join(".git/index")).unwrap();
let head = fs::read(root.join(".git/HEAD")).unwrap();
let disk = changes_by_path(root, &[]);
for name in ["modified", "binary", "mode"] {
assert_eq!(disk[Path::new(name)].status, FileStatus::Modified);
}
if let Some(name) = &non_utf8 {
assert_eq!(disk[name].status, FileStatus::Modified);
}
assert_eq!(disk[Path::new("removed")].status, FileStatus::Removed);
assert_eq!(disk[Path::new(renamed)].status, FileStatus::Renamed);
assert_eq!(
disk[Path::new(renamed)].old_path.as_deref(),
Some(Path::new("old"))
);
for name in [
"added",
"ignored-tracked",
"new-duplicate-one",
"new-duplicate-two",
] {
assert_eq!(disk[Path::new(name)].status, FileStatus::Added);
}
for name in ["duplicate-one", "duplicate-two"] {
assert_eq!(disk[Path::new(name)].status, FileStatus::Removed);
}
for name in ["unchanged", "old", "ignored", "link"] {
assert!(!disk.contains_key(Path::new(name)), "{name}");
}
let overlays = changes_by_path(
root,
&[
(root.join("modified"), "original\n".into()),
(root.join("unchanged"), "dirty\n".into()),
(root.join("removed"), "deleted\n".into()),
(root.join(renamed), "dirty renamed\n".into()),
(root.join("mode"), "mode\n".into()),
(root.join("ignored-buffer"), "ignored\n".into()),
],
);
assert!(!overlays.contains_key(Path::new("modified")));
assert!(!overlays.contains_key(Path::new("ignored-buffer")));
assert_eq!(
overlays[Path::new("unchanged")].status,
FileStatus::Modified
);
assert_eq!(overlays[Path::new("removed")].status, FileStatus::Removed);
assert_eq!(overlays[Path::new(renamed)].status, FileStatus::Renamed);
assert_eq!(overlays[Path::new("mode")].status, FileStatus::Modified);
let aliases = Temporary::new().unwrap();
let alias = aliases.0.join("checkout");
std::os::unix::fs::symlink(root, &alias).unwrap();
let aliased = changes_by_path(root, &[(alias.join("unchanged"), "dirty\n".into())]);
assert_eq!(aliased[Path::new("unchanged")].status, FileStatus::Modified);
fixture_command(root, "git", &["config", "core.filemode", "0"]);
assert!(
!changes_by_path(root, &[(root.join("mode"), "mode\n".into())])
.contains_key(Path::new("mode"))
);
fixture_command(root, "git", &["config", "core.autocrlf", "1"]);
fs::write(root.join("unchanged"), "same\r\n").unwrap();
assert!(!changes_by_path(root, &[]).contains_key(Path::new("unchanged")));
fixture_command(root, "git", &["config", "--unset", "core.autocrlf"]);
fixture_command(root, "git", &["config", "core.filemode", "true"]);
fs::write(root.join("unchanged"), "same\n").unwrap();
assert_eq!(fs::read(root.join(".git/index")).unwrap(), index);
assert_eq!(fs::read(root.join(".git/HEAD")).unwrap(), head);
assert_eq!(
fs::read_to_string(root.join("modified")).unwrap(),
"unstaged\n"
);
assert!(!root.join("removed").exists());
}
#[test]
fn working_git_subdirectory_cross_boundary_moves_worktrees_and_unsafe_roots() {
let temp = picker_git_fixture();
let root = &temp.0;
fs::create_dir(root.join("sub")).unwrap();
fs::write(root.join("sub/outgoing"), "out\n").unwrap();
fs::write(root.join("incoming"), "in\n").unwrap();
fixture_command(root, "git", &["add", "--all"]);
fixture_command(root, "git", &["commit", "--quiet", "-m", "Baseline"]);
fs::rename(root.join("sub/outgoing"), root.join("outgoing")).unwrap();
fs::rename(root.join("incoming"), root.join("sub/incoming")).unwrap();
let changes = changes_by_path(&root.join("sub"), &[]);
assert_eq!(changes.len(), 2);
assert_eq!(changes[Path::new("outgoing")].status, FileStatus::Removed);
assert_eq!(changes[Path::new("incoming")].status, FileStatus::Added);
assert_eq!(changes[Path::new("incoming")].old_path, None);
std::os::unix::fs::symlink(root.join("sub"), root.join("linked")).unwrap();
assert!(changes_by_path(&root.join("linked"), &[]).is_empty());
assert!(changes_by_path(&root.join(".git"), &[]).is_empty());
let unrelated = Temporary::new().unwrap();
assert!(changes_by_path(&unrelated.0, &[]).is_empty());
let worktree = temp.0.join("worktree");
fixture_command(
root,
"git",
&["worktree", "add", "--quiet", "--detach", "worktree", "HEAD"],
);
fs::write(worktree.join("sub/outgoing"), "worktree edit\n").unwrap();
let changes = changes_by_path(&worktree.join("sub"), &[]);
assert_eq!(changes[Path::new("outgoing")].status, FileStatus::Modified);
}
#[test]
fn working_unborn_git_never_runs_filters_external_diff_textconv_or_hooks() {
let temp = picker_git_fixture();
let root = &temp.0;
fs::write(root.join(".gitignore"), "ignored\n").unwrap();
fs::write(root.join("tracked"), "tracked\n").unwrap();
fs::write(root.join("untracked"), "untracked\n").unwrap();
fs::write(root.join("ignored"), "ignored\n").unwrap();
fixture_command(root, "git", &["add", "--", ".gitignore", "tracked"]);
let changes = changes_by_path(root, &[(root.join("named"), "not saved\n".into())]);
for name in [".gitignore", "tracked", "untracked", "named"] {
assert_eq!(changes[Path::new(name)].status, FileStatus::Added);
}
assert!(!changes.contains_key(Path::new("ignored")));
assert!(!root.join("named").exists());
fixture_command(root, "git", &["commit", "--quiet", "-m", "Baseline"]);
fs::write(
root.join(".gitattributes"),
"tracked filter=evil diff=evil\n",
)
.unwrap();
let evil = "sh -c 'touch executed; cat'";
for key in [
"filter.evil.clean",
"filter.evil.process",
"diff.evil.command",
"diff.evil.textconv",
] {
fixture_command(root, "git", &["config", key, evil]);
}
fixture_command(root, "git", &["config", "filter.evil.required", "true"]);
fs::write(root.join("tracked"), "changed\n").unwrap();
assert_eq!(
changes_by_path(root, &[])[Path::new("tracked")].status,
FileStatus::Modified
);
assert_eq!(
changes_by_path(root, &[(root.join("tracked"), "tracked\n".into())])
.get(Path::new("tracked")),
None
);
assert!(!root.join("executed").exists());
assert!(
working_file_changes(root, &[], &|| true)
.unwrap_err()
.contains("cancelled")
);
assert!(
working_file_changes(
root,
&[(root.join("tracked"), "x".repeat(FILE_LIMIT + 1).into())],
&|| false
)
.unwrap_err()
.contains("16 MiB")
);
}
#[test]
fn working_jj_root_parent_colocation_and_merge_queries_do_not_snapshot() {
for colocate in ["--colocate", "--no-colocate"] {
let temp = Temporary::new().unwrap();
let root = &temp.0;
fixture_command(root, "jj", &["git", "init", colocate, "."]);
fs::write(root.join("file"), "baseline\n").unwrap();
assert_eq!(
changes_by_path(root, &[])[Path::new("file")].status,
FileStatus::Added
);
fs::write(root.join(".gitignore"), "ignored\n").unwrap();
fixture_command(root, "jj", &["describe", "-m", "Baseline"]);
fixture_command(root, "jj", &["new"]);
let operation = fixture_command(
root,
"jj",
&[
"--ignore-working-copy",
"op",
"log",
"--no-graph",
"-n",
"1",
"-T",
"id",
],
);
let parent = fixture_command(
root,
"jj",
&[
"--ignore-working-copy",
"log",
"--no-graph",
"-r",
"@-",
"-T",
"commit_id",
],
);
let store = store_for_jj(root).unwrap();
let head = fs::read(store.join("HEAD")).unwrap();
let index = fs::read(store.join("index")).ok();
fs::write(root.join("file"), "disk\n").unwrap();
fs::write(root.join("new"), "new\n").unwrap();
fs::write(root.join("ignored"), "ignored\n").unwrap();
let changes = changes_by_path(root, &[]);
assert_eq!(changes[Path::new("file")].status, FileStatus::Modified);
assert_eq!(changes[Path::new("new")].status, FileStatus::Added);
assert!(!changes.contains_key(Path::new("ignored")));
assert!(
!changes_by_path(root, &[(root.join("file"), "baseline\n".into())])
.contains_key(Path::new("file"))
);
assert_eq!(fs::read(store.join("HEAD")).unwrap(), head);
assert_eq!(fs::read(store.join("index")).ok(), index);
assert_eq!(
fixture_command(
root,
"jj",
&[
"--ignore-working-copy",
"op",
"log",
"--no-graph",
"-n",
"1",
"-T",
"id"
]
),
operation
);
fixture_command(root, "jj", &["new", "root()"]);
fs::write(root.join("sibling"), "sibling\n").unwrap();
fixture_command(root, "jj", &["describe", "-m", "Sibling"]);
fixture_command(root, "jj", &["new", &parent, "@"]);
assert!(changes_by_path(root, &[]).is_empty());
}
}
#[test]
fn working_jj_conflicted_parent_uses_cli_projection_without_snapshotting() {
for colocate in ["--colocate", "--no-colocate"] {
let temp = Temporary::new().unwrap();
let root = &temp.0;
fixture_command(root, "jj", &["git", "init", colocate, "."]);
fs::write(root.join("conflicted"), "base\n").unwrap();
fixture_command(root, "jj", &["describe", "-m", "Base"]);
let base = fixture_command(
root,
"jj",
&[
"--ignore-working-copy",
"log",
"--no-graph",
"-r",
"@",
"-T",
"commit_id",
],
);
fixture_command(root, "jj", &["new"]);
fs::write(root.join("conflicted"), "left\n").unwrap();
fixture_command(root, "jj", &["describe", "-m", "Left"]);
let left = fixture_command(
root,
"jj",
&[
"--ignore-working-copy",
"log",
"--no-graph",
"-r",
"@",
"-T",
"commit_id",
],
);
fixture_command(root, "jj", &["new", &base]);
fs::write(root.join("conflicted"), "right\n").unwrap();
fixture_command(root, "jj", &["describe", "-m", "Right"]);
let right = fixture_command(
root,
"jj",
&[
"--ignore-working-copy",
"log",
"--no-graph",
"-r",
"@",
"-T",
"commit_id",
],
);
fixture_command(root, "jj", &["new", &left, &right]);
fixture_command(root, "jj", &["new"]);
let projected = fs::read_to_string(root.join("conflicted")).unwrap();
assert!(projected.contains("<<<<<<<"));
let operation = fixture_command(
root,
"jj",
&[
"--ignore-working-copy",
"op",
"log",
"--no-graph",
"-n",
"1",
"-T",
"id",
],
);
let mut reader = WorkingFileReader::default();
assert!(reader.read(root, &[], &|| false).unwrap().is_empty());
fs::write(root.join("conflicted"), "resolved in working file\n").unwrap();
let changes = reader.read(root, &[], &|| false).unwrap();
assert_eq!(changes.len(), 1);
assert_eq!(changes[0].path, Path::new("conflicted"));
assert_eq!(changes[0].status, FileStatus::Modified);
assert!(
reader
.read(
root,
&[(root.join("conflicted"), projected.into())],
&|| false
)
.unwrap()
.is_empty()
);
assert_eq!(
fs::read_to_string(root.join("conflicted")).unwrap(),
"resolved in working file\n"
);
assert_eq!(
fixture_command(
root,
"jj",
&[
"--ignore-working-copy",
"op",
"log",
"--no-graph",
"-n",
"1",
"-T",
"id"
]
),
operation
);
}
}
#[test]
fn cancellation_kills_pipe_holding_children_and_drains_both_streams() {
let temp = Temporary::new().unwrap();
let ready = temp.0.join("ready");
let cancelled = Arc::new(AtomicBool::new(false));
let worker_cancelled = Arc::clone(&cancelled);
let gate = ready.clone();
let (sent, received) = mpsc::channel();
let worker = thread::spawn(move || {
let mut command = Command::new("sh");
command
.args([
"-c",
"sleep 600 & printf ready > \"$1\"; wait",
"vamp-diff-test",
])
.arg(gate);
let result = run(command, vec![], &worker_cancelled).map(|_| ());
sent.send(result).unwrap();
});
// File handshake, not a wall-clock scheduling assumption.
while !ready.exists() {
if let Ok(result) = received.try_recv() {
panic!("Process exited before the child was ready: {result:?}");
}
thread::yield_now();
}
cancelled.store(true, Ordering::Release);
assert!(received.recv().unwrap().unwrap_err().contains("cancelled"));
worker.join().unwrap();
let mut command = Command::new("sh");
command.args(["-c", "i=0; while [ \"$i\" -lt 10000 ]; do printf 'line\\n'; printf 'error\\n' >&2; i=$((i+1)); done"]);
let result = run(command, vec![], &AtomicBool::new(false)).unwrap();
assert_eq!(result.code, Some(0));
assert_eq!(result.stdout.len(), 50_000);
assert_eq!(result.stderr.len(), 60_000);
}
}
#[path = "../tests/unit/repository.rs"]
mod tests;