use rstest::rstest;
use super::{Attributes, Case, Limits, Source, State};
const DEEP_CHAIN: &[u8] = b"[attr]m19 -diff\n[attr]m18 m19\n[attr]m17 m18\n[attr]m16 m17\n\
[attr]m15 m16\n[attr]m14 m15\n[attr]m13 m14\n[attr]m12 m13\n[attr]m11 m12\n[attr]m10 m11\n\
[attr]m9 m10\n[attr]m8 m9\n[attr]m7 m8\n[attr]m6 m7\n[attr]m5 m6\n[attr]m4 m5\n[attr]m3 m4\n\
[attr]m2 m3\n[attr]m1 m2\n[attr]m0 m1\nf m0\n";
fn root(content: &[u8]) -> Attributes {
let mut attributes = Attributes::new(Case::Sensitive, Limits::default());
attributes.add(Source::Directory(b""), content).unwrap();
attributes
}
#[rstest]
#[case::later_assignment_wins(b"f diff -diff\n", b"f", State::Unset)]
#[case::later_assignment_wins_reversed(b"f -diff diff\n", b"f", State::Set)]
#[case::direct_after_macro(b"f binary diff\n", b"f", State::Set)]
#[case::macro_after_direct(b"f diff binary\n", b"f", State::Unset)]
#[case::unset_macro_does_not_expand(b"f -binary\n", b"f", State::Unspecified)]
#[case::unspecify(b"f -diff\nf !diff\n", b"f", State::Unspecified)]
#[case::value(b"f diff=rust\n", b"f", State::Value(b"rust"))]
#[case::value_with_equals(b"f diff=a=b\n", b"f", State::Value(b"a=b"))]
#[case::empty_value(b"f diff=\n", b"f", State::Value(b""))]
#[case::value_after_unset_ignored(b"f -diff=x\n", b"f", State::Unset)]
#[case::later_line_wins(b"*.rs diff=rust\nf.rs -diff\n", b"f.rs", State::Unset)]
#[case::earlier_line_used_when_later_misses(
b"*.rs diff=rust\ng.rs -diff\n",
b"f.rs",
State::Value(b"rust")
)]
#[case::basename_anywhere(b"*.rs -diff\n", b"a/b/c.rs", State::Unset)]
#[case::anchored(b"/top -diff\n", b"a/top", State::Unspecified)]
#[case::directory_pattern_matches_no_file(b"dir/ -diff\n", b"dir/f", State::Unspecified)]
#[case::recursive(b"vendor/** -diff\n", b"vendor/a/b", State::Unset)]
#[case::quoted(b"\"q x\" -diff\n", b"q x", State::Unset)]
#[case::quoted_octal(b"\"oct\\101\" -diff\n", b"octA", State::Unset)]
#[case::quote_then_attribute(b"\"x\"y -diff\n", b"x", State::Unset)]
#[case::unterminated_quote_literal(b"\"un -diff\n", b"\"un", State::Unset)]
#[case::negative_pattern_ignored(b"!neg -diff\n", b"neg", State::Unspecified)]
#[case::escaped_bang(b"\\!esc -diff\n", b"!esc", State::Unset)]
#[case::escaped_hash(b"\\#hash -diff\n", b"#hash", State::Unset)]
#[case::comment(b"# f -diff\n", b"#", State::Unspecified)]
#[case::leading_blanks_and_tab(b" f\t-diff\n", b"f", State::Unset)]
#[case::crlf(b"f -diff\r\n", b"f", State::Unset)]
#[case::invalid_name_drops_line(b"f -diff a@b\n", b"f", State::Unspecified)]
#[case::empty_name_drops_line(b"f -diff -\n", b"f", State::Unspecified)]
#[case::quoted_name_drops_line(b"f \"-diff\"\n", b"f", State::Unspecified)]
#[case::digit_name_allowed(b"f 0bad=1 -diff\n", b"f", State::Unset)]
#[case::user_macro(b"[attr]gen -diff\nf gen\n", b"f", State::Unset)]
#[case::nested_macro(b"[attr]inner -diff\n[attr]outer inner\nf outer\n", b"f", State::Unset)]
#[case::later_unset_macro_blocks_expansion(b"f binary\nf -binary\n", b"f", State::Unspecified)]
#[case::later_unspecified_macro_blocks_expansion(
b"f binary\nf !binary\n",
b"f",
State::Unspecified
)]
#[case::same_line_unset_macro_blocks_expansion(b"f binary -binary\n", b"f", State::Unspecified)]
#[case::deep_macro_chain(DEEP_CHAIN, b"f", State::Unset)]
#[case::bom(b"\xef\xbb\xbff -diff\n", b"f", State::Unset)]
fn root_file_state(#[case] content: &[u8], #[case] path: &[u8], #[case] expected: State<'_>) {
assert_eq!(root(content).get(path, b"diff").unwrap(), expected);
}
#[rstest]
#[case::binary(b"binary", State::Set)]
#[case::merge(b"merge", State::Unset)]
#[case::text(b"text", State::Unset)]
fn binary_macro(#[case] name: &[u8], #[case] expected: State<'_>) {
assert_eq!(root(b"f binary\n").get(b"f", name).unwrap(), expected);
}
#[test]
fn higher_file_unset_macro_blocks_expansion() {
let mut attributes = Attributes::new(Case::Sensitive, Limits::default());
attributes
.add(Source::Directory(b""), b"f binary\n")
.unwrap();
attributes.add(Source::Info, b"f -binary\n").unwrap();
assert_eq!(attributes.get(b"f", b"diff").unwrap(), State::Unspecified);
}
#[rstest]
#[case::deeper_directory(b"sub/f", State::Value(b"deep"))]
#[case::info_overrides(b"info", State::Value(b"info"))]
#[case::root_over_global(b"g", State::Value(b"root"))]
#[case::global_over_system(b"s", State::Value(b"global"))]
fn precedence(#[case] path: &[u8], #[case] expected: State<'_>) {
let mut attributes = Attributes::new(Case::Sensitive, Limits::default());
attributes.add(Source::Info, b"info diff=info\n").unwrap();
attributes
.add(Source::Directory(b"sub"), b"f diff=deep\n")
.unwrap();
attributes
.add(
Source::Directory(b""),
b"f diff=root\ninfo diff=root\ng diff=root\n",
)
.unwrap();
attributes
.add(Source::Global, b"g diff=global\ns diff=global\n")
.unwrap();
attributes.add(Source::System, b"s diff=system\n").unwrap();
assert_eq!(attributes.get(path, b"diff").unwrap(), expected);
}
#[test]
fn nested_macro_definition_ignored() {
let mut attributes = Attributes::new(Case::Sensitive, Limits::default());
attributes
.add(Source::Directory(b"sub"), b"[attr]m -diff\nf m\n")
.unwrap();
assert_eq!(
attributes.get(b"sub/f", b"diff").unwrap(),
State::Unspecified
);
assert_eq!(attributes.get(b"sub/f", b"m").unwrap(), State::Set);
}
#[test]
fn ascii_insensitive() {
let mut attributes = Attributes::new(Case::AsciiInsensitive, Limits::default());
attributes
.add(Source::Directory(b"dir"), b"X -diff\n")
.unwrap();
assert_eq!(attributes.get(b"DIR/x", b"diff").unwrap(), State::Unset);
}
#[test]
fn limit_is_atomic() {
let mut attributes = Attributes::new(Case::Sensitive, Limits::default().with_lines(1));
attributes.add(Source::Info, b"a -diff\n").unwrap();
assert!(
attributes
.add(Source::Global, b"b -diff\nc -diff\n")
.is_err()
);
assert_eq!(attributes.get(b"a", b"diff").unwrap(), State::Unset);
assert_eq!(attributes.get(b"b", b"diff").unwrap(), State::Unspecified);
}
#[test]
fn recursive_macro_terminates() {
let attributes = root(b"[attr]loop loop\nf loop\n");
assert_eq!(attributes.get(b"f", b"diff").unwrap(), State::Unspecified);
}