use bstr::ByteSlice; use gix_attributes::search::Outcome; use gix_worktree::stack::state; use crate::stack::probe_case; #[test] fn baseline() -> crate::Result { // Due to the way our setup differs from gits dynamic stack (which involves trying to read files from disk // by path) we can only test one case baseline, so we require multiple platforms (or filesystems) to run this. let case = probe_case()?; let dir = crate::scripted_fixture_read_only("make_attributes_baseline.sh")?; let base = dir.join("basics"); let git_dir = base.join(".git"); let mut buf = Vec::new(); let mut collection = gix_attributes::search::MetadataCollection::default(); let state = gix_worktree::stack::State::for_checkout( false, gix_worktree::validate::path::component::Options { protect_windows: false, protect_ntfs: false, ..Default::default() }, state::Attributes::new( gix_attributes::Search::new_globals([base.join("user.attributes")], &mut buf, &mut collection)?, Some(git_dir.join("info").join("attributes")), gix_worktree::stack::state::attributes::Source::WorktreeThenIdMapping, collection, ), ); let mut cache = gix_worktree::Stack::new(&base, state, case, buf, vec![]); let mut actual = cache.attribute_matches(); let input = std::fs::read(base.join("baseline"))?; for (rela_path, expected) in (baseline::Expectations { lines: input.lines() }) { let entry = cache.at_entry(rela_path, None, &gix_object::find::Never)?; let has_match = entry.matching_attributes(&mut actual); assert_eq!( has_match, !expected.is_empty(), "matches are reported when git reports them, too" ); assert_references(&actual); assert_eq!(actual.iter_selected().count(), 0, "no selection made yet"); let actual: Vec<_> = actual .iter() .filter_map(|m| (!m.assignment.state.is_unspecified()).then_some(m.assignment)) .collect(); assert_eq!(actual, expected, "we have the same matches: {rela_path:?}"); assert_eq!(has_match, !actual.is_empty()); } let mut actual = cache.selected_attribute_matches(["info", "test"]); let input = std::fs::read(base.join("baseline.selected"))?; for (rela_path, expected) in (baseline::Expectations { lines: input.lines() }) { let entry = cache.at_entry(rela_path, None, &gix_object::find::Never)?; let has_match = entry.matching_attributes(&mut actual); assert_eq!( has_match, !expected.is_empty(), "matches are reported when git reports them, too" ); assert_references(&actual); let actual: Vec<_> = actual.iter_selected().map(|m| m.assignment).collect(); assert_eq!(actual, expected, "we have the same matches: {rela_path:?}"); assert_eq!(has_match, !actual.is_empty()); } Ok(()) } fn assert_references(out: &Outcome) { for m in out.iter() { if let Some(source) = m.kind.source_id() { let sm = out .match_by_id(source) .expect("sources are always available in the outcome"); assert_ne!( sm.assignment.name, m.assignment.name, "it's impossible to resolve to ourselves" ); } } } mod baseline { use bstr::{BStr, ByteSlice}; use gix_attributes::{AssignmentRef, StateRef}; pub struct Expectations<'a> { pub lines: bstr::Lines<'a>, } impl<'a> Iterator for Expectations<'a> { type Item = ( &'a BStr, // Names might refer to attributes or macros Vec>, ); fn next(&mut self) -> Option { let path = self.lines.next()?; let mut assignments = Vec::new(); loop { let line = self.lines.next()?; if line.is_empty() { return Some((path.as_bstr(), assignments)); } let mut prev = None; let mut tokens = line.splitn(3, |b| { let is_match = *b == b' ' && prev.take() == Some(b':'); prev = Some(*b); is_match }); if let Some(((_path, attr), info)) = tokens.next().zip(tokens.next()).zip(tokens.next()) { let state = match info { b"set" => StateRef::Set, b"unset" => StateRef::Unset, b"unspecified" => StateRef::Unspecified, _ => StateRef::from_bytes(info), }; let attr = attr.trim_end_with(|b| b == ':'); assignments.push(AssignmentRef { name: gix_attributes::NameRef::try_from(attr.as_bstr()).expect("valid attributes"), state, }); } else { unreachable!("invalid line format: {line:?}", line = line.as_bstr()) } } } } }