170 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Python
		
	
	
	
| from typing import Callable
 | |
| 
 | |
| import pytest
 | |
| from _pytest.mark.expression import Expression
 | |
| from _pytest.mark.expression import ParseError
 | |
| 
 | |
| 
 | |
| def evaluate(input: str, matcher: Callable[[str], bool]) -> bool:
 | |
|     return Expression.compile(input).evaluate(matcher)
 | |
| 
 | |
| 
 | |
| 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",
 | |
|         "None",
 | |
|         "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)
 |