# 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, 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(resources.AstroidCacheSetupMixin, unittest.TestCase): def setUp(self) -> None: sys.path.insert(0, resources.find("data")) MANAGER.always_load_extensions = True def tearDown(self) -> None: MANAGER.always_load_extensions = False sys.path.pop(0) sys.path_importer_cache.pop(resources.find("data"), None) 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: manager = test_utils.brainless_manager() assert "package.sidepackage" not in MANAGER.astroid_cache package = 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() """ # noqa[RUF001] ) 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")