forked from openkylin/astroid
404 lines
12 KiB
Python
404 lines
12 KiB
Python
# Copyright (c) 2006-2008, 2010-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
|
||
# Copyright (c) 2007 Marien Zwart <marienz@gentoo.org>
|
||
# Copyright (c) 2013-2014 Google, Inc.
|
||
# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
|
||
# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
|
||
# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
|
||
# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
|
||
# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
|
||
# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
|
||
# Copyright (c) 2019, 2021 hippo91 <guillaume.peillex@gmail.com>
|
||
# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
|
||
# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
|
||
# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
|
||
# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
|
||
# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
|
||
# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
|
||
|
||
# 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
|
||
|
||
import sys
|
||
import textwrap
|
||
import unittest
|
||
|
||
import pytest
|
||
|
||
from astroid import MANAGER, Instance, nodes, parse, test_utils
|
||
from astroid.builder import AstroidBuilder, extract_node
|
||
from astroid.const import PY38_PLUS
|
||
from astroid.exceptions import InferenceError
|
||
from astroid.raw_building import build_module
|
||
|
||
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)
|
||
|
||
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_ssl_protocol(self) -> None:
|
||
node = extract_node(
|
||
"""
|
||
import ssl
|
||
ssl.PROTOCOL_TLSv1
|
||
"""
|
||
)
|
||
inferred = next(node.infer())
|
||
self.assertIsInstance(inferred, nodes.Const)
|
||
|
||
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)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|