refactor: move embedded tests into dedicated test directory
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
fn working_file_changes(
|
||||
root: &Path,
|
||||
buffers: &[(PathBuf, Arc<str>)],
|
||||
cancelled: &dyn Fn() -> bool,
|
||||
) -> Result<Vec<WorkingFileChange>, String> {
|
||||
WorkingFileReader::default().read(root, buffers, cancelled)
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user