use super::{AttributeSource, CandidateAttributes, parse_text_unset};
use crate::candidate_error::{CandidateError, CandidateFailure};
use crate::candidate_object::{ObjectId, parse_object_id};
use std::collections::{HashMap, HashSet};
use std::ffi::OsString;
use crate::test_support::{REAL_GIT, fixture, git, remove, repository};
use std::path::{Path, PathBuf};
fn asked(names: &[&str]) -> Vec<Vec<u8>> {
return names.iter().map(|name| { return name.as_bytes().to_vec(); }).collect();
}
fn failure_of(result: Result<Vec<Vec<u8>>, CandidateError>) -> CandidateFailure {
return result.expect_err("a malformed reply").failure;
}
#[test]
fn only_an_unset_value_is_reported() {
let reply: &[u8] = b"a\0text\0unset\0b\0text\0set\0c\0text\0unspecified\0d\0text\0auto\0e\0text\0unsetx\0f\0text\0unset\0";
let names: Vec<Vec<u8>> = asked(&["a", "b", "c", "d", "e", "f"]);
let paths: [&[u8]; 6] = [b"a", b"b", b"c", b"d", b"e", b"f"];
assert_eq!(
parse_text_unset(reply, names.as_slice(), &paths).expect("parsed"),
vec![b"a".to_vec(), b"f".to_vec()]
);
assert_eq!(
parse_text_unset(
b"/top/sub/x\0text\0unset\0",
asked(&["/top/sub/x"]).as_slice(),
&[b"sub/x"]
)
.expect("parsed"),
vec![b"sub/x".to_vec()]
);
assert_eq!(
parse_text_unset(b"", &[], &[]).expect("parsed"),
Vec::<Vec<u8>>::new()
);
}
#[test]
fn a_reply_that_does_not_match_the_question_fails() {
let names: Vec<Vec<u8>> = asked(&["a", "b"]);
let paths: [&[u8]; 2] = [b"a", b"b"];
for reply in [
&b"a\0text\0unset\0"[..],
b"a\0text\0unset\0b\0text\0unset\0c\0text\0unset\0",
b"a\0text\0unset\0b\0text\0unset",
b"a\0text\0unset\0c\0text\0unset\0",
b"a\0text\0unset\0b\0eol\0unset\0",
b"b\0text\0unset\0a\0text\0unset\0",
b"",
] {
assert_eq!(
failure_of(parse_text_unset(reply, names.as_slice(), &paths)),
CandidateFailure::ListingMalformed,
"{reply:?}"
);
}
}
#[test]
fn no_path_starts_no_git() {
let attributes: CandidateAttributes = CandidateAttributes::new(
Path::new("/nonexistent/git"),
&[],
&[],
AttributeSource::Index,
);
assert_eq!(
attributes.text_unset(&[]).expect("nothing asked"),
HashSet::<Vec<u8>>::new()
);
}
#[test]
fn a_git_that_cannot_be_started_fails_the_lookup() {
let attributes: CandidateAttributes = CandidateAttributes::new(
Path::new("/nonexistent/git"),
&[],
&[],
AttributeSource::Index,
);
let error: CandidateError = attributes.text_unset(&[b"a"]).expect_err("no Git");
assert_eq!(error.failure, CandidateFailure::GitNotStarted);
assert!(error.message.contains("git rev-parse could not be started"), "{}", error.message);
assert!(!error.message.contains("\"a\""), "{}", error.message);
}
#[test]
fn a_path_without_a_listing_tree_fails_the_lookup() {
let root: PathBuf = fixture("attribute-unlisted");
let repo: PathBuf = repository(root.as_path(), "repo");
let tree: ObjectId = parse_object_id(&[b'a'; 40]).expect("an object name");
let mut trees: HashMap<Vec<u8>, ObjectId> = HashMap::new();
trees.insert(b"known".to_vec(), tree);
let attributes: CandidateAttributes = CandidateAttributes::new(
Path::new(REAL_GIT),
&[OsString::from("-C"), repo.as_os_str().to_os_string()],
&[],
AttributeSource::PerPath(trees),
);
let error: CandidateError = attributes.text_unset(&[b"unknown"]).expect_err("no tree");
assert_eq!(error.failure, CandidateFailure::StaleCandidate);
remove(root.as_path());
}
#[test]
fn a_lookup_from_outside_the_worktree_resolves_top_level_paths() {
let root: PathBuf = fixture("attribute-outside");
let repo: PathBuf = repository(root.as_path(), "repo");
std::fs::write(repo.join(".gitattributes"), b"seed/** -text\n").expect("attributes");
git(repo.as_path(), &["add", ".gitattributes"]);
let overlay: Vec<(OsString, OsString)> = vec![
(OsString::from("GIT_DIR"), repo.join(".git").into_os_string()),
(OsString::from("GIT_WORK_TREE"), repo.as_os_str().to_os_string()),
];
let attributes: CandidateAttributes = CandidateAttributes::new(
Path::new(REAL_GIT),
&[],
overlay.as_slice(),
AttributeSource::Index,
);
let unset: HashSet<Vec<u8>> = attributes
.text_unset(&[b"seed/case", b"plain.txt"])
.expect("resolved");
assert_eq!(unset, HashSet::from([b"seed/case".to_vec()]));
remove(root.as_path());
}