| # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html |
| # For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE |
| # Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt |
| |
| """Tests for the astroid builder and rebuilder module.""" |
| |
| import collections |
| import importlib |
| import os |
| import pathlib |
| import py_compile |
| import socket |
| import sys |
| import tempfile |
| import textwrap |
| import unittest |
| import unittest.mock |
| |
| import pytest |
| |
| from astroid import Instance, builder, nodes, test_utils, util |
| from astroid.const import IS_PYPY, PY38, PY39_PLUS, PYPY_7_3_11_PLUS |
| from astroid.exceptions import ( |
| AstroidBuildingError, |
| AstroidSyntaxError, |
| AttributeInferenceError, |
| InferenceError, |
| StatementMissing, |
| ) |
| from astroid.nodes.scoped_nodes import Module |
| |
| from . import resources |
| |
| |
| class FromToLineNoTest(unittest.TestCase): |
| def setUp(self) -> None: |
| self.astroid = resources.build_file("data/format.py") |
| |
| def test_callfunc_lineno(self) -> None: |
| stmts = self.astroid.body |
| # on line 4: |
| # function('aeozrijz\ |
| # earzer', hop) |
| discard = stmts[0] |
| self.assertIsInstance(discard, nodes.Expr) |
| self.assertEqual(discard.fromlineno, 4) |
| self.assertEqual(discard.tolineno, 5) |
| callfunc = discard.value |
| self.assertIsInstance(callfunc, nodes.Call) |
| self.assertEqual(callfunc.fromlineno, 4) |
| self.assertEqual(callfunc.tolineno, 5) |
| name = callfunc.func |
| self.assertIsInstance(name, nodes.Name) |
| self.assertEqual(name.fromlineno, 4) |
| self.assertEqual(name.tolineno, 4) |
| strarg = callfunc.args[0] |
| self.assertIsInstance(strarg, nodes.Const) |
| if IS_PYPY: |
| self.assertEqual(strarg.fromlineno, 4) |
| if not PY39_PLUS: |
| self.assertEqual(strarg.tolineno, 4) |
| else: |
| self.assertEqual(strarg.tolineno, 5) |
| else: |
| self.assertEqual(strarg.fromlineno, 4) |
| self.assertEqual(strarg.tolineno, 5) |
| namearg = callfunc.args[1] |
| self.assertIsInstance(namearg, nodes.Name) |
| self.assertEqual(namearg.fromlineno, 5) |
| self.assertEqual(namearg.tolineno, 5) |
| # on line 10: |
| # fonction(1, |
| # 2, |
| # 3, |
| # 4) |
| discard = stmts[2] |
| self.assertIsInstance(discard, nodes.Expr) |
| self.assertEqual(discard.fromlineno, 10) |
| self.assertEqual(discard.tolineno, 13) |
| callfunc = discard.value |
| self.assertIsInstance(callfunc, nodes.Call) |
| self.assertEqual(callfunc.fromlineno, 10) |
| self.assertEqual(callfunc.tolineno, 13) |
| name = callfunc.func |
| self.assertIsInstance(name, nodes.Name) |
| self.assertEqual(name.fromlineno, 10) |
| self.assertEqual(name.tolineno, 10) |
| for i, arg in enumerate(callfunc.args): |
| self.assertIsInstance(arg, nodes.Const) |
| self.assertEqual(arg.fromlineno, 10 + i) |
| self.assertEqual(arg.tolineno, 10 + i) |
| |
| def test_function_lineno(self) -> None: |
| stmts = self.astroid.body |
| # on line 15: |
| # def definition(a, |
| # b, |
| # c): |
| # return a + b + c |
| function = stmts[3] |
| self.assertIsInstance(function, nodes.FunctionDef) |
| self.assertEqual(function.fromlineno, 15) |
| self.assertEqual(function.tolineno, 18) |
| return_ = function.body[0] |
| self.assertIsInstance(return_, nodes.Return) |
| self.assertEqual(return_.fromlineno, 18) |
| self.assertEqual(return_.tolineno, 18) |
| |
| def test_decorated_function_lineno(self) -> None: |
| astroid = builder.parse( |
| """ |
| @decorator |
| def function( |
| arg): |
| print (arg) |
| """, |
| __name__, |
| ) |
| function = astroid["function"] |
| # XXX discussable, but that's what is expected by pylint right now, similar to ClassDef |
| self.assertEqual(function.fromlineno, 3) |
| self.assertEqual(function.tolineno, 5) |
| self.assertEqual(function.decorators.fromlineno, 2) |
| self.assertEqual(function.decorators.tolineno, 2) |
| |
| @staticmethod |
| def test_decorated_class_lineno() -> None: |
| code = textwrap.dedent( |
| """ |
| class A: # L2 |
| ... |
| |
| @decorator |
| class B: # L6 |
| ... |
| |
| @deco1 |
| @deco2( |
| var=42 |
| ) |
| class C: # L13 |
| ... |
| """ |
| ) |
| |
| ast_module: nodes.Module = builder.parse(code) # type: ignore[assignment] |
| |
| a = ast_module.body[0] |
| assert isinstance(a, nodes.ClassDef) |
| assert a.fromlineno == 2 |
| assert a.tolineno == 3 |
| |
| b = ast_module.body[1] |
| assert isinstance(b, nodes.ClassDef) |
| assert b.fromlineno == 6 |
| assert b.tolineno == 7 |
| |
| c = ast_module.body[2] |
| assert isinstance(c, nodes.ClassDef) |
| if IS_PYPY and PY38 and not PYPY_7_3_11_PLUS: |
| # Not perfect, but best we can do for PyPy 3.8 (< v7.3.11). |
| # Can't detect closing bracket on new line. |
| assert c.fromlineno == 12 |
| else: |
| assert c.fromlineno == 13 |
| assert c.tolineno == 14 |
| |
| @staticmethod |
| def test_class_with_docstring() -> None: |
| """Test class nodes which only have docstrings.""" |
| code = textwrap.dedent( |
| '''\ |
| class A: |
| """My docstring""" |
| var = 1 |
| |
| class B: |
| """My docstring""" |
| |
| class C: |
| """My docstring |
| is long.""" |
| |
| class D: |
| """My docstring |
| is long. |
| """ |
| |
| class E: |
| ... |
| ''' |
| ) |
| |
| ast_module = builder.parse(code) |
| |
| a = ast_module.body[0] |
| assert isinstance(a, nodes.ClassDef) |
| assert a.fromlineno == 1 |
| assert a.tolineno == 3 |
| |
| b = ast_module.body[1] |
| assert isinstance(b, nodes.ClassDef) |
| assert b.fromlineno == 5 |
| assert b.tolineno == 6 |
| |
| c = ast_module.body[2] |
| assert isinstance(c, nodes.ClassDef) |
| assert c.fromlineno == 8 |
| assert c.tolineno == 10 |
| |
| d = ast_module.body[3] |
| assert isinstance(d, nodes.ClassDef) |
| assert d.fromlineno == 12 |
| assert d.tolineno == 15 |
| |
| e = ast_module.body[4] |
| assert isinstance(d, nodes.ClassDef) |
| assert e.fromlineno == 17 |
| assert e.tolineno == 18 |
| |
| @staticmethod |
| def test_function_with_docstring() -> None: |
| """Test function defintions with only docstrings.""" |
| code = textwrap.dedent( |
| '''\ |
| def a(): |
| """My docstring""" |
| var = 1 |
| |
| def b(): |
| """My docstring""" |
| |
| def c(): |
| """My docstring |
| is long.""" |
| |
| def d(): |
| """My docstring |
| is long. |
| """ |
| |
| def e(a, b): |
| """My docstring |
| is long. |
| """ |
| ''' |
| ) |
| |
| ast_module = builder.parse(code) |
| |
| a = ast_module.body[0] |
| assert isinstance(a, nodes.FunctionDef) |
| assert a.fromlineno == 1 |
| assert a.tolineno == 3 |
| |
| b = ast_module.body[1] |
| assert isinstance(b, nodes.FunctionDef) |
| assert b.fromlineno == 5 |
| assert b.tolineno == 6 |
| |
| c = ast_module.body[2] |
| assert isinstance(c, nodes.FunctionDef) |
| assert c.fromlineno == 8 |
| assert c.tolineno == 10 |
| |
| d = ast_module.body[3] |
| assert isinstance(d, nodes.FunctionDef) |
| assert d.fromlineno == 12 |
| assert d.tolineno == 15 |
| |
| e = ast_module.body[4] |
| assert isinstance(e, nodes.FunctionDef) |
| assert e.fromlineno == 17 |
| assert e.tolineno == 20 |
| |
| def test_class_lineno(self) -> None: |
| stmts = self.astroid.body |
| # on line 20: |
| # class debile(dict, |
| # object): |
| # pass |
| class_ = stmts[4] |
| self.assertIsInstance(class_, nodes.ClassDef) |
| self.assertEqual(class_.fromlineno, 20) |
| self.assertEqual(class_.tolineno, 22) |
| self.assertEqual(class_.blockstart_tolineno, 21) |
| pass_ = class_.body[0] |
| self.assertIsInstance(pass_, nodes.Pass) |
| self.assertEqual(pass_.fromlineno, 22) |
| self.assertEqual(pass_.tolineno, 22) |
| |
| def test_if_lineno(self) -> None: |
| stmts = self.astroid.body |
| # on line 20: |
| # if aaaa: pass |
| # else: |
| # aaaa,bbbb = 1,2 |
| # aaaa,bbbb = bbbb,aaaa |
| if_ = stmts[5] |
| self.assertIsInstance(if_, nodes.If) |
| self.assertEqual(if_.fromlineno, 24) |
| self.assertEqual(if_.tolineno, 27) |
| self.assertEqual(if_.blockstart_tolineno, 24) |
| self.assertEqual(if_.orelse[0].fromlineno, 26) |
| self.assertEqual(if_.orelse[1].tolineno, 27) |
| |
| def test_for_while_lineno(self) -> None: |
| for code in ( |
| """ |
| for a in range(4): |
| print (a) |
| break |
| else: |
| print ("bouh") |
| """, |
| """ |
| while a: |
| print (a) |
| break |
| else: |
| print ("bouh") |
| """, |
| ): |
| astroid = builder.parse(code, __name__) |
| stmt = astroid.body[0] |
| self.assertEqual(stmt.fromlineno, 2) |
| self.assertEqual(stmt.tolineno, 6) |
| self.assertEqual(stmt.blockstart_tolineno, 2) |
| self.assertEqual(stmt.orelse[0].fromlineno, 6) # XXX |
| self.assertEqual(stmt.orelse[0].tolineno, 6) |
| |
| def test_try_except_lineno(self) -> None: |
| astroid = builder.parse( |
| """ |
| try: |
| print (a) |
| except: |
| pass |
| else: |
| print ("bouh") |
| """, |
| __name__, |
| ) |
| try_ = astroid.body[0] |
| self.assertEqual(try_.fromlineno, 2) |
| self.assertEqual(try_.tolineno, 7) |
| self.assertEqual(try_.blockstart_tolineno, 2) |
| self.assertEqual(try_.orelse[0].fromlineno, 7) # XXX |
| self.assertEqual(try_.orelse[0].tolineno, 7) |
| hdlr = try_.handlers[0] |
| self.assertEqual(hdlr.fromlineno, 4) |
| self.assertEqual(hdlr.tolineno, 5) |
| self.assertEqual(hdlr.blockstart_tolineno, 4) |
| |
| def test_try_finally_lineno(self) -> None: |
| astroid = builder.parse( |
| """ |
| try: |
| print (a) |
| finally: |
| print ("bouh") |
| """, |
| __name__, |
| ) |
| try_ = astroid.body[0] |
| self.assertEqual(try_.fromlineno, 2) |
| self.assertEqual(try_.tolineno, 5) |
| self.assertEqual(try_.blockstart_tolineno, 2) |
| self.assertEqual(try_.finalbody[0].fromlineno, 5) # XXX |
| self.assertEqual(try_.finalbody[0].tolineno, 5) |
| |
| def test_try_finally_25_lineno(self) -> None: |
| astroid = builder.parse( |
| """ |
| try: |
| print (a) |
| except: |
| pass |
| finally: |
| print ("bouh") |
| """, |
| __name__, |
| ) |
| try_ = astroid.body[0] |
| self.assertEqual(try_.fromlineno, 2) |
| self.assertEqual(try_.tolineno, 7) |
| self.assertEqual(try_.blockstart_tolineno, 2) |
| self.assertEqual(try_.finalbody[0].fromlineno, 7) # XXX |
| self.assertEqual(try_.finalbody[0].tolineno, 7) |
| |
| def test_with_lineno(self) -> None: |
| astroid = builder.parse( |
| """ |
| from __future__ import with_statement |
| with file("/tmp/pouet") as f: |
| print (f) |
| """, |
| __name__, |
| ) |
| with_ = astroid.body[1] |
| self.assertEqual(with_.fromlineno, 3) |
| self.assertEqual(with_.tolineno, 4) |
| self.assertEqual(with_.blockstart_tolineno, 3) |
| |
| |
| class BuilderTest(unittest.TestCase): |
| def setUp(self) -> None: |
| self.manager = test_utils.brainless_manager() |
| self.builder = builder.AstroidBuilder(self.manager) |
| |
| def test_data_build_null_bytes(self) -> None: |
| with self.assertRaises(AstroidSyntaxError): |
| self.builder.string_build("\x00") |
| |
| def test_data_build_invalid_x_escape(self) -> None: |
| with self.assertRaises(AstroidSyntaxError): |
| self.builder.string_build('"\\x1"') |
| |
| def test_data_build_error_filename(self) -> None: |
| """Check that error filename is set to modname if given.""" |
| with pytest.raises(AstroidSyntaxError, match="invalid escape sequence") as ctx: |
| self.builder.string_build("'\\d+\\.\\d+'") |
| assert isinstance(ctx.value.error, SyntaxError) |
| assert ctx.value.error.filename == "<unknown>" |
| |
| with pytest.raises(AstroidSyntaxError, match="invalid escape sequence") as ctx: |
| self.builder.string_build("'\\d+\\.\\d+'", modname="mymodule") |
| assert isinstance(ctx.value.error, SyntaxError) |
| assert ctx.value.error.filename == "mymodule" |
| |
| def test_missing_newline(self) -> None: |
| """Check that a file with no trailing new line is parseable.""" |
| resources.build_file("data/noendingnewline.py") |
| |
| def test_missing_file(self) -> None: |
| with self.assertRaises(AstroidBuildingError): |
| resources.build_file("data/inexistent.py") |
| |
| def test_inspect_build0(self) -> None: |
| """Test astroid tree build from a living object.""" |
| builtin_ast = self.manager.ast_from_module_name("builtins") |
| # just check type and object are there |
| builtin_ast.getattr("type") |
| objectastroid = builtin_ast.getattr("object")[0] |
| self.assertIsInstance(objectastroid.getattr("__new__")[0], nodes.FunctionDef) |
| # check open file alias |
| builtin_ast.getattr("open") |
| # check 'help' is there (defined dynamically by site.py) |
| builtin_ast.getattr("help") |
| # check property has __init__ |
| pclass = builtin_ast["property"] |
| self.assertIn("__init__", pclass) |
| self.assertIsInstance(builtin_ast["None"], nodes.Const) |
| self.assertIsInstance(builtin_ast["True"], nodes.Const) |
| self.assertIsInstance(builtin_ast["False"], nodes.Const) |
| self.assertIsInstance(builtin_ast["Exception"], nodes.ClassDef) |
| self.assertIsInstance(builtin_ast["NotImplementedError"], nodes.ClassDef) |
| |
| def test_inspect_build1(self) -> None: |
| time_ast = self.manager.ast_from_module_name("time") |
| self.assertTrue(time_ast) |
| self.assertEqual(time_ast["time"].args.defaults, None) |
| |
| def test_inspect_build3(self) -> None: |
| self.builder.inspect_build(unittest) |
| |
| def test_inspect_build_type_object(self) -> None: |
| builtin_ast = self.manager.ast_from_module_name("builtins") |
| |
| inferred = list(builtin_ast.igetattr("object")) |
| self.assertEqual(len(inferred), 1) |
| inferred = inferred[0] |
| self.assertEqual(inferred.name, "object") |
| inferred.as_string() # no crash test |
| |
| inferred = list(builtin_ast.igetattr("type")) |
| self.assertEqual(len(inferred), 1) |
| inferred = inferred[0] |
| self.assertEqual(inferred.name, "type") |
| inferred.as_string() # no crash test |
| |
| def test_inspect_transform_module(self) -> None: |
| # ensure no cached version of the time module |
| self.manager._mod_file_cache.pop(("time", None), None) |
| self.manager.astroid_cache.pop("time", None) |
| |
| def transform_time(node: Module) -> None: |
| if node.name == "time": |
| node.transformed = True |
| |
| self.manager.register_transform(nodes.Module, transform_time) |
| try: |
| time_ast = self.manager.ast_from_module_name("time") |
| self.assertTrue(getattr(time_ast, "transformed", False)) |
| finally: |
| self.manager.unregister_transform(nodes.Module, transform_time) |
| |
| def test_package_name(self) -> None: |
| """Test base properties and method of an astroid module.""" |
| datap = resources.build_file("data/__init__.py", "data") |
| self.assertEqual(datap.name, "data") |
| self.assertEqual(datap.package, 1) |
| datap = resources.build_file("data/__init__.py", "data.__init__") |
| self.assertEqual(datap.name, "data") |
| self.assertEqual(datap.package, 1) |
| datap = resources.build_file("data/tmp__init__.py", "data.tmp__init__") |
| self.assertEqual(datap.name, "data.tmp__init__") |
| self.assertEqual(datap.package, 0) |
| |
| def test_yield_parent(self) -> None: |
| """Check if we added discard nodes as yield parent (w/ compiler).""" |
| code = """ |
| def yiell(): #@ |
| yield 0 |
| if noe: |
| yield more |
| """ |
| func = builder.extract_node(code) |
| self.assertIsInstance(func, nodes.FunctionDef) |
| stmt = func.body[0] |
| self.assertIsInstance(stmt, nodes.Expr) |
| self.assertIsInstance(stmt.value, nodes.Yield) |
| self.assertIsInstance(func.body[1].body[0], nodes.Expr) |
| self.assertIsInstance(func.body[1].body[0].value, nodes.Yield) |
| |
| def test_object(self) -> None: |
| obj_ast = self.builder.inspect_build(object) |
| self.assertIn("__setattr__", obj_ast) |
| |
| def test_newstyle_detection(self) -> None: |
| data = """ |
| class A: |
| "old style" |
| |
| class B(A): |
| "old style" |
| |
| class C(object): |
| "new style" |
| |
| class D(C): |
| "new style" |
| |
| __metaclass__ = type |
| |
| class E(A): |
| "old style" |
| |
| class F: |
| "new style" |
| """ |
| mod_ast = builder.parse(data, __name__) |
| self.assertTrue(mod_ast["A"].newstyle) |
| self.assertTrue(mod_ast["B"].newstyle) |
| self.assertTrue(mod_ast["E"].newstyle) |
| self.assertTrue(mod_ast["C"].newstyle) |
| self.assertTrue(mod_ast["D"].newstyle) |
| self.assertTrue(mod_ast["F"].newstyle) |
| |
| def test_globals(self) -> None: |
| data = """ |
| CSTE = 1 |
| |
| def update_global(): |
| global CSTE |
| CSTE += 1 |
| |
| def global_no_effect(): |
| global CSTE2 |
| print (CSTE) |
| """ |
| astroid = builder.parse(data, __name__) |
| self.assertEqual(len(astroid.getattr("CSTE")), 2) |
| self.assertIsInstance(astroid.getattr("CSTE")[0], nodes.AssignName) |
| self.assertEqual(astroid.getattr("CSTE")[0].fromlineno, 2) |
| self.assertEqual(astroid.getattr("CSTE")[1].fromlineno, 6) |
| with self.assertRaises(AttributeInferenceError): |
| astroid.getattr("CSTE2") |
| with self.assertRaises(InferenceError): |
| next(astroid["global_no_effect"].ilookup("CSTE2")) |
| |
| def test_socket_build(self) -> None: |
| astroid = self.builder.module_build(socket) |
| # XXX just check the first one. Actually 3 objects are inferred (look at |
| # the socket module) but the last one as those attributes dynamically |
| # set and astroid is missing this. |
| for fclass in astroid.igetattr("socket"): |
| self.assertIn("connect", fclass) |
| self.assertIn("send", fclass) |
| self.assertIn("close", fclass) |
| break |
| |
| def test_gen_expr_var_scope(self) -> None: |
| data = "l = list(n for n in range(10))\n" |
| astroid = builder.parse(data, __name__) |
| # n unavailable outside gen expr scope |
| self.assertNotIn("n", astroid) |
| # test n is inferable anyway |
| n = test_utils.get_name_node(astroid, "n") |
| self.assertIsNot(n.scope(), astroid) |
| self.assertEqual([i.__class__ for i in n.infer()], [util.Uninferable.__class__]) |
| |
| def test_no_future_imports(self) -> None: |
| mod = builder.parse("import sys") |
| self.assertEqual(set(), mod.future_imports) |
| |
| def test_future_imports(self) -> None: |
| mod = builder.parse("from __future__ import print_function") |
| self.assertEqual({"print_function"}, mod.future_imports) |
| |
| def test_two_future_imports(self) -> None: |
| mod = builder.parse( |
| """ |
| from __future__ import print_function |
| from __future__ import absolute_import |
| """ |
| ) |
| self.assertEqual({"print_function", "absolute_import"}, mod.future_imports) |
| |
| def test_inferred_build(self) -> None: |
| code = """ |
| class A: pass |
| A.type = "class" |
| |
| def A_assign_type(self): |
| print (self) |
| A.assign_type = A_assign_type |
| """ |
| astroid = builder.parse(code) |
| lclass = list(astroid.igetattr("A")) |
| self.assertEqual(len(lclass), 1) |
| lclass = lclass[0] |
| self.assertIn("assign_type", lclass.locals) |
| self.assertIn("type", lclass.locals) |
| |
| def test_infer_can_assign_regular_object(self) -> None: |
| mod = builder.parse( |
| """ |
| class A: |
| pass |
| a = A() |
| a.value = "is set" |
| a.other = "is set" |
| """ |
| ) |
| obj = list(mod.igetattr("a")) |
| self.assertEqual(len(obj), 1) |
| obj = obj[0] |
| self.assertIsInstance(obj, Instance) |
| self.assertIn("value", obj.instance_attrs) |
| self.assertIn("other", obj.instance_attrs) |
| |
| def test_infer_can_assign_has_slots(self) -> None: |
| mod = builder.parse( |
| """ |
| class A: |
| __slots__ = ('value',) |
| a = A() |
| a.value = "is set" |
| a.other = "not set" |
| """ |
| ) |
| obj = list(mod.igetattr("a")) |
| self.assertEqual(len(obj), 1) |
| obj = obj[0] |
| self.assertIsInstance(obj, Instance) |
| self.assertIn("value", obj.instance_attrs) |
| self.assertNotIn("other", obj.instance_attrs) |
| |
| def test_infer_can_assign_no_classdict(self) -> None: |
| mod = builder.parse( |
| """ |
| a = object() |
| a.value = "not set" |
| """ |
| ) |
| obj = list(mod.igetattr("a")) |
| self.assertEqual(len(obj), 1) |
| obj = obj[0] |
| self.assertIsInstance(obj, Instance) |
| self.assertNotIn("value", obj.instance_attrs) |
| |
| def test_augassign_attr(self) -> None: |
| builder.parse( |
| """ |
| class Counter: |
| v = 0 |
| def inc(self): |
| self.v += 1 |
| """, |
| __name__, |
| ) |
| # TODO: Check self.v += 1 generate AugAssign(AssAttr(...)), |
| # not AugAssign(GetAttr(AssName...)) |
| |
| def test_inferred_dont_pollute(self) -> None: |
| code = """ |
| def func(a=None): |
| a.custom_attr = 0 |
| def func2(a={}): |
| a.custom_attr = 0 |
| """ |
| builder.parse(code) |
| # pylint: disable=no-member |
| nonetype = nodes.const_factory(None) |
| self.assertNotIn("custom_attr", nonetype.locals) |
| self.assertNotIn("custom_attr", nonetype.instance_attrs) |
| nonetype = nodes.const_factory({}) |
| self.assertNotIn("custom_attr", nonetype.locals) |
| self.assertNotIn("custom_attr", nonetype.instance_attrs) |
| |
| def test_asstuple(self) -> None: |
| code = "a, b = range(2)" |
| astroid = builder.parse(code) |
| self.assertIn("b", astroid.locals) |
| code = """ |
| def visit_if(self, node): |
| node.test, body = node.tests[0] |
| """ |
| astroid = builder.parse(code) |
| self.assertIn("body", astroid["visit_if"].locals) |
| |
| def test_build_constants(self) -> None: |
| """Test expected values of constants after rebuilding.""" |
| code = """ |
| def func(): |
| return None |
| return |
| return 'None' |
| """ |
| astroid = builder.parse(code) |
| none, nothing, chain = (ret.value for ret in astroid.body[0].body) |
| self.assertIsInstance(none, nodes.Const) |
| self.assertIsNone(none.value) |
| self.assertIsNone(nothing) |
| self.assertIsInstance(chain, nodes.Const) |
| self.assertEqual(chain.value, "None") |
| |
| def test_not_implemented(self) -> None: |
| node = builder.extract_node( |
| """ |
| NotImplemented #@ |
| """ |
| ) |
| inferred = next(node.infer()) |
| self.assertIsInstance(inferred, nodes.Const) |
| self.assertEqual(inferred.value, NotImplemented) |
| |
| def test_type_comments_without_content(self) -> None: |
| node = builder.parse( |
| """ |
| a = 1 # type: # any comment |
| """ |
| ) |
| assert node |
| |
| |
| class FileBuildTest(unittest.TestCase): |
| def setUp(self) -> None: |
| self.module = resources.build_file("data/module.py", "data.module") |
| |
| def test_module_base_props(self) -> None: |
| """Test base properties and method of an astroid module.""" |
| module = self.module |
| self.assertEqual(module.name, "data.module") |
| assert isinstance(module.doc_node, nodes.Const) |
| self.assertEqual(module.doc_node.value, "test module for astroid\n") |
| self.assertEqual(module.fromlineno, 0) |
| self.assertIsNone(module.parent) |
| self.assertEqual(module.frame(), module) |
| self.assertEqual(module.frame(), module) |
| self.assertEqual(module.root(), module) |
| self.assertEqual(module.file, os.path.abspath(resources.find("data/module.py"))) |
| self.assertEqual(module.pure_python, 1) |
| self.assertEqual(module.package, 0) |
| self.assertFalse(module.is_statement) |
| with self.assertRaises(StatementMissing): |
| with pytest.warns(DeprecationWarning) as records: |
| self.assertEqual(module.statement(future=True), module) |
| assert len(records) == 1 |
| with self.assertRaises(StatementMissing): |
| module.statement() |
| |
| def test_module_locals(self) -> None: |
| """Test the 'locals' dictionary of an astroid module.""" |
| module = self.module |
| _locals = module.locals |
| self.assertIs(_locals, module.globals) |
| keys = sorted(_locals.keys()) |
| should = [ |
| "MY_DICT", |
| "NameNode", |
| "YO", |
| "YOUPI", |
| "__revision__", |
| "global_access", |
| "modutils", |
| "four_args", |
| "os", |
| "redirect", |
| ] |
| should.sort() |
| self.assertEqual(keys, sorted(should)) |
| |
| def test_function_base_props(self) -> None: |
| """Test base properties and method of an astroid function.""" |
| module = self.module |
| function = module["global_access"] |
| self.assertEqual(function.name, "global_access") |
| assert isinstance(function.doc_node, nodes.Const) |
| self.assertEqual(function.doc_node.value, "function test") |
| self.assertEqual(function.fromlineno, 11) |
| self.assertTrue(function.parent) |
| self.assertEqual(function.frame(), function) |
| self.assertEqual(function.parent.frame(), module) |
| self.assertEqual(function.frame(), function) |
| self.assertEqual(function.parent.frame(), module) |
| self.assertEqual(function.root(), module) |
| self.assertEqual([n.name for n in function.args.args], ["key", "val"]) |
| self.assertEqual(function.type, "function") |
| |
| def test_function_locals(self) -> None: |
| """Test the 'locals' dictionary of an astroid function.""" |
| _locals = self.module["global_access"].locals |
| self.assertEqual(len(_locals), 4) |
| keys = sorted(_locals.keys()) |
| self.assertEqual(keys, ["i", "key", "local", "val"]) |
| |
| def test_class_base_props(self) -> None: |
| """Test base properties and method of an astroid class.""" |
| module = self.module |
| klass = module["YO"] |
| self.assertEqual(klass.name, "YO") |
| assert isinstance(klass.doc_node, nodes.Const) |
| self.assertEqual(klass.doc_node.value, "hehe\n haha") |
| self.assertEqual(klass.fromlineno, 25) |
| self.assertTrue(klass.parent) |
| self.assertEqual(klass.frame(), klass) |
| self.assertEqual(klass.parent.frame(), module) |
| self.assertEqual(klass.frame(), klass) |
| self.assertEqual(klass.parent.frame(), module) |
| self.assertEqual(klass.root(), module) |
| self.assertEqual(klass.basenames, []) |
| self.assertTrue(klass.newstyle) |
| |
| def test_class_locals(self) -> None: |
| """Test the 'locals' dictionary of an astroid class.""" |
| module = self.module |
| klass1 = module["YO"] |
| locals1 = klass1.locals |
| keys = sorted(locals1.keys()) |
| assert_keys = ["__init__", "__module__", "__qualname__", "a"] |
| self.assertEqual(keys, assert_keys) |
| klass2 = module["YOUPI"] |
| locals2 = klass2.locals |
| keys = locals2.keys() |
| assert_keys = [ |
| "__init__", |
| "__module__", |
| "__qualname__", |
| "class_attr", |
| "class_method", |
| "method", |
| "static_method", |
| ] |
| self.assertEqual(sorted(keys), assert_keys) |
| |
| def test_class_instance_attrs(self) -> None: |
| module = self.module |
| klass1 = module["YO"] |
| klass2 = module["YOUPI"] |
| self.assertEqual(list(klass1.instance_attrs.keys()), ["yo"]) |
| self.assertEqual(list(klass2.instance_attrs.keys()), ["member"]) |
| |
| def test_class_basenames(self) -> None: |
| module = self.module |
| klass1 = module["YO"] |
| klass2 = module["YOUPI"] |
| self.assertEqual(klass1.basenames, []) |
| self.assertEqual(klass2.basenames, ["YO"]) |
| |
| def test_method_base_props(self) -> None: |
| """Test base properties and method of an astroid method.""" |
| klass2 = self.module["YOUPI"] |
| # "normal" method |
| method = klass2["method"] |
| self.assertEqual(method.name, "method") |
| self.assertEqual([n.name for n in method.args.args], ["self"]) |
| assert isinstance(method.doc_node, nodes.Const) |
| self.assertEqual(method.doc_node.value, "method\n test") |
| self.assertEqual(method.fromlineno, 48) |
| self.assertEqual(method.type, "method") |
| # class method |
| method = klass2["class_method"] |
| self.assertEqual([n.name for n in method.args.args], ["cls"]) |
| self.assertEqual(method.type, "classmethod") |
| # static method |
| method = klass2["static_method"] |
| self.assertEqual(method.args.args, []) |
| self.assertEqual(method.type, "staticmethod") |
| |
| def test_method_locals(self) -> None: |
| """Test the 'locals' dictionary of an astroid method.""" |
| method = self.module["YOUPI"]["method"] |
| _locals = method.locals |
| keys = sorted(_locals) |
| # ListComp variables are not accessible outside |
| self.assertEqual(len(_locals), 3) |
| self.assertEqual(keys, ["autre", "local", "self"]) |
| |
| def test_unknown_encoding(self) -> None: |
| with self.assertRaises(AstroidSyntaxError): |
| resources.build_file("data/invalid_encoding.py") |
| |
| |
| def test_module_build_dunder_file() -> None: |
| """Test that module_build() can work with modules that have the *__file__* |
| attribute. |
| """ |
| module = builder.AstroidBuilder().module_build(collections) |
| assert module.path[0] == collections.__file__ |
| |
| |
| @pytest.mark.xfail( |
| reason=( |
| "The builtin ast module does not fail with a specific error " |
| "for syntax error caused by invalid type comments." |
| ), |
| ) |
| def test_parse_module_with_invalid_type_comments_does_not_crash(): |
| node = builder.parse( |
| """ |
| # op { |
| # name: "AssignAddVariableOp" |
| # input_arg { |
| # name: "resource" |
| # type: DT_RESOURCE |
| # } |
| # input_arg { |
| # name: "value" |
| # type_attr: "dtype" |
| # } |
| # attr { |
| # name: "dtype" |
| # type: "type" |
| # } |
| # is_stateful: true |
| # } |
| a, b = 2 |
| """ |
| ) |
| assert isinstance(node, nodes.Module) |
| |
| |
| def test_arguments_of_signature() -> None: |
| """Test that arguments is None for function without an inferable signature.""" |
| node = builder.extract_node("int") |
| classdef: nodes.ClassDef = next(node.infer()) |
| assert all(i.args.args is None for i in classdef.getattr("__dir__")) |
| |
| |
| class HermeticInterpreterTest(unittest.TestCase): |
| """Modeled on https://github.com/pylint-dev/astroid/pull/1207#issuecomment-951455588.""" |
| |
| @classmethod |
| def setUpClass(cls): |
| """Simulate a hermetic interpreter environment having no code on the filesystem.""" |
| with tempfile.TemporaryDirectory() as tmp_dir: |
| sys.path.append(tmp_dir) |
| |
| # Write a python file and compile it to .pyc |
| # To make this test have even more value, we would need to come up with some |
| # code that gets inferred differently when we get its "partial representation". |
| # This code is too simple for that. But we can't use builtins either, because we would |
| # have to delete builtins from the filesystem. But even if we engineered that, |
| # the difference might evaporate over time as inference changes. |
| cls.code_snippet = "def func(): return 42" |
| with tempfile.NamedTemporaryFile( |
| mode="w", dir=tmp_dir, suffix=".py", delete=False |
| ) as tmp: |
| tmp.write(cls.code_snippet) |
| pyc_file = py_compile.compile(tmp.name) |
| cls.pyc_name = tmp.name.replace(".py", ".pyc") |
| os.remove(tmp.name) |
| os.rename(pyc_file, cls.pyc_name) |
| |
| # Import the module |
| cls.imported_module_path = pathlib.Path(cls.pyc_name) |
| cls.imported_module = importlib.import_module(cls.imported_module_path.stem) |
| |
| # Delete source code from module object, filesystem, and path |
| del cls.imported_module.__file__ |
| os.remove(cls.imported_module_path) |
| sys.path.remove(tmp_dir) |
| |
| def test_build_from_live_module_without_source_file(self) -> None: |
| """Assert that inspect_build() is not called. |
| |
| See comment in module_build() before the call to inspect_build(): |
| "get a partial representation by introspection" |
| |
| This "partial representation" was presumably causing unexpected behavior. |
| """ |
| # Sanity check |
| self.assertIsNone( |
| self.imported_module.__loader__.get_source(self.imported_module_path.stem) |
| ) |
| with self.assertRaises(AttributeError): |
| _ = self.imported_module.__file__ |
| |
| my_builder = builder.AstroidBuilder() |
| with unittest.mock.patch.object( |
| self.imported_module.__loader__, |
| "get_source", |
| return_value=self.code_snippet, |
| ): |
| with unittest.mock.patch.object( |
| my_builder, "inspect_build", side_effect=AssertionError |
| ): |
| my_builder.module_build( |
| self.imported_module, modname=self.imported_module_path.stem |
| ) |