blob: 45f241f8cf6bce937e4297f242491f2b21ff993a [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
import sys
import textwrap
import unittest
from unittest import mock
import pytest
from astroid import MANAGER, Instance, bases, manager, nodes, parse, test_utils
from astroid.builder import AstroidBuilder, _extract_single_node, extract_node
from astroid.context import InferenceContext
from astroid.exceptions import InferenceError
from astroid.raw_building import build_module
from astroid.util import Uninferable
from . import resources
try:
import numpy # pylint: disable=unused-import
except ImportError:
HAS_NUMPY = False
else:
HAS_NUMPY = True
class NonRegressionTests(unittest.TestCase):
def setUp(self) -> None:
sys.path.insert(0, resources.find("data"))
MANAGER.always_load_extensions = True
self.addCleanup(MANAGER.clear_cache)
def tearDown(self) -> None:
MANAGER.always_load_extensions = False
sys.path.pop(0)
sys.path_importer_cache.pop(resources.find("data"), None)
def test_manager_instance_attributes_reference_global_MANAGER(self) -> None:
for expected in (True, False):
with mock.patch.dict(
manager.AstroidManager.brain,
values={"always_load_extensions": expected},
):
assert (
MANAGER.always_load_extensions
== manager.AstroidManager.brain["always_load_extensions"]
)
with mock.patch.dict(
manager.AstroidManager.brain,
values={"optimize_ast": expected},
):
assert (
MANAGER.optimize_ast == manager.AstroidManager.brain["optimize_ast"]
)
def test_module_path(self) -> None:
man = test_utils.brainless_manager()
mod = man.ast_from_module_name("package.import_package_subpackage_module")
package = next(mod.igetattr("package"))
self.assertEqual(package.name, "package")
subpackage = next(package.igetattr("subpackage"))
self.assertIsInstance(subpackage, nodes.Module)
self.assertTrue(subpackage.package)
self.assertEqual(subpackage.name, "package.subpackage")
module = next(subpackage.igetattr("module"))
self.assertEqual(module.name, "package.subpackage.module")
def test_package_sidepackage(self) -> None:
brainless_manager = test_utils.brainless_manager()
assert "package.sidepackage" not in MANAGER.astroid_cache
package = brainless_manager.ast_from_module_name("absimp")
self.assertIsInstance(package, nodes.Module)
self.assertTrue(package.package)
subpackage = next(package.getattr("sidepackage")[0].infer())
self.assertIsInstance(subpackage, nodes.Module)
self.assertTrue(subpackage.package)
self.assertEqual(subpackage.name, "absimp.sidepackage")
def test_living_property(self) -> None:
builder = AstroidBuilder()
builder._done = {}
builder._module = sys.modules[__name__]
builder.object_build(build_module("module_name", ""), Whatever)
@unittest.skipIf(not HAS_NUMPY, "Needs numpy")
def test_numpy_crash(self):
"""Test don't crash on numpy."""
# a crash occurred somewhere in the past, and an
# InferenceError instead of a crash was better, but now we even infer!
builder = AstroidBuilder()
data = """
from numpy import multiply
multiply([1, 2], [3, 4])
"""
astroid = builder.string_build(data, __name__, __file__)
callfunc = astroid.body[1].value.func
inferred = callfunc.inferred()
self.assertEqual(len(inferred), 1)
@unittest.skipUnless(HAS_NUMPY, "Needs numpy")
def test_numpy_distutils(self):
"""Special handling of virtualenv's patching of distutils shouldn't interfere
with numpy.distutils.
PY312_PLUS -- This test will likely become unnecessary when Python 3.12 is
numpy's minimum version. (numpy.distutils will be removed then.)
"""
node = extract_node(
"""
from numpy.distutils.misc_util import is_sequence
is_sequence("ABC") #@
"""
)
inferred = node.inferred()
self.assertIsInstance(inferred[0], nodes.Const)
def test_nameconstant(self) -> None:
# used to fail for Python 3.4
builder = AstroidBuilder()
astroid = builder.string_build("def test(x=True): pass")
default = astroid.body[0].args.args[0]
self.assertEqual(default.name, "x")
self.assertEqual(next(default.infer()).value, True)
def test_recursion_regression_issue25(self) -> None:
builder = AstroidBuilder()
data = """
import recursion as base
_real_Base = base.Base
class Derived(_real_Base):
pass
def run():
base.Base = Derived
"""
astroid = builder.string_build(data, __name__, __file__)
# Used to crash in _is_metaclass, due to wrong
# ancestors chain
classes = astroid.nodes_of_class(nodes.ClassDef)
for klass in classes:
# triggers the _is_metaclass call
klass.type # pylint: disable=pointless-statement # noqa: B018
def test_decorator_callchain_issue42(self) -> None:
builder = AstroidBuilder()
data = """
def test():
def factory(func):
def newfunc():
func()
return newfunc
return factory
@test()
def crash():
pass
"""
astroid = builder.string_build(data, __name__, __file__)
self.assertEqual(astroid["crash"].type, "function")
def test_filter_stmts_scoping(self) -> None:
builder = AstroidBuilder()
data = """
def test():
compiler = int()
class B(compiler.__class__):
pass
compiler = B()
return compiler
"""
astroid = builder.string_build(data, __name__, __file__)
test = astroid["test"]
result = next(test.infer_call_result(astroid))
self.assertIsInstance(result, Instance)
base = next(result._proxied.bases[0].infer())
self.assertEqual(base.name, "int")
def test_filter_stmts_nested_if(self) -> None:
builder = AstroidBuilder()
data = """
def test(val):
variable = None
if val == 1:
variable = "value"
if variable := "value":
pass
elif val == 2:
variable = "value_two"
variable = "value_two"
return variable
"""
module = builder.string_build(data, __name__, __file__)
test_func = module["test"]
result = list(test_func.infer_call_result(module))
assert len(result) == 3
assert isinstance(result[0], nodes.Const)
assert result[0].value is None
assert result[0].lineno == 3
assert isinstance(result[1], nodes.Const)
assert result[1].value == "value"
assert result[1].lineno == 7
assert isinstance(result[1], nodes.Const)
assert result[2].value == "value_two"
assert result[2].lineno == 12
def test_ancestors_patching_class_recursion(self) -> None:
node = AstroidBuilder().string_build(
textwrap.dedent(
"""
import string
Template = string.Template
class A(Template):
pass
class B(A):
pass
def test(x=False):
if x:
string.Template = A
else:
string.Template = B
"""
)
)
klass = node["A"]
ancestors = list(klass.ancestors())
self.assertEqual(ancestors[0].qname(), "string.Template")
def test_ancestors_yes_in_bases(self) -> None:
# Test for issue https://bitbucket.org/logilab/astroid/issue/84
# This used to crash astroid with a TypeError, because an Uninferable
# node was present in the bases
node = extract_node(
"""
def with_metaclass(meta, *bases):
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {})
import lala
class A(with_metaclass(object, lala.lala)): #@
pass
"""
)
ancestors = list(node.ancestors())
self.assertEqual(len(ancestors), 1)
self.assertEqual(ancestors[0].qname(), "builtins.object")
def test_ancestors_missing_from_function(self) -> None:
# Test for https://www.logilab.org/ticket/122793
node = extract_node(
"""
def gen(): yield
GEN = gen()
next(GEN)
"""
)
self.assertRaises(InferenceError, next, node.infer())
def test_unicode_in_docstring(self) -> None:
# Crashed for astroid==1.4.1
# Test for https://bitbucket.org/logilab/astroid/issues/273/
# In a regular file, "coding: utf-8" would have been used.
node = extract_node(
f"""
from __future__ import unicode_literals
class MyClass(object):
def method(self):
"With unicode : {'’'} "
instance = MyClass()
"""
)
next(node.value.infer()).as_string()
def test_binop_generates_nodes_with_parents(self) -> None:
node = extract_node(
"""
def no_op(*args):
pass
def foo(*args):
def inner(*more_args):
args + more_args #@
return inner
"""
)
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Tuple)
self.assertIsNotNone(inferred.parent)
self.assertIsInstance(inferred.parent, nodes.BinOp)
def test_decorator_names_inference_error_leaking(self) -> None:
node = extract_node(
"""
class Parent(object):
@property
def foo(self):
pass
class Child(Parent):
@Parent.foo.getter
def foo(self): #@
return super(Child, self).foo + ['oink']
"""
)
inferred = next(node.infer())
self.assertEqual(inferred.decoratornames(), {".Parent.foo.getter"})
def test_recursive_property_method(self) -> None:
node = extract_node(
"""
class APropert():
@property
def property(self):
return self
APropert().property
"""
)
next(node.infer())
def test_uninferable_string_argument_of_namedtuple(self) -> None:
node = extract_node(
"""
import collections
collections.namedtuple('{}'.format("a"), '')()
"""
)
next(node.infer())
def test_regression_inference_of_self_in_lambda(self) -> None:
code = """
class A:
@b(lambda self: __(self))
def d(self):
pass
"""
node = extract_node(code)
inferred = next(node.infer())
assert isinstance(inferred, Instance)
assert inferred.qname() == ".A"
def test_inference_context_consideration(self) -> None:
"""https://github.com/PyCQA/astroid/issues/1828"""
code = """
class Base:
def return_type(self):
return type(self)()
class A(Base):
def method(self):
return self.return_type()
class B(Base):
def method(self):
return self.return_type()
A().method() #@
B().method() #@
"""
node1, node2 = extract_node(code)
inferred1 = next(node1.infer())
assert inferred1.qname() == ".A"
inferred2 = next(node2.infer())
assert inferred2.qname() == ".B"
class Whatever:
a = property(lambda x: x, lambda x: x) # type: ignore[misc]
def test_ancestor_looking_up_redefined_function() -> None:
code = """
class Foo:
def _format(self):
pass
def format(self):
self.format = self._format
self.format()
Foo
"""
node = extract_node(code)
inferred = next(node.infer())
ancestor = next(inferred.ancestors())
_, found = ancestor.lookup("format")
assert len(found) == 1
assert isinstance(found[0], nodes.FunctionDef)
def test_crash_in_dunder_inference_prevented() -> None:
code = """
class MyClass():
def fu(self, objects):
delitem = dict.__delitem__.__get__(self, dict)
delitem #@
"""
inferred = next(extract_node(code).infer())
assert inferred.qname() == "builtins.dict.__delitem__"
def test_regression_crash_classmethod() -> None:
"""Regression test for a crash reported in
https://github.com/pylint-dev/pylint/issues/4982.
"""
code = """
class Base:
@classmethod
def get_first_subclass(cls):
for subclass in cls.__subclasses__():
return subclass
return object
subclass = Base.get_first_subclass()
class Another(subclass):
pass
"""
parse(code)
def test_max_inferred_for_complicated_class_hierarchy() -> None:
"""Regression test for a crash reported in
https://github.com/pylint-dev/pylint/issues/5679.
The class hierarchy of 'sqlalchemy' is so intricate that it becomes uninferable with
the standard max_inferred of 100. We used to crash when this happened.
"""
# Create module and get relevant nodes
module = resources.build_file(
str(resources.RESOURCE_PATH / "max_inferable_limit_for_classes" / "main.py")
)
init_attr_node = module.body[-1].body[0].body[0].value.func
init_object_node = module.body[-1].mro()[-1]["__init__"]
super_node = next(init_attr_node.expr.infer())
# Arbitrarily limit the max number of infered nodes per context
InferenceContext.max_inferred = -1
context = InferenceContext()
# Try to infer 'object.__init__' > because of limit is impossible
for inferred in bases._infer_stmts([init_object_node], context, frame=super):
assert inferred == Uninferable
# Reset inference limit
InferenceContext.max_inferred = 100
# Check that we don't crash on a previously uninferable node
assert super_node.getattr("__init__", context=context)[0] == Uninferable
@mock.patch(
"astroid.nodes.ImportFrom._infer",
side_effect=RecursionError,
)
def test_recursion_during_inference(mocked) -> None:
"""Check that we don't crash if we hit the recursion limit during inference."""
node: nodes.Call = _extract_single_node(
"""
from module import something
something()
"""
)
with pytest.raises(InferenceError) as error:
next(node.infer())
assert error.value.message.startswith("RecursionError raised")