blob: 5266f3d0cbd613e7962e18aef1951f06cd9ca495 [file] [log] [blame]
use std::path::Path;
#[test]
fn directories() -> crate::Result {
baseline::run("directory", true, baseline::directories)
}
#[test]
fn no_pathspecs_match_everything() -> crate::Result {
let mut search = gix_pathspec::Search::from_specs([], None, Path::new(""))?;
assert_eq!(search.patterns().count(), 0, "nothing artificial is added");
let m = search
.pattern_matching_relative_path("hello".into(), None, &mut |_, _, _, _| {
unreachable!("must not be called")
})
.expect("matches");
assert_eq!(m.pattern.prefix_directory(), "", "there is no prefix as none was given");
Ok(())
}
#[test]
fn init_with_exclude() -> crate::Result {
let search = gix_pathspec::Search::from_specs(pathspecs(&["tests/", ":!*.sh"]), None, Path::new(""))?;
assert_eq!(search.patterns().count(), 2, "nothing artificial is added");
assert!(
search.patterns().next().expect("first of two").is_excluded(),
"re-orded so that excluded are first"
);
assert_eq!(search.common_prefix(), "tests");
Ok(())
}
#[test]
fn no_pathspecs_respect_prefix() -> crate::Result {
let mut search = gix_pathspec::Search::from_specs([], Some(Path::new("a")), Path::new(""))?;
assert_eq!(
search.patterns().count(),
1,
"we get an artificial pattern to get the prefix"
);
assert!(
search
.pattern_matching_relative_path("hello".into(), None, &mut |_, _, _, _| unreachable!(
"must not be called"
))
.is_none(),
"not the right prefix"
);
let m = search
.pattern_matching_relative_path("a/b".into(), None, &mut |_, _, _, _| unreachable!("must not be called"))
.expect("match");
assert_eq!(
m.pattern.prefix_directory(),
"a",
"the prefix directory matched verbatim"
);
Ok(())
}
#[test]
fn prefixes_are_always_case_insensitive() -> crate::Result {
let path = gix_testtools::scripted_fixture_read_only("match_baseline_files.sh")?.join("paths");
let items = baseline::parse_paths(path)?;
for (spec, prefix, common_prefix, expected) in [
(":(icase)bar", "FOO", "FOO", &["FOO/BAR", "FOO/bAr", "FOO/bar"] as &[_]),
(":(icase)bar", "F", "F", &[]),
(":(icase)bar", "FO", "FO", &[]),
(":(icase)../bar", "fOo", "", &["BAR", "bAr", "bar"]),
("../bar", "fOo", "bar", &["bar"]),
(" ", "", " ", &[" "]), // whitespace can match verbatim
(" hi*", "", " hi", &[" hi "]), // whitespace can match with globs as well
(":(icase)../bar", "fO", "", &["BAR", "bAr", "bar"]), // prefixes are virtual, and don't have to exist at all.
(
":(icase)../foo/bar",
"FOO",
"",
&[
"FOO/BAR", "FOO/bAr", "FOO/bar", "fOo/BAR", "fOo/bAr", "fOo/bar", "foo/BAR", "foo/bAr", "foo/bar",
],
),
("../foo/bar", "FOO", "foo/bar", &["foo/bar"]),
(
":(icase)../foo/../fOo/bar",
"FOO",
"",
&[
"FOO/BAR", "FOO/bAr", "FOO/bar", "fOo/BAR", "fOo/bAr", "fOo/bar", "foo/BAR", "foo/bAr", "foo/bar",
],
),
("../foo/../fOo/BAR", "FOO", "fOo/BAR", &["fOo/BAR"]),
] {
let mut search = gix_pathspec::Search::from_specs(
gix_pathspec::parse(spec.as_bytes(), Default::default()),
Some(Path::new(prefix)),
Path::new(""),
)?;
assert_eq!(search.common_prefix(), common_prefix, "{spec} {prefix}");
let actual: Vec<_> = items
.iter()
.filter(|relative_path| {
search
.pattern_matching_relative_path(relative_path.as_str().into(), Some(false), &mut |_, _, _, _| false)
.is_some()
})
.collect();
assert_eq!(actual, expected, "{spec} {prefix}");
}
Ok(())
}
#[test]
fn common_prefix() -> crate::Result {
for (specs, prefix, expected) in [
(&["foo/bar", ":(icase)foo/bar"] as &[_], None, ""),
(&["foo/bar", "foo"], None, "foo"),
(&["foo/bar/baz", "foo/bar/"], None, "foo/bar"), // directory trailing slashes are ignored, but that prefix shouldn't care anyway
(&[":(icase)bar", ":(icase)bart"], Some("foo"), "foo"), // only case-sensitive portions count
(&["bar", "bart"], Some("foo"), "foo/bar"), // otherwise everything that matches counts
(&["bar", "bart", "ba"], Some("foo"), "foo/ba"),
] {
let search = gix_pathspec::Search::from_specs(
specs
.iter()
.map(|s| gix_pathspec::parse(s.as_bytes(), Default::default()).expect("valid")),
prefix.map(Path::new),
Path::new(""),
)?;
assert_eq!(search.common_prefix(), expected, "{specs:?} {prefix:?}");
}
Ok(())
}
#[test]
fn files() -> crate::Result {
baseline::run("file", false, baseline::files)
}
fn pathspecs(input: &[&str]) -> Vec<gix_pathspec::Pattern> {
input
.iter()
.map(|pattern| gix_pathspec::parse(pattern.as_bytes(), Default::default()).expect("known to be valid"))
.collect()
}
mod baseline {
use std::path::{Path, PathBuf};
use bstr::{BString, ByteSlice};
pub fn run(
name: &str,
items_are_dirs: bool,
init: impl FnOnce() -> crate::Result<(PathBuf, Vec<String>, Vec<Expected>)>,
) -> crate::Result {
let (root, items, expected) = init()?;
let mut collection = Default::default();
let attrs =
gix_attributes::Search::new_globals(Some(root.join(".gitattributes")), &mut Vec::new(), &mut collection)?;
let tests = expected.len();
for expected in expected {
let mut search = gix_pathspec::Search::from_specs(expected.pathspecs, None, Path::new(""))?;
let actual: Vec<_> = items
.iter()
.filter(|path| {
search
.pattern_matching_relative_path(
path.as_str().into(),
Some(items_are_dirs),
&mut |rela_path, case, is_dir, out| {
out.initialize(&collection);
attrs.pattern_matching_relative_path(rela_path, case, Some(is_dir), out)
},
)
.map_or(false, |m| !m.is_excluded())
})
.cloned()
.collect();
let matches_expectation = actual == expected.matches;
assert_eq!(
matches_expectation,
expected.is_consistent,
"{} - {actual:?} == {:?}",
search.patterns().map(|p| format!("{p}")).collect::<Vec<_>>().join(", "),
expected.matches
);
}
eprintln!("{tests} {name} matches OK");
Ok(())
}
#[derive(Debug)]
pub struct Expected {
pub pathspecs: Vec<gix_pathspec::Pattern>,
pub matches: Vec<String>,
/// If true, this means that the baseline is different from what we get, and that our solution is consistent with the rules.
pub is_consistent: bool,
}
pub fn parse_paths(path: PathBuf) -> std::io::Result<Vec<String>> {
let buf = std::fs::read(path)?;
Ok(buf.lines().map(BString::from).map(|s| s.to_string()).collect())
}
fn parse_blocks(input: &[u8], parse_matches: impl Fn(&[u8]) -> Vec<String>) -> Vec<Expected> {
input
.split(|b| *b == b';')
.filter(|b| !b.is_empty())
.map(move |block| {
let mut lines = block.lines();
let mut is_inconsistent = false;
let pathspecs = lines
.next()
.expect("pathspec")
.split(|b| *b == b'+')
.filter(|spec| {
is_inconsistent = spec.as_bstr() == "git-inconsistency";
!is_inconsistent
})
.filter(|s| !s.trim().is_empty())
.map(|pathspec| gix_pathspec::parse(pathspec.trim(), Default::default()).expect("valid pathspec"))
.collect();
Expected {
pathspecs,
matches: parse_matches(lines.as_bytes()),
is_consistent: !is_inconsistent,
}
})
.collect()
}
mod submodule {
use bstr::ByteSlice;
pub fn matches_from_status(input: &[u8]) -> impl Iterator<Item = (bool, String)> + '_ {
input.lines().map(|line| {
let matches = line[0] == b' ';
assert_eq!(!matches, line[0] == b'-');
let mut tokens = line[1..].split(|b| *b == b' ').skip(1);
let path = tokens.next().expect("path").to_str().expect("valid UTF-8");
(matches, path.to_owned())
})
}
pub fn parse_expected(input: &[u8]) -> Vec<super::Expected> {
super::parse_blocks(input, |block| {
matches_from_status(block)
.filter_map(|(matches, module_path)| matches.then_some(module_path))
.collect()
})
}
}
mod files {
use bstr::{BString, ByteSlice};
pub fn parse_expected(input: &[u8]) -> Vec<super::Expected> {
super::parse_blocks(input, |block| {
block.lines().map(BString::from).map(|s| s.to_string()).collect()
})
}
}
pub fn directories() -> crate::Result<(PathBuf, Vec<String>, Vec<Expected>)> {
let root = gix_testtools::scripted_fixture_read_only("match_baseline_dirs.sh")?.join("parent");
let buf = std::fs::read(root.join("paths"))?;
let items = submodule::matches_from_status(&buf)
.map(|(_matches, path)| path)
.collect();
let expected = submodule::parse_expected(&std::fs::read(root.join("baseline.git"))?);
Ok((root, items, expected))
}
pub fn files() -> crate::Result<(PathBuf, Vec<String>, Vec<Expected>)> {
let root = gix_testtools::scripted_fixture_read_only("match_baseline_files.sh")?;
let items = parse_paths(root.join("paths"))?;
let expected = files::parse_expected(&std::fs::read(root.join("baseline.git"))?);
Ok((root, items, expected))
}
}