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_inference_calls.py | |
parent | dc98a63a9fddf774e29ad340ff40cac3718faac3 (diff) | |
download | astroid-git-d4852be40cdf88d61d2c13fce22f1ec08fa23f3f.tar.gz |
[brain tests] Rename 'unittest' prefix to 'test'
Diffstat (limited to 'tests/test_inference_calls.py')
-rw-r--r-- | tests/test_inference_calls.py | 593 |
1 files changed, 593 insertions, 0 deletions
diff --git a/tests/test_inference_calls.py b/tests/test_inference_calls.py new file mode 100644 index 00000000..72afb989 --- /dev/null +++ b/tests/test_inference_calls.py @@ -0,0 +1,593 @@ +# 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 + +"""Tests for function call inference.""" + +from astroid import bases, builder, nodes +from astroid.util import Uninferable + + +def test_no_return() -> None: + """Test function with no return statements.""" + node = builder.extract_node( + """ + def f(): + pass + + f() #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert inferred[0] is Uninferable + + +def test_one_return() -> None: + """Test function with a single return that always executes.""" + node = builder.extract_node( + """ + def f(): + return 1 + + f() #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.Const) + assert inferred[0].value == 1 + + +def test_one_return_possible() -> None: + """Test function with a single return that only sometimes executes. + + Note: currently, inference doesn't handle this type of control flow + """ + node = builder.extract_node( + """ + def f(x): + if x: + return 1 + + f(1) #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.Const) + assert inferred[0].value == 1 + + +def test_multiple_returns() -> None: + """Test function with multiple returns.""" + node = builder.extract_node( + """ + def f(x): + if x > 10: + return 1 + elif x > 20: + return 2 + else: + return 3 + + f(100) #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 3 + assert all(isinstance(node, nodes.Const) for node in inferred) + assert {node.value for node in inferred} == {1, 2, 3} + + +def test_argument() -> None: + """Test function whose return value uses its arguments.""" + node = builder.extract_node( + """ + def f(x, y): + return x + y + + f(1, 2) #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.Const) + assert inferred[0].value == 3 + + +def test_inner_call() -> None: + """Test function where return value is the result of a separate function call.""" + node = builder.extract_node( + """ + def f(): + return g() + + def g(): + return 1 + + f() #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.Const) + assert inferred[0].value == 1 + + +def test_inner_call_with_const_argument() -> None: + """Test function where return value is the result of a separate function call, + with a constant value passed to the inner function. + """ + node = builder.extract_node( + """ + def f(): + return g(1) + + def g(y): + return y + 2 + + f() #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.Const) + assert inferred[0].value == 3 + + +def test_inner_call_with_dynamic_argument() -> None: + """Test function where return value is the result of a separate function call, + with a dynamic value passed to the inner function. + + Currently, this is Uninferable. + """ + node = builder.extract_node( + """ + def f(x): + return g(x) + + def g(y): + return y + 2 + + f(1) #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert inferred[0] is Uninferable + + +def test_method_const_instance_attr() -> None: + """Test method where the return value is based on an instance attribute with a + constant value. + """ + node = builder.extract_node( + """ + class A: + def __init__(self): + self.x = 1 + + def get_x(self): + return self.x + + A().get_x() #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.Const) + assert inferred[0].value == 1 + + +def test_method_const_instance_attr_multiple() -> None: + """Test method where the return value is based on an instance attribute with + multiple possible constant values, across different methods. + """ + node = builder.extract_node( + """ + class A: + def __init__(self, x): + if x: + self.x = 1 + else: + self.x = 2 + + def set_x(self): + self.x = 3 + + def get_x(self): + return self.x + + A().get_x() #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 3 + assert all(isinstance(node, nodes.Const) for node in inferred) + assert {node.value for node in inferred} == {1, 2, 3} + + +def test_method_const_instance_attr_same_method() -> None: + """Test method where the return value is based on an instance attribute with + multiple possible constant values, including in the method being called. + + Note that even with a simple control flow where the assignment in the method body + is guaranteed to override any previous assignments, all possible constant values + are returned. + """ + node = builder.extract_node( + """ + class A: + def __init__(self, x): + if x: + self.x = 1 + else: + self.x = 2 + + def set_x(self): + self.x = 3 + + def get_x(self): + self.x = 4 + return self.x + + A().get_x() #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 4 + assert all(isinstance(node, nodes.Const) for node in inferred) + assert {node.value for node in inferred} == {1, 2, 3, 4} + + +def test_method_dynamic_instance_attr_1() -> None: + """Test method where the return value is based on an instance attribute with + a dynamically-set value in a different method. + + In this case, the return value is Uninferable. + """ + node = builder.extract_node( + """ + class A: + def __init__(self, x): + self.x = x + + def get_x(self): + return self.x + + A(1).get_x() #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert inferred[0] is Uninferable + + +def test_method_dynamic_instance_attr_2() -> None: + """Test method where the return value is based on an instance attribute with + a dynamically-set value in the same method. + """ + node = builder.extract_node( + """ + class A: + # Note: no initializer, so the only assignment happens in get_x + + def get_x(self, x): + self.x = x + return self.x + + A().get_x(1) #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.Const) + assert inferred[0].value == 1 + + +def test_method_dynamic_instance_attr_3() -> None: + """Test method where the return value is based on an instance attribute with + a dynamically-set value in a different method. + + This is currently Uninferable. + """ + node = builder.extract_node( + """ + class A: + def get_x(self, x): # x is unused + return self.x + + def set_x(self, x): + self.x = x + + A().get_x(10) #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert inferred[0] is Uninferable # not 10! + + +def test_method_dynamic_instance_attr_4() -> None: + """Test method where the return value is based on an instance attribute with + a dynamically-set value in a different method, and is passed a constant value. + + This is currently Uninferable. + """ + node = builder.extract_node( + """ + class A: + # Note: no initializer, so the only assignment happens in get_x + + def get_x(self): + self.set_x(10) + return self.x + + def set_x(self, x): + self.x = x + + A().get_x() #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert inferred[0] is Uninferable + + +def test_method_dynamic_instance_attr_5() -> None: + """Test method where the return value is based on an instance attribute with + a dynamically-set value in a different method, and is passed a constant value. + + But, where the outer and inner functions have the same signature. + + Inspired by https://github.com/PyCQA/pylint/issues/400 + + This is currently Uninferable. + """ + node = builder.extract_node( + """ + class A: + # Note: no initializer, so the only assignment happens in get_x + + def get_x(self, x): + self.set_x(10) + return self.x + + def set_x(self, x): + self.x = x + + A().get_x(1) #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert inferred[0] is Uninferable + + +def test_method_dynamic_instance_attr_6() -> None: + """Test method where the return value is based on an instance attribute with + a dynamically-set value in a different method, and is passed a dynamic value. + + This is currently Uninferable. + """ + node = builder.extract_node( + """ + class A: + # Note: no initializer, so the only assignment happens in get_x + + def get_x(self, x): + self.set_x(x + 1) + return self.x + + def set_x(self, x): + self.x = x + + A().get_x(1) #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert inferred[0] is Uninferable + + +def test_dunder_getitem() -> None: + """Test for the special method __getitem__ (used by Instance.getitem). + + This is currently Uninferable, until we can infer instance attribute values through + constructor calls. + """ + node = builder.extract_node( + """ + class A: + def __init__(self, x): + self.x = x + + def __getitem__(self, i): + return self.x + i + + A(1)[2] #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert inferred[0] is Uninferable + + +def test_instance_method() -> None: + """Tests for instance method, both bound and unbound.""" + nodes_ = builder.extract_node( + """ + class A: + def method(self, x): + return x + + A().method(42) #@ + + # In this case, the 1 argument is bound to self, which is ignored in the method + A.method(1, 42) #@ + """ + ) + + for node in nodes_: + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.Const) + assert inferred[0].value == 42 + + +def test_class_method() -> None: + """Tests for class method calls, both instance and with the class.""" + nodes_ = builder.extract_node( + """ + class A: + @classmethod + def method(cls, x): + return x + + A.method(42) #@ + A().method(42) #@ + + """ + ) + + for node in nodes_: + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.Const), node + assert inferred[0].value == 42 + + +def test_static_method() -> None: + """Tests for static method calls, both instance and with the class.""" + nodes_ = builder.extract_node( + """ + class A: + @staticmethod + def method(x): + return x + + A.method(42) #@ + A().method(42) #@ + """ + ) + + for node in nodes_: + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.Const), node + assert inferred[0].value == 42 + + +def test_instance_method_inherited() -> None: + """Tests for instance methods that are inherited from a superclass. + + Based on https://github.com/PyCQA/astroid/issues/1008. + """ + nodes_ = builder.extract_node( + """ + class A: + def method(self): + return self + + class B(A): + pass + + A().method() #@ + A.method(A()) #@ + + B().method() #@ + B.method(B()) #@ + A.method(B()) #@ + """ + ) + expected_names = ["A", "A", "B", "B", "B"] + for node, expected in zip(nodes_, expected_names): + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], bases.Instance) + assert inferred[0].name == expected + + +def test_class_method_inherited() -> None: + """Tests for class methods that are inherited from a superclass. + + Based on https://github.com/PyCQA/astroid/issues/1008. + """ + nodes_ = builder.extract_node( + """ + class A: + @classmethod + def method(cls): + return cls + + class B(A): + pass + + A().method() #@ + A.method() #@ + + B().method() #@ + B.method() #@ + """ + ) + expected_names = ["A", "A", "B", "B"] + for node, expected in zip(nodes_, expected_names): + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.ClassDef) + assert inferred[0].name == expected + + +def test_chained_attribute_inherited() -> None: + """Tests for class methods that are inherited from a superclass. + + Based on https://github.com/PyCQA/pylint/issues/4220. + """ + node = builder.extract_node( + """ + class A: + def f(self): + return 42 + + + class B(A): + def __init__(self): + self.a = A() + result = self.a.f() + + def f(self): + pass + + + B().a.f() #@ + """ + ) + assert isinstance(node, nodes.NodeNG) + inferred = node.inferred() + assert len(inferred) == 1 + assert isinstance(inferred[0], nodes.Const) + assert inferred[0].value == 42 |