use super::*;
use crate::action::Action;
use crate::doctor::doctor_action;
use crate::test_support::{fixture, git, git_context, remove, repository};
use std::ffi::OsString;
use std::path::PathBuf;
fn record(scope: &str, origin: &str, key_and_value: &str) -> Vec<u8> {
return format!("{scope}\0{origin}\0{key_and_value}\0").into_bytes();
}
fn doctor_stderr(context: &GitContext, repo: &Path, code: i32) -> String {
let Action::Exit { code: actual, stderr, .. } = doctor_action(context, repo) else {
panic!("doctor must end in an exit action");
};
assert_eq!(actual, code, "{stderr}");
return stderr;
}
fn context_with_global(file: &Path) -> GitContext {
let mut context: GitContext = git_context();
context.overlay.retain(|(name, _)| { return name != "GIT_CONFIG_GLOBAL"; });
context.overlay.push((OsString::from("GIT_CONFIG_GLOBAL"), file.as_os_str().to_os_string()));
return context;
}
fn off_line(state: &str) -> String {
return format!(
"git cli-git doctor: Git's fsmonitor is off by the owner's decision of {DECISION_DATE} ({state}). {FSMONITOR_REASON} See {FSMONITOR_DOCUMENT}.\n"
);
}
fn on_finding(setting: &str, remedy: &str) -> String {
return format!(
"git cli-git doctor: Git's fsmonitor is on, against the owner's decision of {DECISION_DATE} to keep it off: {setting}. {FSMONITOR_REASON} See {FSMONITOR_DOCUMENT}. {remedy}\n"
);
}
#[test]
fn the_listing_keeps_the_fsmonitor_entries_with_scope_origin_and_value() {
let mut listing: Vec<u8> = Vec::new();
listing.extend(record("system", "file:/etc/gitconfig", "core.bare\nfalse"));
listing.extend(record("global", "file:/home/a/.gitconfig", "core.fsmonitor\ntrue"));
listing.extend(record("local", "file:.git/config", "core.fsmonitor"));
listing.extend(record("command", "command line:", "core.fsmonitor\n/hook\nwith newline"));
let entries: Vec<FsmonitorEntry> = parse_fsmonitor_entries(listing.as_slice()).expect("a well-formed listing");
assert_eq!(
entries,
vec![
FsmonitorEntry { scope: "global".into(), origin: "file:/home/a/.gitconfig".into(), value: Some(b"true".to_vec()) },
FsmonitorEntry { scope: "local".into(), origin: "file:.git/config".into(), value: None },
FsmonitorEntry { scope: "command".into(), origin: "command line:".into(), value: Some(b"/hook\nwith newline".to_vec()) },
]
);
assert_eq!(parse_fsmonitor_entries(b"").expect("an empty listing"), Vec::new());
assert_eq!(parse_fsmonitor_entries(b"global\0file:/x"), None, "a listing in another shape is refused whole");
}
#[test]
fn an_unset_key_is_off_and_says_not_set() {
assert_eq!(render_fsmonitor(&[]), off_line("core.fsmonitor is not set"));
}
#[test]
fn the_last_entry_decides_and_false_is_off() {
let entries: Vec<FsmonitorEntry> = vec![
FsmonitorEntry { scope: "global".into(), origin: "file:/home/a/.gitconfig".into(), value: Some(b"true".to_vec()) },
FsmonitorEntry { scope: "local".into(), origin: "file:.git/config".into(), value: Some(b"false".to_vec()) },
];
assert_eq!(render_fsmonitor(entries.as_slice()), off_line("core.fsmonitor is false in file:.git/config, local scope"));
}
#[test]
fn true_in_the_global_file_names_the_file_the_scope_and_the_command() {
let entries: Vec<FsmonitorEntry> =
vec![FsmonitorEntry { scope: "global".into(), origin: "file:/home/a/.gitconfig".into(), value: Some(b"true".to_vec()) }];
assert_eq!(
render_fsmonitor(entries.as_slice()),
on_finding(
"core.fsmonitor is true in file:/home/a/.gitconfig, global scope",
"Turn it off with: git config --global core.fsmonitor false"
)
);
}
#[test]
fn a_hook_path_or_a_valueless_key_is_on_and_each_scope_has_its_command() {
let hook: Vec<FsmonitorEntry> =
vec![FsmonitorEntry { scope: "system".into(), origin: "file:/etc/gitconfig".into(), value: Some(b"/opt/hook".to_vec()) }];
assert_eq!(
render_fsmonitor(hook.as_slice()),
on_finding(
"core.fsmonitor names the hook /opt/hook in file:/etc/gitconfig, system scope",
"Turn it off with: git config --system core.fsmonitor false"
)
);
let bare: Vec<FsmonitorEntry> =
vec![FsmonitorEntry { scope: "worktree".into(), origin: "file:/r/.git/config.worktree".into(), value: None }];
assert_eq!(
render_fsmonitor(bare.as_slice()),
on_finding(
"core.fsmonitor is true (the key has no value) in file:/r/.git/config.worktree, worktree scope",
"Turn it off with: git config --worktree core.fsmonitor false"
)
);
}
#[test]
fn a_command_scope_value_names_no_config_command() {
let entries: Vec<FsmonitorEntry> =
vec![FsmonitorEntry { scope: "command".into(), origin: "command line:".into(), value: Some(b"true".to_vec()) }];
assert_eq!(
render_fsmonitor(entries.as_slice()),
on_finding(
"core.fsmonitor is true in command line:, command scope",
"No configuration file sets it: remove the `-c core.fsmonitor=...` option or the GIT_CONFIG_COUNT or GIT_CONFIG_PARAMETERS environment variable that passes it."
)
);
}
#[test]
fn doctor_says_fsmonitor_is_off_for_a_repository_that_does_not_set_it() {
let root: PathBuf = fixture("doctor-fsmonitor-unset");
let repo: PathBuf = repository(root.as_path(), "repo");
assert_eq!(doctor_stderr(&git_context(), repo.as_path(), 0), off_line("core.fsmonitor is not set"));
remove(root.as_path());
}
#[test]
fn doctor_says_fsmonitor_is_off_when_the_repository_sets_false() {
let root: PathBuf = fixture("doctor-fsmonitor-false");
let repo: PathBuf = repository(root.as_path(), "repo");
git(repo.as_path(), &["config", "core.fsmonitor", "false"]);
assert_eq!(
doctor_stderr(&git_context(), repo.as_path(), 0),
off_line("core.fsmonitor is false in file:.git/config, local scope")
);
remove(root.as_path());
}
#[test]
fn doctor_names_the_repository_file_that_turns_fsmonitor_on() {
let root: PathBuf = fixture("doctor-fsmonitor-local-true");
let repo: PathBuf = repository(root.as_path(), "repo");
git(repo.as_path(), &["config", "core.fsmonitor", "true"]);
assert_eq!(
doctor_stderr(&git_context(), repo.as_path(), 0),
on_finding(
"core.fsmonitor is true in file:.git/config, local scope",
"Turn it off with: git config --local core.fsmonitor false"
)
);
remove(root.as_path());
}
#[test]
fn doctor_names_the_global_file_that_turns_fsmonitor_on_and_never_edits_it() {
let root: PathBuf = fixture("doctor-fsmonitor-global-true");
let repo: PathBuf = repository(root.as_path(), "repo");
let global: PathBuf = root.join("home-gitconfig");
let text: &str = "[core]\n\tfsmonitor = true\n";
std::fs::write(&global, text).expect("global file");
let stderr: String = doctor_stderr(&context_with_global(global.as_path()), repo.as_path(), 0);
assert_eq!(
stderr,
on_finding(
format!("core.fsmonitor is true in file:{}, global scope", global.display()).as_str(),
"Turn it off with: git config --global core.fsmonitor false"
)
);
assert_eq!(std::fs::read_to_string(&global).expect("global file"), text);
remove(root.as_path());
}
#[test]
fn doctor_names_the_global_file_that_holds_false() {
let root: PathBuf = fixture("doctor-fsmonitor-global-false");
let repo: PathBuf = repository(root.as_path(), "repo");
let global: PathBuf = root.join("home-gitconfig");
std::fs::write(&global, "[core]\n\tfsmonitor = false\n").expect("global file");
assert_eq!(
doctor_stderr(&context_with_global(global.as_path()), repo.as_path(), 0),
off_line(format!("core.fsmonitor is false in file:{}, global scope", global.display()).as_str())
);
remove(root.as_path());
}
#[test]
fn a_false_spelling_other_than_false_is_named_as_written() {
for (written, shown) in [("0", "0 (Git reads it as false)"), ("off", "off (Git reads it as false)"), ("", "empty (Git reads it as false)")] {
let entries: Vec<FsmonitorEntry> =
vec![FsmonitorEntry { scope: "global".into(), origin: "file:/home/a/.gitconfig".into(), value: Some(written.as_bytes().to_vec()) }];
assert_eq!(
render_fsmonitor(entries.as_slice()),
off_line(format!("core.fsmonitor is {shown} in file:/home/a/.gitconfig, global scope").as_str())
);
}
}
#[test]
fn a_listing_that_cannot_be_read_is_said_plainly() {
let root: PathBuf = fixture("doctor-fsmonitor-unreadable");
let repo: PathBuf = repository(root.as_path(), "repo");
let mut missing: GitContext = git_context();
missing.real_git = root.join("no-such-git");
let not_started: String = fsmonitor_report(&missing, repo.as_path());
assert!(
not_started.starts_with("git cli-git doctor: could not read Git's fsmonitor setting: git config --list could not start: ")
&& not_started.ends_with('\n'),
"{not_started}"
);
std::fs::write(repo.join(".git/config"), "[core\n").expect("a broken configuration");
let refused: String = fsmonitor_report(&git_context(), repo.as_path());
assert!(
refused.starts_with("git cli-git doctor: could not read Git's fsmonitor setting: git config --list failed: "),
"{refused}"
);
remove(root.as_path());
}