blob: 84ac65e09900a49a82bd388116bd9e5da4ab1b21 [file] [log] [blame]
# 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
)