2020-05-11 16:20:43 +08:00
|
|
|
from typing import Callable
|
|
|
|
|
2020-04-25 18:38:53 +08:00
|
|
|
import pytest
|
2020-05-11 16:20:43 +08:00
|
|
|
from _pytest.mark.expression import Expression
|
2020-04-25 18:38:53 +08:00
|
|
|
from _pytest.mark.expression import ParseError
|
|
|
|
|
|
|
|
|
2020-05-11 16:20:43 +08:00
|
|
|
def evaluate(input: str, matcher: Callable[[str], bool]) -> bool:
|
|
|
|
return Expression.compile(input).evaluate(matcher)
|
|
|
|
|
|
|
|
|
2020-04-25 18:38:53 +08:00
|
|
|
def test_empty_is_false() -> None:
|
|
|
|
assert not evaluate("", lambda ident: False)
|
|
|
|
assert not evaluate("", lambda ident: True)
|
|
|
|
assert not evaluate(" ", lambda ident: False)
|
|
|
|
assert not evaluate("\t", lambda ident: False)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
("expr", "expected"),
|
|
|
|
(
|
|
|
|
("true", True),
|
|
|
|
("true", True),
|
|
|
|
("false", False),
|
|
|
|
("not true", False),
|
|
|
|
("not false", True),
|
|
|
|
("not not true", True),
|
|
|
|
("not not false", False),
|
|
|
|
("true and true", True),
|
|
|
|
("true and false", False),
|
|
|
|
("false and true", False),
|
|
|
|
("true and true and true", True),
|
|
|
|
("true and true and false", False),
|
|
|
|
("true and true and not true", False),
|
|
|
|
("false or false", False),
|
|
|
|
("false or true", True),
|
|
|
|
("true or true", True),
|
|
|
|
("true or true or false", True),
|
|
|
|
("true and true or false", True),
|
|
|
|
("not true or true", True),
|
|
|
|
("(not true) or true", True),
|
|
|
|
("not (true or true)", False),
|
|
|
|
("true and true or false and false", True),
|
|
|
|
("true and (true or false) and false", False),
|
|
|
|
("true and (true or (not (not false))) and false", False),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
def test_basic(expr: str, expected: bool) -> None:
|
|
|
|
matcher = {"true": True, "false": False}.__getitem__
|
|
|
|
assert evaluate(expr, matcher) is expected
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
("expr", "expected"),
|
|
|
|
(
|
|
|
|
(" true ", True),
|
|
|
|
(" ((((((true)))))) ", True),
|
|
|
|
(" ( ((\t (((true))))) \t \t)", True),
|
|
|
|
("( true and (((false))))", False),
|
|
|
|
("not not not not true", True),
|
|
|
|
("not not not not not true", False),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
def test_syntax_oddeties(expr: str, expected: bool) -> None:
|
|
|
|
matcher = {"true": True, "false": False}.__getitem__
|
|
|
|
assert evaluate(expr, matcher) is expected
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
("expr", "column", "message"),
|
|
|
|
(
|
|
|
|
("(", 2, "expected not OR left parenthesis OR identifier; got end of input"),
|
|
|
|
(" (", 3, "expected not OR left parenthesis OR identifier; got end of input",),
|
|
|
|
(
|
|
|
|
")",
|
|
|
|
1,
|
|
|
|
"expected not OR left parenthesis OR identifier; got right parenthesis",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
") ",
|
|
|
|
1,
|
|
|
|
"expected not OR left parenthesis OR identifier; got right parenthesis",
|
|
|
|
),
|
|
|
|
("not", 4, "expected not OR left parenthesis OR identifier; got end of input",),
|
|
|
|
(
|
|
|
|
"not not",
|
|
|
|
8,
|
|
|
|
"expected not OR left parenthesis OR identifier; got end of input",
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"(not)",
|
|
|
|
5,
|
|
|
|
"expected not OR left parenthesis OR identifier; got right parenthesis",
|
|
|
|
),
|
|
|
|
("and", 1, "expected not OR left parenthesis OR identifier; got and"),
|
|
|
|
(
|
|
|
|
"ident and",
|
|
|
|
10,
|
|
|
|
"expected not OR left parenthesis OR identifier; got end of input",
|
|
|
|
),
|
|
|
|
("ident and or", 11, "expected not OR left parenthesis OR identifier; got or",),
|
|
|
|
("ident ident", 7, "expected end of input; got identifier"),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
def test_syntax_errors(expr: str, column: int, message: str) -> None:
|
|
|
|
with pytest.raises(ParseError) as excinfo:
|
|
|
|
evaluate(expr, lambda ident: True)
|
|
|
|
assert excinfo.value.column == column
|
|
|
|
assert excinfo.value.message == message
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"ident",
|
|
|
|
(
|
|
|
|
".",
|
|
|
|
"...",
|
|
|
|
":::",
|
|
|
|
"a:::c",
|
|
|
|
"a+-b",
|
|
|
|
"אבגד",
|
|
|
|
"aaאבגדcc",
|
|
|
|
"a[bcd]",
|
|
|
|
"1234",
|
|
|
|
"1234abcd",
|
|
|
|
"1234and",
|
|
|
|
"notandor",
|
|
|
|
"not_and_or",
|
|
|
|
"not[and]or",
|
|
|
|
"1234+5678",
|
|
|
|
"123.232",
|
|
|
|
"True",
|
|
|
|
"False",
|
2020-06-05 20:52:08 +08:00
|
|
|
"None",
|
2020-04-25 18:38:53 +08:00
|
|
|
"if",
|
|
|
|
"else",
|
|
|
|
"while",
|
|
|
|
),
|
|
|
|
)
|
|
|
|
def test_valid_idents(ident: str) -> None:
|
|
|
|
assert evaluate(ident, {ident: True}.__getitem__)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"ident",
|
|
|
|
(
|
|
|
|
"/",
|
|
|
|
"\\",
|
|
|
|
"^",
|
|
|
|
"*",
|
|
|
|
"=",
|
|
|
|
"&",
|
|
|
|
"%",
|
|
|
|
"$",
|
|
|
|
"#",
|
|
|
|
"@",
|
|
|
|
"!",
|
|
|
|
"~",
|
|
|
|
"{",
|
|
|
|
"}",
|
|
|
|
'"',
|
|
|
|
"'",
|
|
|
|
"|",
|
|
|
|
";",
|
|
|
|
"←",
|
|
|
|
),
|
|
|
|
)
|
|
|
|
def test_invalid_idents(ident: str) -> None:
|
|
|
|
with pytest.raises(ParseError):
|
|
|
|
evaluate(ident, lambda ident: True)
|