use super::*;
use crate::subvolume_test_support::{PlainPlatform, make_checkout, migrate_arguments, run_command};
use crate::test_support::{fixture, git, remove, repository};
fn exit_parts(action: Action) -> (i32, String, String) {
let Action::Exit { code, stdout, stderr } = action else { panic!("the command ends in an exit action") };
return (code, stdout, stderr);
}
#[test]
fn the_main_checkout_is_found_from_every_starting_point() {
let parent: PathBuf = fixture("command-root-discovery");
let root: PathBuf = make_checkout(parent.as_path(), "checkout");
git(root.as_path(), &["worktree", "add", "--quiet", "--detach", "../linked"]);
std::fs::create_dir_all(parent.join("linked/sub")).expect("subdirectory");
for start in [root.clone(), root.join("src/deep"), parent.join("linked"), parent.join("linked/sub")] {
assert_eq!(main_checkout_root(start.as_path()), Ok(root.clone()), "{}", start.display());
}
remove(parent.as_path());
}
#[test]
fn starts_without_a_main_checkout_are_refused_with_the_reason() {
let parent: PathBuf = fixture("command-root-refused");
let reason: String = main_checkout_root(parent.join("absent").as_path()).expect_err("missing");
assert!(reason.contains("cannot resolve"), "{reason}");
let reason2: String = main_checkout_root(parent.as_path()).expect_err("no repository above");
assert!(reason2.contains("not inside a Git working tree"), "{reason2}");
let seed: PathBuf = repository(parent.as_path(), "seed");
git(parent.as_path(), &["clone", "--quiet", "--bare", seed.to_str().expect("path"), "store.git"]);
git(parent.join("store.git").as_path(), &["worktree", "add", "--quiet", "--detach", "../from-bare"]);
let reason3: String = main_checkout_root(parent.join("from-bare").as_path()).expect_err("bare common directory");
assert!(reason3.contains("is not named .git"), "{reason3}");
remove(parent.as_path());
}
#[test]
fn a_gitfile_that_does_not_lead_to_a_main_checkout_is_refused() {
let parent: PathBuf = fixture("command-gitfile");
std::fs::create_dir(parent.join("not-gitfile")).expect("directory");
std::fs::write(parent.join("not-gitfile/.git"), b"something else\n").expect("file");
let reason: String = main_checkout_root(parent.join("not-gitfile").as_path()).expect_err("no gitdir line");
assert!(reason.contains("not a gitfile"), "{reason}");
std::fs::create_dir_all(parent.join("submodule/admin")).expect("directories");
std::fs::write(parent.join("submodule/.git"), b"gitdir: admin\n").expect("file");
let reason2: String = main_checkout_root(parent.join("submodule").as_path()).expect_err("no commondir");
assert!(reason2.contains("has no commondir"), "{reason2}");
std::fs::create_dir_all(parent.join("dangling/admin")).expect("directories");
std::fs::write(parent.join("dangling/.git"), b"gitdir: admin\n").expect("file");
std::fs::write(parent.join("dangling/admin/commondir"), b"../nowhere\n").expect("commondir");
let reason3: String = main_checkout_root(parent.join("dangling").as_path()).expect_err("common directory missing");
assert!(reason3.contains("cannot resolve the common directory"), "{reason3}");
std::fs::create_dir(parent.join("unreadable")).expect("directory");
std::os::unix::fs::symlink("nowhere", parent.join("unreadable/.git")).expect("dangling link");
let reason4: String = main_checkout_root(parent.join("unreadable").as_path()).expect_err("unreadable gitfile");
assert!(reason4.contains("cannot read"), "{reason4}");
remove(parent.as_path());
}
#[test]
fn the_help_needs_no_repository() {
let parent: PathBuf = fixture("command-help");
let arguments: MigrateArguments = MigrateArguments { help: true, ..migrate_arguments() };
assert_eq!(exit_parts(migrate_action(&arguments, parent.as_path())), (0, String::from(MIGRATE_HELP), String::new()));
remove(parent.as_path());
}
#[test]
fn outside_a_repository_the_command_refuses_with_status_one() {
let parent: PathBuf = fixture("command-outside");
let (code, stdout, stderr) = exit_parts(migrate_action(&migrate_arguments(), parent.as_path()));
assert_eq!((code, stdout.as_str()), (REFUSED_EXIT_CODE, ""));
assert!(stderr.contains("refused:") && stderr.contains("What to do:") && stderr.ends_with("Nothing was changed.\n"), "{stderr}");
remove(parent.as_path());
}
#[test]
fn the_preview_through_the_entry_point_changes_nothing() {
let parent: PathBuf = fixture("command-preview");
let root: PathBuf = make_checkout(parent.as_path(), "checkout");
git(root.as_path(), &["worktree", "add", "--quiet", "--detach", "../linked"]);
let before: Vec<String> = names(parent.as_path());
let arguments: MigrateArguments = MigrateArguments { dry_run: true, ..migrate_arguments() };
let on_btrfs: bool = crate::fs_kind::filesystem_kind(root.as_path()) == Ok(crate::fs_kind::FilesystemKind::Btrfs);
for start in [root.clone(), parent.join("linked")] {
let (code, _stdout, _stderr) = exit_parts(migrate_action(&arguments, start.as_path()));
let already: bool = std::os::unix::fs::MetadataExt::ino(&std::fs::metadata(root.as_path()).expect("metadata")) == 256;
let expected: i32 = if on_btrfs && !already { 0 } else { REFUSED_EXIT_CODE };
assert_eq!(code, expected, "{}", start.display());
}
assert_eq!(names(parent.as_path()), before, "a preview creates nothing beside the checkout");
remove(parent.as_path());
}
fn names(directory: &Path) -> Vec<String> {
let mut found: Vec<String> = std::fs::read_dir(directory).expect("listing").flatten().map(|item| { return item.file_name().to_string_lossy().into_owned(); }).collect();
found.sort();
return found;
}
#[test]
fn every_refusal_is_printed_with_its_remedy_and_one_closing_line() {
let (parent, root) = crate::subvolume_test_support::checkout_with_worktree("command-refusals");
std::fs::write(root.join(".git/MERGE_HEAD"), b"x").expect("marker");
std::fs::write(root.join(".git/CHERRY_PICK_HEAD"), b"x").expect("marker");
let (code, lines) = run_command(&PlainPlatform::new(), &migrate_arguments(), root.as_path(), &crate::subvolume_error::Interrupt::never());
assert_eq!(code, REFUSED_EXIT_CODE);
assert_eq!(lines.iter().filter(|line: &&String| { return line.contains("refused:"); }).count(), 2, "{lines:#?}");
assert_eq!(lines.iter().filter(|line: &&String| { return line.trim_start().starts_with("What to do:"); }).count(), 2);
assert_eq!(lines.last().map(String::as_str), Some("Nothing was changed."));
remove(parent.as_path());
}