use super::*;
use crate::subvolume_test_support::{PlainPlatform, backdate, checkout_with_worktree};
use crate::test_support::{git, remove};
fn two_worktrees(name: &str) -> (PathBuf, PathBuf) {
let (parent, root) = checkout_with_worktree(name);
git(root.as_path(), &["worktree", "add", "--quiet", "--detach", "second"]);
let exclude: PathBuf = root.join(".git/info/exclude");
let mut text: String = std::fs::read_to_string(&exclude).unwrap_or_default();
text.push_str("/second/\n");
std::fs::write(&exclude, text).expect("exclude");
for worktree in ["wt", "second"] {
backdate(root.join(worktree).as_path());
git(root.join(worktree).as_path(), &["update-index", "--refresh"]);
}
return (parent, root);
}
fn run(platform: &dyn Platform, arguments: &ConvertArguments, cwd: &Path) -> (i32, Vec<String>) {
let mut lines: Vec<String> = Vec::new();
let code: i32 = execute(platform, arguments, cwd, &Interrupt::never(), &mut |text: &str| { lines.push(String::from(text)); });
return (code, lines);
}
#[test]
fn all_worktrees_are_listed_then_converted() {
let (parent, root) = two_worktrees("convert-command-all");
let platform: PlainPlatform = PlainPlatform::new();
let dry: ConvertArguments = ConvertArguments { dry_run: true, all: true, ..ConvertArguments::default() };
std::fs::write(root.join(".git/worktrees/second/locked"), b"").expect("lock the second");
let (code, lines) = run(&platform, &dry, root.as_path());
assert_eq!(code, 0, "{lines:#?}");
assert!(lines.iter().any(|line: &String| { return line.trim() == "would convert"; }), "{lines:#?}");
assert!(lines.iter().any(|line: &String| { return line.contains("skipped:") && line.contains("locked"); }), "{lines:#?}");
assert!(!platform.is_subvolume_root(root.join("wt").as_path()).expect("examined"), "a dry run converts nothing");
std::fs::remove_file(root.join(".git/worktrees/second/locked")).expect("unlock");
let real: ConvertArguments = ConvertArguments { all: true, ..ConvertArguments::default() };
let (real_code, real_lines) = run(&platform, &real, root.as_path());
assert_eq!(real_code, 0, "{real_lines:#?}");
assert!(platform.is_subvolume_root(root.join("wt").as_path()).expect("examined"));
assert!(platform.is_subvolume_root(root.join("second").as_path()).expect("examined"));
let (again_code, again_lines) = run(&platform, &real, root.as_path());
assert_eq!(again_code, 0);
assert_eq!(again_lines.iter().filter(|line: &&String| { return line.contains("already a subvolume"); }).count(), 2, "{again_lines:#?}");
remove(parent.as_path());
}
#[test]
fn a_refused_named_worktree_is_status_one_and_a_failure_is_status_two() {
let (parent, root) = two_worktrees("convert-command-named");
let platform: PlainPlatform = PlainPlatform::new();
std::fs::write(root.join(".git/worktrees/wt/index.lock"), b"another git").expect("lock");
let named: ConvertArguments = ConvertArguments { paths: vec![std::ffi::OsString::from("wt")], ..ConvertArguments::default() };
let (code, lines) = run(&platform, &named, root.as_path());
assert_eq!(code, REFUSED_EXIT_CODE, "{lines:#?}");
assert!(lines.iter().any(|line: &String| { return line.contains("index.lock"); }), "{lines:#?}");
let missing: ConvertArguments = ConvertArguments { paths: vec![std::ffi::OsString::from("nowhere")], ..ConvertArguments::default() };
let (missing_code, missing_lines) = run(&platform, &missing, root.as_path());
assert_eq!(missing_code, REFUSED_EXIT_CODE, "{missing_lines:#?}");
std::fs::remove_file(root.join(".git/worktrees/wt/index.lock")).expect("unlock");
let killed: i32 = execute(&platform, &named, root.as_path(), &Interrupt::at("created", 0), &mut |_text: &str| {});
assert_eq!(killed, FAILED_EXIT_CODE);
let (finished_code, finished_lines) = run(&platform, &named, root.as_path());
assert_eq!(finished_code, 0, "{finished_lines:#?}");
remove(parent.as_path());
}
#[test]
fn help_is_printed() {
let action: Action = convert_action(&ConvertArguments { help: true, ..ConvertArguments::default() }, Path::new("."), &[], None);
assert!(matches!(action, Action::Exit { code: 0, ref stdout, .. } if stdout.contains("--abandon")));
}
#[test]
fn the_risk_is_said_once_before_anything_is_converted() {
let (parent, root) = two_worktrees("convert-command-risk");
let platform: PlainPlatform = PlainPlatform::new();
let real: ConvertArguments = ConvertArguments { all: true, ..ConvertArguments::default() };
let (code, lines) = run(&platform, &real, root.as_path());
assert_eq!(code, 0, "{lines:#?}");
assert_eq!(lines.iter().filter(|line: &&String| { return line.as_str() == RISK_NOTICE; }).count(), 1, "said once: {lines:#?}");
assert_eq!(lines.first().map(String::as_str), Some(RISK_NOTICE), "before the first worktree is named");
let dry: ConvertArguments = ConvertArguments { dry_run: true, all: true, ..ConvertArguments::default() };
let (_dry_code, dry_lines) = run(&platform, &dry, root.as_path());
assert!(!dry_lines.iter().any(|line: &String| { return line.as_str() == RISK_NOTICE; }), "a dry run removes nothing");
remove(parent.as_path());
}
#[test]
fn a_command_line_attribute_source_stops_the_content_shortcut() {
let (parent, root) = two_worktrees("convert-command-source");
let platform: PlainPlatform = PlainPlatform::new();
let readme: PathBuf = root.join("wt/README.md");
let bytes: Vec<u8> = std::fs::read(&readme).expect("read");
std::fs::write(&readme, bytes).expect("rewrite");
let named: ConvertArguments = ConvertArguments { dry_run: true, paths: vec![std::ffi::OsString::from("wt")], command_line_attributes: true, ..ConvertArguments::default() };
let (_code, lines) = run(&platform, &named, root.as_path());
assert!(lines.iter().any(|line: &String| { return line.contains("--attr-source") && line.contains("cannot be ruled out"); }), "{lines:#?}");
let without: ConvertArguments = ConvertArguments { command_line_attributes: false, ..named };
let (_plain_code, plain_lines) = run(&platform, &without, root.as_path());
assert!(plain_lines.iter().any(|line: &String| { return line.trim() == "would convert"; }), "{plain_lines:#?}");
remove(parent.as_path());
}