diff options
author | Pierre Sassoulas <pierre.sassoulas@gmail.com> | 2023-02-09 21:54:24 +0100 |
---|---|---|
committer | Pierre Sassoulas <pierre.sassoulas@gmail.com> | 2023-02-09 23:23:31 +0100 |
commit | d4852be40cdf88d61d2c13fce22f1ec08fa23f3f (patch) | |
tree | eeafa70f68063de0d87119e871807bd4f0af6c62 /tests/test_regrtest.py | |
parent | dc98a63a9fddf774e29ad340ff40cac3718faac3 (diff) | |
download | astroid-git-d4852be40cdf88d61d2c13fce22f1ec08fa23f3f.tar.gz |
[brain tests] Rename 'unittest' prefix to 'test'
Diffstat (limited to 'tests/test_regrtest.py')
-rw-r--r-- | tests/test_regrtest.py | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/tests/test_regrtest.py b/tests/test_regrtest.py new file mode 100644 index 00000000..783f1cc1 --- /dev/null +++ b/tests/test_regrtest.py @@ -0,0 +1,441 @@ +# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html +# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/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.const import PY38_PLUS +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 + + 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") + + @pytest.mark.skipif(not PY38_PLUS, reason="needs assignment expressions") + 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" + + +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/PyCQA/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/PyCQA/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") |