revset: migrate syntactic tests to AST-based

This will hopefully help remove PartialEq from RevsetExpression. Some assertions
are duplicated so that AST->RevsetExpression transformation is covered by tests.
This commit is contained in:
Yuya Nishihara 2024-06-03 11:23:33 +09:00
parent 538701429a
commit 556c7291c1
2 changed files with 724 additions and 527 deletions

View file

@ -2262,54 +2262,6 @@ mod tests {
parse_with_workspace("main@", &other_workspace_id),
Ok(main_wc)
);
assert_eq!(
parse("main@origin"),
Ok(RevsetExpression::remote_symbol(
"main".to_string(),
"origin".to_string()
))
);
// Quoted component in @ expression
assert_eq!(
parse(r#""foo bar"@"#),
Ok(RevsetExpression::working_copy(WorkspaceId::new(
"foo bar".to_string()
)))
);
assert_eq!(
parse(r#""foo bar"@origin"#),
Ok(RevsetExpression::remote_symbol(
"foo bar".to_string(),
"origin".to_string()
))
);
assert_eq!(
parse(r#"main@"foo bar""#),
Ok(RevsetExpression::remote_symbol(
"main".to_string(),
"foo bar".to_string()
))
);
assert_eq!(
parse(r#"'foo bar'@'bar baz'"#),
Ok(RevsetExpression::remote_symbol(
"foo bar".to_string(),
"bar baz".to_string()
))
);
// Quoted "@" is not interpreted as a working copy or remote symbol
assert_eq!(
parse(r#""@""#),
Ok(RevsetExpression::symbol("@".to_string()))
);
assert_eq!(
parse(r#""main@""#),
Ok(RevsetExpression::symbol("main@".to_string()))
);
assert_eq!(
parse(r#""main@origin""#),
Ok(RevsetExpression::symbol("main@origin".to_string()))
);
// "@" in function argument must be quoted
assert_eq!(
parse("author(foo@)"),
@ -2325,15 +2277,6 @@ mod tests {
);
// Parse a single symbol
assert_eq!(parse("foo"), Ok(foo_symbol.clone()));
// Internal '.', '-', and '+' are allowed
assert_eq!(
parse("foo.bar-v1+7"),
Ok(RevsetExpression::symbol("foo.bar-v1+7".to_string()))
);
assert_eq!(
parse("foo.bar-v1+7-"),
Ok(RevsetExpression::symbol("foo.bar-v1+7".to_string()).parents())
);
// Default arguments for *branches() are all ""
assert_eq!(parse("branches()"), parse(r#"branches("")"#));
assert_eq!(parse("remote_branches()"), parse(r#"remote_branches("")"#));
@ -2341,17 +2284,7 @@ mod tests {
parse("remote_branches()"),
parse(r#"remote_branches("", "")"#)
);
// '.' is not allowed at the beginning or end
assert_eq!(parse(".foo"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo."), Err(RevsetParseErrorKind::SyntaxError));
// Multiple '.', '-', '+' are not allowed
assert_eq!(parse("foo.+bar"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo--bar"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo+-bar"), Err(RevsetParseErrorKind::SyntaxError));
// Parse a parenthesized symbol
assert_eq!(parse("(foo)"), Ok(foo_symbol.clone()));
// Parse a quoted symbol
assert_eq!(parse("\"foo\""), Ok(foo_symbol.clone()));
assert_eq!(parse("'foo'"), Ok(foo_symbol.clone()));
// Parse the "parents" operator
assert_eq!(parse("foo-"), Ok(foo_symbol.parents()));
@ -2382,131 +2315,21 @@ mod tests {
);
// Parse the "negate" operator
assert_eq!(parse("~ foo"), Ok(foo_symbol.negated()));
assert_eq!(
parse("~ ~~ foo"),
Ok(foo_symbol.negated().negated().negated())
);
// Parse the "intersection" operator
assert_eq!(parse("foo & bar"), Ok(foo_symbol.intersection(&bar_symbol)));
// Parse the "union" operator
assert_eq!(parse("foo | bar"), Ok(foo_symbol.union(&bar_symbol)));
// Parse the "difference" operator
assert_eq!(parse("foo ~ bar"), Ok(foo_symbol.minus(&bar_symbol)));
// Parentheses are allowed before suffix operators
assert_eq!(parse("(foo)-"), Ok(foo_symbol.parents()));
// Space is allowed around expressions
assert_eq!(parse(" ::foo "), Ok(foo_symbol.ancestors()));
assert_eq!(parse("( ::foo )"), Ok(foo_symbol.ancestors()));
// Space is not allowed around prefix operators
assert_eq!(parse(" :: foo "), Err(RevsetParseErrorKind::SyntaxError));
// Incomplete parse
assert_eq!(parse("foo | -"), Err(RevsetParseErrorKind::SyntaxError));
// Space is allowed around infix operators and function arguments
assert_eq!(
parse_with_workspace(
" description( arg1 ) ~ file( arg1 , arg2 ) ~ visible_heads( ) ",
&main_workspace_id
),
Ok(RevsetExpression::filter(RevsetFilterPredicate::Description(
StringPattern::Substring("arg1".to_string())
))
.minus(&RevsetExpression::filter(RevsetFilterPredicate::File(
FilesetExpression::union_all(vec![
FilesetExpression::prefix_path(RepoPathBuf::from_internal_string("arg1")),
FilesetExpression::prefix_path(RepoPathBuf::from_internal_string("arg2")),
])
)))
.minus(&RevsetExpression::visible_heads()))
);
// Space is allowed around keyword arguments
assert_eq!(
parse("remote_branches( remote = foo )").unwrap(),
parse("remote_branches(remote=foo)").unwrap(),
);
// Trailing comma isn't allowed for empty argument
assert!(parse("branches(,)").is_err());
// Trailing comma is allowed for the last argument
assert!(parse("branches(a,)").is_ok());
assert!(parse("branches(a , )").is_ok());
assert!(parse("branches(,a)").is_err());
assert!(parse("branches(a,,)").is_err());
assert!(parse("branches(a , , )").is_err());
assert!(parse_with_workspace("file(a,b,)", &main_workspace_id).is_ok());
assert!(parse_with_workspace("file(a,,b)", &main_workspace_id).is_err());
assert!(parse("remote_branches(a,remote=b , )").is_ok());
assert!(parse("remote_branches(a,,remote=b)").is_err());
}
#[test]
fn test_parse_revset_with_modifier() {
let all_symbol = RevsetExpression::symbol("all".to_owned());
let foo_symbol = RevsetExpression::symbol("foo".to_owned());
// all: is a program modifier, but all:: isn't
assert_eq!(
parse_with_modifier("all:"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_with_modifier("all:foo"),
Ok((foo_symbol.clone(), Some(RevsetModifier::All)))
);
assert_eq!(
parse_with_modifier("all::"),
Ok((all_symbol.descendants(), None))
);
assert_eq!(
parse_with_modifier("all::foo"),
Ok((all_symbol.dag_range_to(&foo_symbol), None))
);
// all::: could be parsed as all:(::), but rejected for simplicity
assert_eq!(
parse_with_modifier("all:::"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_with_modifier("all:::foo"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_with_modifier("all:(foo)"),
Ok((foo_symbol.clone(), Some(RevsetModifier::All)))
);
assert_eq!(
parse_with_modifier("all:all::foo"),
Ok((
all_symbol.dag_range_to(&foo_symbol),
Some(RevsetModifier::All)
))
);
assert_eq!(
parse_with_modifier("all:all | foo"),
Ok((all_symbol.union(&foo_symbol), Some(RevsetModifier::All)))
);
assert_eq!(
parse_with_modifier("all: ::foo"),
Ok((foo_symbol.ancestors(), Some(RevsetModifier::All)))
);
assert_eq!(
parse_with_modifier(" all: foo"),
Ok((foo_symbol.clone(), Some(RevsetModifier::All)))
);
assert_matches!(
parse_with_modifier("(all:foo)"),
Err(RevsetParseErrorKind::NotInfixOperator { .. })
);
assert_matches!(
parse_with_modifier("all :foo"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_matches!(
parse_with_modifier("all:all:all"),
Err(RevsetParseErrorKind::NotInfixOperator { .. })
);
// Top-level string pattern can't be parsed, which is an error anyway
assert_eq!(
@ -2515,41 +2338,6 @@ mod tests {
);
}
#[test]
fn test_parse_whitespace() {
let ascii_whitespaces: String = ('\x00'..='\x7f')
.filter(char::is_ascii_whitespace)
.collect();
assert_eq!(
parse(&format!("{ascii_whitespaces}all()")).unwrap(),
parse("all()").unwrap(),
);
}
#[test]
fn test_parse_string_literal() {
let branches_expr =
|s: &str| RevsetExpression::branches(StringPattern::Substring(s.to_owned()));
// "\<char>" escapes
assert_eq!(
parse(r#"branches("\t\r\n\"\\\0")"#),
Ok(branches_expr("\t\r\n\"\\\0"))
);
// Invalid "\<char>" escape
assert_eq!(
parse(r#"branches("\y")"#),
Err(RevsetParseErrorKind::SyntaxError)
);
// Single-quoted raw string
assert_eq!(parse(r#"branches('')"#), Ok(branches_expr("")));
assert_eq!(parse(r#"branches('a\n')"#), Ok(branches_expr(r"a\n")));
assert_eq!(parse(r#"branches('\')"#), Ok(branches_expr(r"\")));
assert_eq!(parse(r#"branches('"')"#), Ok(branches_expr(r#"""#)));
}
#[test]
fn test_parse_string_pattern() {
assert_eq!(
@ -2570,24 +2358,6 @@ mod tests {
"foo".to_owned()
)))
);
assert_eq!(
parse(r#"branches("exact:foo")"#),
Ok(RevsetExpression::branches(StringPattern::Substring(
"exact:foo".to_owned()
)))
);
assert_eq!(
parse(r#"branches((exact:"foo"))"#),
Ok(RevsetExpression::branches(StringPattern::Exact(
"foo".to_owned()
)))
);
assert_eq!(
parse(r#"branches(exact:'\')"#),
Ok(RevsetExpression::branches(StringPattern::Exact(
r"\".to_owned()
)))
);
assert_eq!(
parse(r#"branches(bad:"foo")"#),
Err(RevsetParseErrorKind::Expression(
@ -2606,10 +2376,6 @@ mod tests {
"Expected expression of string pattern".to_owned()
))
);
assert_matches!(
parse(r#"branches(exact:("foo"))"#),
Err(RevsetParseErrorKind::NotInfixOperator { .. })
);
// String pattern isn't allowed at top level.
assert_matches!(
@ -2618,132 +2384,15 @@ mod tests {
);
}
#[test]
fn test_parse_revset_alias_symbol_decl() {
let mut aliases_map = RevsetAliasesMap::new();
// Working copy or remote symbol cannot be used as an alias name.
assert!(aliases_map.insert("@", "none()").is_err());
assert!(aliases_map.insert("a@", "none()").is_err());
assert!(aliases_map.insert("a@b", "none()").is_err());
}
#[test]
fn test_parse_revset_alias_formal_parameter() {
let mut aliases_map = RevsetAliasesMap::new();
// Working copy or remote symbol cannot be used as an parameter name.
assert!(aliases_map.insert("f(@)", "none()").is_err());
assert!(aliases_map.insert("f(a@)", "none()").is_err());
assert!(aliases_map.insert("f(a@b)", "none()").is_err());
// Trailing comma isn't allowed for empty parameter
assert!(aliases_map.insert("f(,)", "none()").is_err());
// Trailing comma is allowed for the last parameter
assert!(aliases_map.insert("g(a,)", "none()").is_ok());
assert!(aliases_map.insert("h(a , )", "none()").is_ok());
assert!(aliases_map.insert("i(,a)", "none()").is_err());
assert!(aliases_map.insert("j(a,,)", "none()").is_err());
assert!(aliases_map.insert("k(a , , )", "none()").is_err());
assert!(aliases_map.insert("l(a,b,)", "none()").is_ok());
assert!(aliases_map.insert("m(a,,b)", "none()").is_err());
}
#[test]
fn test_parse_revset_compat_operator() {
assert_eq!(
parse(":foo"),
Err(RevsetParseErrorKind::NotPrefixOperator {
op: ":".to_owned(),
similar_op: "::".to_owned(),
description: "ancestors".to_owned(),
})
);
assert_eq!(
parse("foo^"),
Err(RevsetParseErrorKind::NotPostfixOperator {
op: "^".to_owned(),
similar_op: "-".to_owned(),
description: "parents".to_owned(),
})
);
assert_eq!(
parse("foo + bar"),
Err(RevsetParseErrorKind::NotInfixOperator {
op: "+".to_owned(),
similar_op: "|".to_owned(),
description: "union".to_owned(),
})
);
assert_eq!(
parse("foo - bar"),
Err(RevsetParseErrorKind::NotInfixOperator {
op: "-".to_owned(),
similar_op: "~".to_owned(),
description: "difference".to_owned(),
})
);
}
#[test]
fn test_parse_revset_operator_combinations() {
let foo_symbol = RevsetExpression::symbol("foo".to_string());
// Parse repeated "parents" operator
assert_eq!(
parse("foo---"),
Ok(foo_symbol.parents().parents().parents())
);
// Parse repeated "children" operator
assert_eq!(
parse("foo+++"),
Ok(foo_symbol.children().children().children())
);
// Set operator associativity/precedence
assert_eq!(parse("~x|y").unwrap(), parse("(~x)|y").unwrap());
assert_eq!(parse("x&~y").unwrap(), parse("x&(~y)").unwrap());
assert_eq!(parse("x~~y").unwrap(), parse("x~(~y)").unwrap());
assert_eq!(parse("x~~~y").unwrap(), parse("x~(~(~y))").unwrap());
assert_eq!(parse("~x::y").unwrap(), parse("~(x::y)").unwrap());
assert_eq!(parse("x|y|z").unwrap(), parse("(x|y)|z").unwrap());
assert_eq!(parse("x&y|z").unwrap(), parse("(x&y)|z").unwrap());
assert_eq!(parse("x|y&z").unwrap(), parse("x|(y&z)").unwrap());
assert_eq!(parse("x|y~z").unwrap(), parse("x|(y~z)").unwrap());
assert_eq!(parse("::&..").unwrap(), parse("(::)&(..)").unwrap());
// Parse repeated "ancestors"/"descendants"/"dag range"/"range" operators
assert_eq!(parse("::foo::"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse(":::foo"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("::::foo"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo:::"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo::::"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo:::bar"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo::::bar"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("::foo::bar"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo::bar::"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("::::"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("....foo"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo...."), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo.....bar"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("..foo..bar"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("foo..bar.."), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("...."), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("::.."), Err(RevsetParseErrorKind::SyntaxError));
// Parse combinations of "parents"/"children" operators and the range operators.
// The former bind more strongly.
assert_eq!(parse("foo-+"), Ok(foo_symbol.parents().children()));
assert_eq!(parse("foo-::"), Ok(foo_symbol.parents().descendants()));
assert_eq!(parse("::foo+"), Ok(foo_symbol.children().ancestors()));
assert_eq!(parse("::-"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(parse("..+"), Err(RevsetParseErrorKind::SyntaxError));
}
#[test]
fn test_parse_revset_function() {
let foo_symbol = RevsetExpression::symbol("foo".to_string());
assert_eq!(parse("parents(foo)"), Ok(foo_symbol.parents()));
assert_eq!(parse("parents((foo))"), Ok(foo_symbol.parents()));
assert_eq!(parse("parents(\"foo\")"), Ok(foo_symbol.parents()));
assert_eq!(
parse("ancestors(parents(foo))"),
Ok(foo_symbol.parents().ancestors())
);
assert_eq!(parse("parents(foo"), Err(RevsetParseErrorKind::SyntaxError));
assert_eq!(
parse("parents(foo,foo)"),
Err(RevsetParseErrorKind::InvalidFunctionArguments {
@ -2774,12 +2423,6 @@ mod tests {
"Expected expression of string pattern".to_string()
))
);
assert_eq!(
parse("description((foo))"),
Ok(RevsetExpression::filter(
RevsetFilterPredicate::Description(StringPattern::Substring("foo".to_string()))
))
);
assert_eq!(
parse("description(\"(foo)\")"),
Ok(RevsetExpression::filter(
@ -2876,22 +2519,6 @@ mod tests {
parse_with_aliases("AB|c", [("AB", "a|b")]).unwrap(),
parse("(a|b)|c").unwrap()
);
assert_eq!(
parse_with_aliases("AB::heads(AB)", [("AB", "a|b")]).unwrap(),
parse("(a|b)::heads(a|b)").unwrap()
);
// Not string substitution 'a&b|c', but tree substitution.
assert_eq!(
parse_with_aliases("a&BC", [("BC", "b|c")]).unwrap(),
parse("a&(b|c)").unwrap()
);
// String literal should not be substituted with alias.
assert_eq!(
parse_with_aliases(r#"A|"A"|'A'"#, [("A", "a")]).unwrap(),
parse("a|A|A").unwrap()
);
// Alias can be substituted to string literal.
assert_eq!(
@ -2912,172 +2539,20 @@ mod tests {
parse("author(exact:a)").unwrap()
);
// Part of string pattern cannot be substituted.
assert_eq!(
parse_with_aliases("author(exact:A)", [("A", "a")]).unwrap(),
parse("author(exact:A)").unwrap()
);
// Part of @ symbol cannot be substituted.
assert_eq!(
parse_with_aliases("A@", [("A", "a")]).unwrap(),
parse("A@").unwrap()
);
assert_eq!(
parse_with_aliases("A@b", [("A", "a")]).unwrap(),
parse("A@b").unwrap()
);
assert_eq!(
parse_with_aliases("a@B", [("B", "b")]).unwrap(),
parse("a@B").unwrap()
);
// Modifier cannot be substituted.
assert_eq!(
parse_with_aliases_and_modifier("all:all", [("all", "ALL")]).unwrap(),
parse_with_modifier("all:ALL").unwrap()
);
// Top-level alias can be substituted to modifier expression.
assert_eq!(
parse_with_aliases_and_modifier("A", [("A", "all:a")]),
parse_with_modifier("all:a")
);
// Sub-expression alias cannot be substituted to modifier expression.
assert_eq!(
parse_with_aliases_and_modifier("A-", [("A", "all:a")]),
Err(RevsetParseErrorKind::BadAliasExpansion("A".to_owned()))
);
// Multi-level substitution.
assert_eq!(
parse_with_aliases("A", [("A", "BC"), ("BC", "b|C"), ("C", "c")]).unwrap(),
parse("b|c").unwrap()
);
// Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
assert_eq!(
parse_with_aliases("A", [("A", "A")]),
Err(RevsetParseErrorKind::BadAliasExpansion("A".to_owned()))
);
assert_eq!(
parse_with_aliases("A", [("A", "B"), ("B", "b|C"), ("C", "c|B")]),
Err(RevsetParseErrorKind::BadAliasExpansion("A".to_owned()))
);
// Error in alias definition.
assert_eq!(
parse_with_aliases("A", [("A", "a(")]),
Err(RevsetParseErrorKind::BadAliasExpansion("A".to_owned()))
);
}
#[test]
fn test_expand_function_alias() {
assert_eq!(
parse_with_aliases("F()", [("F( )", "a")]).unwrap(),
parse("a").unwrap()
);
assert_eq!(
parse_with_aliases("F(a)", [("F( x )", "x")]).unwrap(),
parse("a").unwrap()
);
assert_eq!(
parse_with_aliases("F(a, b)", [("F( x, y )", "x|y")]).unwrap(),
parse("a|b").unwrap()
);
// Arguments should be resolved in the current scope.
assert_eq!(
parse_with_aliases("F(a::y,b::x)", [("F(x,y)", "x|y")]).unwrap(),
parse("(a::y)|(b::x)").unwrap()
);
// F(a) -> G(a)&y -> (x|a)&y
assert_eq!(
parse_with_aliases("F(a)", [("F(x)", "G(x)&y"), ("G(y)", "x|y")]).unwrap(),
parse("(x|a)&y").unwrap()
);
// F(G(a)) -> F(x|a) -> G(x|a)&y -> (x|(x|a))&y
assert_eq!(
parse_with_aliases("F(G(a))", [("F(x)", "G(x)&y"), ("G(y)", "x|y")]).unwrap(),
parse("(x|(x|a))&y").unwrap()
);
// Function parameter should precede the symbol alias.
assert_eq!(
parse_with_aliases("F(a)|X", [("F(X)", "X"), ("X", "x")]).unwrap(),
parse("a|x").unwrap()
);
// Function parameter shouldn't be expanded in symbol alias.
assert_eq!(
parse_with_aliases("F(a)", [("F(x)", "x|A"), ("A", "x")]).unwrap(),
parse("a|x").unwrap()
);
// String literal should not be substituted with function parameter.
assert_eq!(
parse_with_aliases("F(a)", [("F(x)", r#"x|"x""#)]).unwrap(),
parse("a|x").unwrap()
);
// Pass string literal as parameter.
assert_eq!(
parse_with_aliases("F(a)", [("F(x)", "author(x)|committer(x)")]).unwrap(),
parse("author(a)|committer(a)").unwrap()
);
// Modifier expression body as parameter.
assert_eq!(
parse_with_aliases_and_modifier("F(a|b)", [("F(x)", "all:x")]).unwrap(),
parse_with_modifier("all:(a|b)").unwrap()
);
// Function and symbol aliases reside in separate namespaces.
assert_eq!(
parse_with_aliases("A()", [("A()", "A"), ("A", "a")]).unwrap(),
parse("a").unwrap()
);
// Invalid number of arguments.
assert_eq!(
parse_with_aliases("F(a)", [("F()", "x")]),
Err(RevsetParseErrorKind::InvalidFunctionArguments {
name: "F".to_owned(),
message: "Expected 0 arguments".to_owned()
})
);
assert_eq!(
parse_with_aliases("F()", [("F(x)", "x")]),
Err(RevsetParseErrorKind::InvalidFunctionArguments {
name: "F".to_owned(),
message: "Expected 1 arguments".to_owned()
})
);
assert_eq!(
parse_with_aliases("F(a,b,c)", [("F(x,y)", "x|y")]),
Err(RevsetParseErrorKind::InvalidFunctionArguments {
name: "F".to_owned(),
message: "Expected 2 arguments".to_owned()
})
);
// Keyword argument isn't supported for now.
assert_eq!(
parse_with_aliases("F(x=y)", [("F(x)", "x")]),
Err(RevsetParseErrorKind::InvalidFunctionArguments {
name: "F".to_owned(),
message: "Unexpected keyword arguments".to_owned()
})
);
// Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
assert_eq!(
parse_with_aliases(
"F(a)",
[("F(x)", "G(x)"), ("G(x)", "H(x)"), ("H(x)", "F(x)")]
),
Err(RevsetParseErrorKind::BadAliasExpansion("F()".to_owned()))
);
}
#[test]

View file

@ -799,7 +799,6 @@ fn expect_literal_with<T>(
}
#[cfg(test)]
#[allow(unused)] // TODO: remove
mod tests {
use assert_matches::assert_matches;
@ -913,4 +912,727 @@ mod tests {
);
assert_ne!(parse_normalized(r#" foo "#), parse_normalized(r#" "foo" "#));
}
#[test]
fn test_parse_revset() {
// Parse "@" (the current working copy)
assert_eq!(parse_into_kind("@"), Ok(ExpressionKind::AtCurrentWorkspace));
assert_eq!(
parse_into_kind("main@"),
Ok(ExpressionKind::AtWorkspace("main".to_owned()))
);
assert_eq!(
parse_into_kind("main@origin"),
Ok(ExpressionKind::RemoteSymbol {
name: "main".to_owned(),
remote: "origin".to_owned()
})
);
// Quoted component in @ expression
assert_eq!(
parse_into_kind(r#""foo bar"@"#),
Ok(ExpressionKind::AtWorkspace("foo bar".to_owned()))
);
assert_eq!(
parse_into_kind(r#""foo bar"@origin"#),
Ok(ExpressionKind::RemoteSymbol {
name: "foo bar".to_owned(),
remote: "origin".to_owned()
})
);
assert_eq!(
parse_into_kind(r#"main@"foo bar""#),
Ok(ExpressionKind::RemoteSymbol {
name: "main".to_owned(),
remote: "foo bar".to_owned()
})
);
assert_eq!(
parse_into_kind(r#"'foo bar'@'bar baz'"#),
Ok(ExpressionKind::RemoteSymbol {
name: "foo bar".to_owned(),
remote: "bar baz".to_owned()
})
);
// Quoted "@" is not interpreted as a working copy or remote symbol
assert_eq!(
parse_into_kind(r#""@""#),
Ok(ExpressionKind::String("@".to_owned()))
);
assert_eq!(
parse_into_kind(r#""main@""#),
Ok(ExpressionKind::String("main@".to_owned()))
);
assert_eq!(
parse_into_kind(r#""main@origin""#),
Ok(ExpressionKind::String("main@origin".to_owned()))
);
// Internal '.', '-', and '+' are allowed
assert_eq!(
parse_into_kind("foo.bar-v1+7"),
Ok(ExpressionKind::Identifier("foo.bar-v1+7"))
);
assert_eq!(
parse_normalized("foo.bar-v1+7-"),
parse_normalized("(foo.bar-v1+7)-")
);
// '.' is not allowed at the beginning or end
assert_eq!(
parse_into_kind(".foo"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("foo."),
Err(RevsetParseErrorKind::SyntaxError)
);
// Multiple '.', '-', '+' are not allowed
assert_eq!(
parse_into_kind("foo.+bar"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("foo--bar"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("foo+-bar"),
Err(RevsetParseErrorKind::SyntaxError)
);
// Parse a parenthesized symbol
assert_eq!(parse_normalized("(foo)"), parse_normalized("foo"));
// Parse a quoted symbol
assert_eq!(
parse_into_kind("\"foo\""),
Ok(ExpressionKind::String("foo".to_owned()))
);
assert_eq!(
parse_into_kind("'foo'"),
Ok(ExpressionKind::String("foo".to_owned()))
);
// Parse the "parents" operator
assert_matches!(
parse_into_kind("foo-"),
Ok(ExpressionKind::Unary(UnaryOp::Parents, _))
);
// Parse the "children" operator
assert_matches!(
parse_into_kind("foo+"),
Ok(ExpressionKind::Unary(UnaryOp::Children, _))
);
// Parse the "ancestors" operator
assert_matches!(
parse_into_kind("::foo"),
Ok(ExpressionKind::Unary(UnaryOp::DagRangePre, _))
);
// Parse the "descendants" operator
assert_matches!(
parse_into_kind("foo::"),
Ok(ExpressionKind::Unary(UnaryOp::DagRangePost, _))
);
// Parse the "dag range" operator
assert_matches!(
parse_into_kind("foo::bar"),
Ok(ExpressionKind::Binary(BinaryOp::DagRange, _, _))
);
// Parse the nullary "dag range" operator
assert_matches!(parse_into_kind("::"), Ok(ExpressionKind::DagRangeAll));
// Parse the "range" prefix operator
assert_matches!(
parse_into_kind("..foo"),
Ok(ExpressionKind::Unary(UnaryOp::RangePre, _))
);
assert_matches!(
parse_into_kind("foo.."),
Ok(ExpressionKind::Unary(UnaryOp::RangePost, _))
);
assert_matches!(
parse_into_kind("foo..bar"),
Ok(ExpressionKind::Binary(BinaryOp::Range, _, _))
);
// Parse the nullary "range" operator
assert_matches!(parse_into_kind(".."), Ok(ExpressionKind::RangeAll));
// Parse the "negate" operator
assert_matches!(
parse_into_kind("~ foo"),
Ok(ExpressionKind::Unary(UnaryOp::Negate, _))
);
assert_eq!(
parse_normalized("~ ~~ foo"),
parse_normalized("~(~(~(foo)))"),
);
// Parse the "intersection" operator
assert_matches!(
parse_into_kind("foo & bar"),
Ok(ExpressionKind::Binary(BinaryOp::Intersection, _, _))
);
// Parse the "union" operator
assert_matches!(
parse_into_kind("foo | bar"),
Ok(ExpressionKind::Binary(BinaryOp::Union, _, _))
);
// Parse the "difference" operator
assert_matches!(
parse_into_kind("foo ~ bar"),
Ok(ExpressionKind::Binary(BinaryOp::Difference, _, _))
);
// Parentheses are allowed before suffix operators
assert_eq!(parse_normalized("(foo)-"), parse_normalized("foo-"));
// Space is allowed around expressions
assert_eq!(parse_normalized(" ::foo "), parse_normalized("::foo"));
assert_eq!(parse_normalized("( ::foo )"), parse_normalized("::foo"));
// Space is not allowed around prefix operators
assert_eq!(
parse_into_kind(" :: foo "),
Err(RevsetParseErrorKind::SyntaxError)
);
// Incomplete parse
assert_eq!(
parse_into_kind("foo | -"),
Err(RevsetParseErrorKind::SyntaxError)
);
// Space is allowed around infix operators and function arguments
assert_eq!(
parse_normalized(
" description( arg1 ) ~ file( arg1 , arg2 ) ~ visible_heads( ) ",
),
parse_normalized("(description(arg1) ~ file(arg1, arg2)) ~ visible_heads()"),
);
// Space is allowed around keyword arguments
assert_eq!(
parse_normalized("remote_branches( remote = foo )"),
parse_normalized("remote_branches(remote=foo)"),
);
// Trailing comma isn't allowed for empty argument
assert!(parse_into_kind("branches(,)").is_err());
// Trailing comma is allowed for the last argument
assert_eq!(
parse_normalized("branches(a,)"),
parse_normalized("branches(a)")
);
assert_eq!(
parse_normalized("branches(a , )"),
parse_normalized("branches(a)")
);
assert!(parse_into_kind("branches(,a)").is_err());
assert!(parse_into_kind("branches(a,,)").is_err());
assert!(parse_into_kind("branches(a , , )").is_err());
assert_eq!(
parse_normalized("file(a,b,)"),
parse_normalized("file(a, b)")
);
assert!(parse_into_kind("file(a,,b)").is_err());
assert_eq!(
parse_normalized("remote_branches(a,remote=b , )"),
parse_normalized("remote_branches(a, remote=b)"),
);
assert!(parse_into_kind("remote_branches(a,,remote=b)").is_err());
}
#[test]
fn test_parse_revset_with_modifier() {
// all: is a program modifier, but all:: isn't
assert_eq!(
parse_into_kind("all:"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_matches!(
parse_into_kind("all:foo"),
Ok(ExpressionKind::Modifier(modifier)) if modifier.name == "all"
);
assert_matches!(
parse_into_kind("all::"),
Ok(ExpressionKind::Unary(UnaryOp::DagRangePost, _))
);
assert_matches!(
parse_into_kind("all::foo"),
Ok(ExpressionKind::Binary(BinaryOp::DagRange, _, _))
);
// all::: could be parsed as all:(::), but rejected for simplicity
assert_eq!(
parse_into_kind("all:::"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("all:::foo"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(parse_normalized("all:(foo)"), parse_normalized("all:foo"));
assert_eq!(
parse_normalized("all:all::foo"),
parse_normalized("all:(all::foo)"),
);
assert_eq!(
parse_normalized("all:all | foo"),
parse_normalized("all:(all | foo)"),
);
assert_eq!(
parse_normalized("all: ::foo"),
parse_normalized("all:(::foo)"),
);
assert_eq!(parse_normalized(" all: foo"), parse_normalized("all:foo"));
assert_eq!(
parse_into_kind("(all:foo)"),
Ok(ExpressionKind::StringPattern {
kind: "all",
value: "foo".to_owned()
})
);
assert_matches!(
parse_into_kind("all :foo"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_normalized("all:all:all"),
parse_normalized("all:(all:all)"),
);
}
#[test]
fn test_parse_whitespace() {
let ascii_whitespaces: String = ('\x00'..='\x7f')
.filter(char::is_ascii_whitespace)
.collect();
assert_eq!(
parse_normalized(&format!("{ascii_whitespaces}all()")),
parse_normalized("all()"),
);
}
#[test]
fn test_parse_string_literal() {
// "\<char>" escapes
assert_eq!(
parse_into_kind(r#" "\t\r\n\"\\\0" "#),
Ok(ExpressionKind::String("\t\r\n\"\\\0".to_owned()))
);
// Invalid "\<char>" escape
assert_eq!(
parse_into_kind(r#" "\y" "#),
Err(RevsetParseErrorKind::SyntaxError)
);
// Single-quoted raw string
assert_eq!(
parse_into_kind(r#" '' "#),
Ok(ExpressionKind::String("".to_owned()))
);
assert_eq!(
parse_into_kind(r#" 'a\n' "#),
Ok(ExpressionKind::String(r"a\n".to_owned()))
);
assert_eq!(
parse_into_kind(r#" '\' "#),
Ok(ExpressionKind::String(r"\".to_owned()))
);
assert_eq!(
parse_into_kind(r#" '"' "#),
Ok(ExpressionKind::String(r#"""#.to_owned()))
);
}
#[test]
fn test_parse_string_pattern() {
assert_eq!(
parse_into_kind(r#"(substring:"foo")"#),
Ok(ExpressionKind::StringPattern {
kind: "substring",
value: "foo".to_owned()
})
);
assert_eq!(
parse_into_kind(r#"("exact:foo")"#),
Ok(ExpressionKind::String("exact:foo".to_owned()))
);
assert_eq!(
parse_normalized(r#"(exact:"foo" )"#),
parse_normalized(r#"(exact:"foo")"#),
);
assert_eq!(
parse_into_kind(r#"(exact:'\')"#),
Ok(ExpressionKind::StringPattern {
kind: "exact",
value: r"\".to_owned()
})
);
assert_matches!(
parse_into_kind(r#"(exact:("foo" ))"#),
Err(RevsetParseErrorKind::NotInfixOperator { .. })
);
}
#[test]
fn test_parse_revset_alias_symbol_decl() {
let mut aliases_map = RevsetAliasesMap::new();
// Working copy or remote symbol cannot be used as an alias name.
assert!(aliases_map.insert("@", "none()").is_err());
assert!(aliases_map.insert("a@", "none()").is_err());
assert!(aliases_map.insert("a@b", "none()").is_err());
}
#[test]
fn test_parse_revset_alias_formal_parameter() {
let mut aliases_map = RevsetAliasesMap::new();
// Working copy or remote symbol cannot be used as an parameter name.
assert!(aliases_map.insert("f(@)", "none()").is_err());
assert!(aliases_map.insert("f(a@)", "none()").is_err());
assert!(aliases_map.insert("f(a@b)", "none()").is_err());
// Trailing comma isn't allowed for empty parameter
assert!(aliases_map.insert("f(,)", "none()").is_err());
// Trailing comma is allowed for the last parameter
assert!(aliases_map.insert("g(a,)", "none()").is_ok());
assert!(aliases_map.insert("h(a , )", "none()").is_ok());
assert!(aliases_map.insert("i(,a)", "none()").is_err());
assert!(aliases_map.insert("j(a,,)", "none()").is_err());
assert!(aliases_map.insert("k(a , , )", "none()").is_err());
assert!(aliases_map.insert("l(a,b,)", "none()").is_ok());
assert!(aliases_map.insert("m(a,,b)", "none()").is_err());
}
#[test]
fn test_parse_revset_compat_operator() {
assert_eq!(
parse_into_kind(":foo"),
Err(RevsetParseErrorKind::NotPrefixOperator {
op: ":".to_owned(),
similar_op: "::".to_owned(),
description: "ancestors".to_owned(),
})
);
assert_eq!(
parse_into_kind("foo^"),
Err(RevsetParseErrorKind::NotPostfixOperator {
op: "^".to_owned(),
similar_op: "-".to_owned(),
description: "parents".to_owned(),
})
);
assert_eq!(
parse_into_kind("foo + bar"),
Err(RevsetParseErrorKind::NotInfixOperator {
op: "+".to_owned(),
similar_op: "|".to_owned(),
description: "union".to_owned(),
})
);
assert_eq!(
parse_into_kind("foo - bar"),
Err(RevsetParseErrorKind::NotInfixOperator {
op: "-".to_owned(),
similar_op: "~".to_owned(),
description: "difference".to_owned(),
})
);
}
#[test]
fn test_parse_revset_operator_combinations() {
// Parse repeated "parents" operator
assert_eq!(parse_normalized("foo---"), parse_normalized("((foo-)-)-"));
// Parse repeated "children" operator
assert_eq!(parse_normalized("foo+++"), parse_normalized("((foo+)+)+"));
// Set operator associativity/precedence
assert_eq!(parse_normalized("~x|y"), parse_normalized("(~x)|y"));
assert_eq!(parse_normalized("x&~y"), parse_normalized("x&(~y)"));
assert_eq!(parse_normalized("x~~y"), parse_normalized("x~(~y)"));
assert_eq!(parse_normalized("x~~~y"), parse_normalized("x~(~(~y))"));
assert_eq!(parse_normalized("~x::y"), parse_normalized("~(x::y)"));
assert_eq!(parse_normalized("x|y|z"), parse_normalized("(x|y)|z"));
assert_eq!(parse_normalized("x&y|z"), parse_normalized("(x&y)|z"));
assert_eq!(parse_normalized("x|y&z"), parse_normalized("x|(y&z)"));
assert_eq!(parse_normalized("x|y~z"), parse_normalized("x|(y~z)"));
assert_eq!(parse_normalized("::&.."), parse_normalized("(::)&(..)"));
// Parse repeated "ancestors"/"descendants"/"dag range"/"range" operators
assert_eq!(
parse_into_kind("::foo::"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind(":::foo"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("::::foo"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("foo:::"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("foo::::"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("foo:::bar"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("foo::::bar"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("::foo::bar"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("foo::bar::"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("::::"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("....foo"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("foo...."),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("foo.....bar"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("..foo..bar"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("foo..bar.."),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("...."),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("::.."),
Err(RevsetParseErrorKind::SyntaxError)
);
// Parse combinations of "parents"/"children" operators and the range operators.
// The former bind more strongly.
assert_eq!(parse_normalized("foo-+"), parse_normalized("(foo-)+"));
assert_eq!(parse_normalized("foo-::"), parse_normalized("(foo-)::"));
assert_eq!(parse_normalized("::foo+"), parse_normalized("::(foo+)"));
assert_eq!(
parse_into_kind("::-"),
Err(RevsetParseErrorKind::SyntaxError)
);
assert_eq!(
parse_into_kind("..+"),
Err(RevsetParseErrorKind::SyntaxError)
);
}
#[test]
fn test_parse_revset_function() {
assert_matches!(
parse_into_kind("parents(foo)"),
Ok(ExpressionKind::FunctionCall(_))
);
assert_eq!(
parse_normalized("parents((foo))"),
parse_normalized("parents(foo)"),
);
assert_eq!(
parse_into_kind("parents(foo"),
Err(RevsetParseErrorKind::SyntaxError)
);
}
#[test]
fn test_expand_symbol_alias() {
assert_eq!(
with_aliases([("AB", "a|b")]).parse_normalized("AB|c"),
parse_normalized("(a|b)|c")
);
assert_eq!(
with_aliases([("AB", "a|b")]).parse_normalized("AB::heads(AB)"),
parse_normalized("(a|b)::heads(a|b)")
);
// Not string substitution 'a&b|c', but tree substitution.
assert_eq!(
with_aliases([("BC", "b|c")]).parse_normalized("a&BC"),
parse_normalized("a&(b|c)")
);
// String literal should not be substituted with alias.
assert_eq!(
with_aliases([("A", "a")]).parse_normalized(r#"A|"A"|'A'"#),
parse_normalized("a|'A'|'A'")
);
// Part of string pattern cannot be substituted.
assert_eq!(
with_aliases([("A", "a")]).parse_normalized("author(exact:A)"),
parse_normalized("author(exact:A)")
);
// Part of @ symbol cannot be substituted.
assert_eq!(
with_aliases([("A", "a")]).parse_normalized("A@"),
parse_normalized("A@")
);
assert_eq!(
with_aliases([("A", "a")]).parse_normalized("A@b"),
parse_normalized("A@b")
);
assert_eq!(
with_aliases([("B", "b")]).parse_normalized("a@B"),
parse_normalized("a@B")
);
// Modifier cannot be substituted.
assert_eq!(
with_aliases([("all", "ALL")]).parse_normalized("all:all"),
parse_normalized("all:ALL")
);
// Top-level alias can be substituted to modifier expression.
assert_eq!(
with_aliases([("A", "all:a")]).parse_normalized("A"),
parse_normalized("all:a")
);
// Multi-level substitution.
assert_eq!(
with_aliases([("A", "BC"), ("BC", "b|C"), ("C", "c")]).parse_normalized("A"),
parse_normalized("b|c")
);
// Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
assert_eq!(
with_aliases([("A", "A")]).parse("A").unwrap_err().kind,
RevsetParseErrorKind::BadAliasExpansion("A".to_owned())
);
assert_eq!(
with_aliases([("A", "B"), ("B", "b|C"), ("C", "c|B")])
.parse("A")
.unwrap_err()
.kind,
RevsetParseErrorKind::BadAliasExpansion("A".to_owned())
);
// Error in alias definition.
assert_eq!(
with_aliases([("A", "a(")]).parse("A").unwrap_err().kind,
RevsetParseErrorKind::BadAliasExpansion("A".to_owned())
);
}
#[test]
fn test_expand_function_alias() {
assert_eq!(
with_aliases([("F( )", "a")]).parse_normalized("F()"),
parse_normalized("a")
);
assert_eq!(
with_aliases([("F( x )", "x")]).parse_normalized("F(a)"),
parse_normalized("a")
);
assert_eq!(
with_aliases([("F( x, y )", "x|y")]).parse_normalized("F(a, b)"),
parse_normalized("a|b")
);
// Arguments should be resolved in the current scope.
assert_eq!(
with_aliases([("F(x,y)", "x|y")]).parse_normalized("F(a::y,b::x)"),
parse_normalized("(a::y)|(b::x)")
);
// F(a) -> G(a)&y -> (x|a)&y
assert_eq!(
with_aliases([("F(x)", "G(x)&y"), ("G(y)", "x|y")]).parse_normalized("F(a)"),
parse_normalized("(x|a)&y")
);
// F(G(a)) -> F(x|a) -> G(x|a)&y -> (x|(x|a))&y
assert_eq!(
with_aliases([("F(x)", "G(x)&y"), ("G(y)", "x|y")]).parse_normalized("F(G(a))"),
parse_normalized("(x|(x|a))&y")
);
// Function parameter should precede the symbol alias.
assert_eq!(
with_aliases([("F(X)", "X"), ("X", "x")]).parse_normalized("F(a)|X"),
parse_normalized("a|x")
);
// Function parameter shouldn't be expanded in symbol alias.
assert_eq!(
with_aliases([("F(x)", "x|A"), ("A", "x")]).parse_normalized("F(a)"),
parse_normalized("a|x")
);
// String literal should not be substituted with function parameter.
assert_eq!(
with_aliases([("F(x)", r#"x|"x""#)]).parse_normalized("F(a)"),
parse_normalized("a|'x'")
);
// Modifier expression body as parameter.
assert_eq!(
with_aliases([("F(x)", "all:x")]).parse_normalized("F(a|b)"),
parse_normalized("all:(a|b)")
);
// Function and symbol aliases reside in separate namespaces.
assert_eq!(
with_aliases([("A()", "A"), ("A", "a")]).parse_normalized("A()"),
parse_normalized("a")
);
// Invalid number of arguments.
assert_eq!(
with_aliases([("F()", "x")]).parse("F(a)").unwrap_err().kind,
RevsetParseErrorKind::InvalidFunctionArguments {
name: "F".to_owned(),
message: "Expected 0 arguments".to_owned()
}
);
assert_eq!(
with_aliases([("F(x)", "x")]).parse("F()").unwrap_err().kind,
RevsetParseErrorKind::InvalidFunctionArguments {
name: "F".to_owned(),
message: "Expected 1 arguments".to_owned()
}
);
assert_eq!(
with_aliases([("F(x,y)", "x|y")])
.parse("F(a,b,c)")
.unwrap_err()
.kind,
RevsetParseErrorKind::InvalidFunctionArguments {
name: "F".to_owned(),
message: "Expected 2 arguments".to_owned()
}
);
// Keyword argument isn't supported for now.
assert_eq!(
with_aliases([("F(x)", "x")])
.parse("F(x=y)")
.unwrap_err()
.kind,
RevsetParseErrorKind::InvalidFunctionArguments {
name: "F".to_owned(),
message: "Unexpected keyword arguments".to_owned()
}
);
// Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
assert_eq!(
with_aliases([("F(x)", "G(x)"), ("G(x)", "H(x)"), ("H(x)", "F(x)")])
.parse("F(a)")
.unwrap_err()
.kind,
RevsetParseErrorKind::BadAliasExpansion("F()".to_owned())
);
}
}