def test_comprehensions(): assert ({"x"}, {"y"}) == find_names(ast.parse("(x for x in y)")) assert ({"x"}, {"x"}) == find_names(ast.parse("(x for x in x)")) assert ({"x", "xx"}, {"xxx"}) == find_names(ast.parse("(x for xx in xxx for x in xx)")) assert ({"x", "xx"}, {"xx", "xxx"}) == find_names(ast.parse("(x for x in xx for xx in xxx)"))
def check_find_names(code: str, defined: Set[str], undefined: Set[str], confirm: bool = True) -> None: assert (defined, undefined) == find_names(ast.parse(code)) if not confirm: return exec_locals = {} actually_undefined = undefined - set(dir(__import__("builtins"))) if actually_undefined: # If something is actually undefined, we should raise a NameError when we execute # (if we hit another exception first, we fix the test!) with pytest.raises(NameError) as e: exec(code, exec_locals) assert re.search(r"name '(\w+)' is not", e.value.args[0]).group(1) in actually_undefined else: try: exec(code, exec_locals) except Exception as e: # Unlike above, allow this code to fail, but if it fails, it shouldn't be a NameError! assert not isinstance(e, NameError) exec_locals = set(exec_locals) exec_locals -= {"__builtins__", "__annotations__"} # In general, we over define things, because we don't deal with scopes and such. So just check # a subset relationship holds, we could tighten this check in the future. assert exec_locals <= defined
def test_basic(): assert (set(), set("x")) == find_names(ast.parse("x[:3]")) assert ({"x"}, set()) == find_names(ast.parse("x = 1")) assert ({"x", "y"}, set()) == find_names(ast.parse("x = 1; y = x + 1"))
def test_del(): assert ({"x"}, {"x"}) == find_names(ast.parse("x = 3; del x; x"))
def test_args_bad(): assert ({"f", "x"}, {"x"}) == find_names(ast.parse("f = lambda x: x; x"))
def test_args(): assert ({"f"}, {"x"}) == find_names(ast.parse("f = lambda: x")) assert ({"f", "x"}, set()) == find_names(ast.parse("f = lambda x: x")) assert ({"f", "x"}, {"y"}) == find_names(ast.parse("f = lambda x: y")) assert ({"a", "b", "c", "x", "y", "z"}, set()) == find_names( ast.parse("def f(x, y = 0, *z, a, b = 0, **c): ..."))
def test_weird_assignments(): assert ({"x"}, {"x"}) == find_names(ast.parse("x += 1")) assert ({"x"}, {"x"}) == find_names(ast.parse("for x in x: pass")) assert ({"x", "y"}, {"x", "y"}) == find_names(ast.parse("x, y = x, y")) if sys.version_info >= (3, 8): assert ({"x"}, {"x"}) == find_names(ast.parse("(x := x)"))
def test_builtins(): assert (set(), {"print"}) == find_names(ast.parse("print(5)")) assert ({"print"}, set()) == find_names(ast.parse("print = 5; print(5)"))
def test_loops(): assert ({"x"}, {"y", "print"}) == find_names(ast.parse("for x in y: print(x)")) assert (set(), {"x"}) == find_names(ast.parse("while x: pass"))
def check_find_names(code: str, defined: Set[str], undefined: Set[str]) -> None: assert (defined, undefined) == find_names(ast.parse(code))