summaryrefslogtreecommitdiff
path: root/tests/checkers/unittest_stdlib.py
blob: bbd8e044b1da50a42f018d59511cb2508018b2d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# Copyright (c) 2015-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
# Copyright (c) 2015 Cezar <celnazli@bitdefender.com>
# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
# Copyright (c) 2017 Martin <MartinBasti@users.noreply.github.com>
# Copyright (c) 2019-2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
# Copyright (c) 2020 hippo91 <guillaume.peillex@gmail.com>
# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>

# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE

import contextlib
from typing import Any, Callable, Iterator, Optional, Union

import astroid
from astroid import nodes
from astroid.manager import AstroidManager
from astroid.nodes.node_classes import AssignAttr, Name

from pylint.checkers import stdlib
from pylint.interfaces import UNDEFINED
from pylint.testutils import CheckerTestCase, Message


@contextlib.contextmanager
def _add_transform(
    manager: AstroidManager,
    node: type,
    transform: Callable,
    predicate: Optional[Any] = None,
) -> Iterator:
    manager.register_transform(node, transform, predicate)
    try:
        yield
    finally:
        manager.unregister_transform(node, transform, predicate)


class TestStdlibChecker(CheckerTestCase):
    CHECKER_CLASS = stdlib.StdlibChecker

    def test_deprecated_no_qname_on_unexpected_nodes(self) -> None:
        # Test that we don't crash on nodes which don't have
        # a qname method. While this test might seem weird since
        # it uses a transform, it's actually testing a crash that
        # happened in production, but there was no way to retrieve
        # the code for which this occurred (how an AssignAttr
        # got to be the result of a function inference
        # beats me..)

        def infer_func(
            node: Name, context: Optional[Any] = None
        ) -> Iterator[
            Union[Iterator, Iterator[AssignAttr]]
        ]:  # pylint: disable=unused-argument
            new_node = nodes.AssignAttr()
            new_node.parent = node
            yield new_node

        manager = astroid.MANAGER
        transform = astroid.inference_tip(infer_func)
        with _add_transform(manager, nodes.Name, transform):
            node = astroid.extract_node(
                """
            call_something()
            """
            )
            with self.assertNoMessages():
                self.checker.visit_call(node)

    def test_copy_environ(self) -> None:
        # shallow copy of os.environ should be reported
        node = astroid.extract_node(
            """
        import copy, os
        copy.copy(os.environ)
        """
        )
        with self.assertAddsMessages(
            Message(msg_id="shallow-copy-environ", node=node, confidence=UNDEFINED)
        ):
            self.checker.visit_call(node)

    def test_copy_environ_hidden(self) -> None:
        # shallow copy of os.environ should be reported
        # hide function names to be sure that checker is not just matching text
        node = astroid.extract_node(
            """
        from copy import copy as test_cp
        import os as o
        test_cp(o.environ)
        """
        )
        with self.assertAddsMessages(
            Message(msg_id="shallow-copy-environ", node=node, confidence=UNDEFINED)
        ):
            self.checker.visit_call(node)

    def test_copy_dict(self) -> None:
        # copy of dict is OK
        node = astroid.extract_node(
            """
        import copy
        test_dict = {}
        copy.copy(test_dict)
        """
        )
        with self.assertNoMessages():
            self.checker.visit_call(node)

    def test_copy_uninferable(self) -> None:
        # copy of uninferable object should not raise exception, nor make
        # the checker crash
        node = astroid.extract_node(
            """
        import copy
        from missing_library import MissingObject
        copy.copy(MissingObject)
        """
        )
        with self.assertNoMessages():
            self.checker.visit_call(node)

    def test_deepcopy_environ(self) -> None:
        # deepcopy of os.environ is OK
        node = astroid.extract_node(
            """
        import copy, os
        copy.deepcopy(os.environ)
        """
        )
        with self.assertNoMessages():
            self.checker.visit_call(node)