summaryrefslogtreecommitdiff
path: root/tests/test_raw_building.py
blob: 093e003cc0cb6d0c6e0f0cb7dc3fcc97b1bbaeb3 (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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
"""
'tests.testdata.python3.data.fake_module_with_warnings' and
'tests.testdata.python3.data.fake_module_with_warnings' are fake modules
to simulate issues in unittest below
"""

# 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

from __future__ import annotations

import logging
import os
import sys
import types
import unittest
from typing import Any
from unittest import mock

import _io
import pytest

import tests.testdata.python3.data.fake_module_with_broken_getattr as fm_getattr
import tests.testdata.python3.data.fake_module_with_warnings as fm
from astroid.builder import AstroidBuilder
from astroid.const import IS_PYPY
from astroid.raw_building import (
    attach_dummy_node,
    build_class,
    build_from_import,
    build_function,
    build_module,
)


class RawBuildingTC(unittest.TestCase):
    def test_attach_dummy_node(self) -> None:
        node = build_module("MyModule")
        attach_dummy_node(node, "DummyNode")
        self.assertEqual(1, len(list(node.get_children())))

    def test_build_module(self) -> None:
        node = build_module("MyModule")
        self.assertEqual(node.name, "MyModule")
        self.assertEqual(node.pure_python, False)
        self.assertEqual(node.package, False)
        self.assertEqual(node.parent, None)

    def test_build_class(self) -> None:
        node = build_class("MyClass")
        self.assertEqual(node.name, "MyClass")
        self.assertEqual(node.doc_node, None)

    def test_build_function(self) -> None:
        node = build_function("MyFunction")
        self.assertEqual(node.name, "MyFunction")
        self.assertEqual(node.doc_node, None)

    def test_build_function_args(self) -> None:
        args = ["myArgs1", "myArgs2"]
        node = build_function("MyFunction", args)
        self.assertEqual("myArgs1", node.args.args[0].name)
        self.assertEqual("myArgs2", node.args.args[1].name)
        self.assertEqual(2, len(node.args.args))

    def test_build_function_defaults(self) -> None:
        defaults = ["defaults1", "defaults2"]
        node = build_function(name="MyFunction", args=None, defaults=defaults)
        self.assertEqual(2, len(node.args.defaults))

    def test_build_function_posonlyargs(self) -> None:
        node = build_function(name="MyFunction", posonlyargs=["a", "b"])
        self.assertEqual(2, len(node.args.posonlyargs))

    def test_build_function_kwonlyargs(self) -> None:
        node = build_function(name="MyFunction", kwonlyargs=["a", "b"])
        assert len(node.args.kwonlyargs) == 2
        assert node.args.kwonlyargs[0].name == "a"
        assert node.args.kwonlyargs[1].name == "b"

    def test_build_from_import(self) -> None:
        names = ["exceptions, inference, inspector"]
        node = build_from_import("astroid", names)
        self.assertEqual(len(names), len(node.names))

    @unittest.skipIf(IS_PYPY, "Only affects CPython")
    def test_io_is__io(self):
        # _io module calls itself io. This leads
        # to cyclic dependencies when astroid tries to resolve
        # what io.BufferedReader is. The code that handles this
        # is in astroid.raw_building.imported_member, which verifies
        # the true name of the module.
        builder = AstroidBuilder()
        module = builder.inspect_build(_io)
        buffered_reader = module.getattr("BufferedReader")[0]
        self.assertEqual(buffered_reader.root().name, "io")

    def test_build_function_deepinspect_deprecation(self) -> None:
        # Tests https://github.com/pylint-dev/astroid/issues/1717
        # When astroid deep inspection of modules raises
        # attribute errors when getting all attributes
        # Create a mock module to simulate a Cython module
        m = types.ModuleType("test")

        # Attach a mock of pandas with the same behavior
        m.pd = fm

        # This should not raise an exception
        AstroidBuilder().module_build(m, "test")

    def test_module_object_with_broken_getattr(self) -> None:
        # Tests https://github.com/pylint-dev/astroid/issues/1958
        # When astroid deep inspection of modules raises
        # errors when using hasattr().

        # This should not raise an exception
        AstroidBuilder().inspect_build(fm_getattr, "test")


@pytest.mark.skipif(
    "posix" not in sys.builtin_module_names, reason="Platform doesn't support posix"
)
def test_build_module_getattr_catch_output(
    capsys: pytest.CaptureFixture[str],
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Catch stdout and stderr in module __getattr__ calls when building a module.

    Usually raised by DeprecationWarning or FutureWarning.
    """
    caplog.set_level(logging.INFO)
    original_sys = sys.modules
    original_module = sys.modules["posix"]
    expected_out = "INFO (TEST): Welcome to posix!"
    expected_err = "WARNING (TEST): Monkey-patched version of posix - module getattr"

    class CustomGetattr:
        def __getattr__(self, name: str) -> Any:
            print(f"{expected_out}")
            print(expected_err, file=sys.stderr)
            return getattr(original_module, name)

    def mocked_sys_modules_getitem(name: str) -> types.ModuleType | CustomGetattr:
        if name != "posix":
            return original_sys[name]
        return CustomGetattr()

    with mock.patch("astroid.raw_building.sys.modules") as sys_mock:
        sys_mock.__getitem__.side_effect = mocked_sys_modules_getitem
        builder = AstroidBuilder()
        builder.inspect_build(os)

    out, err = capsys.readouterr()
    assert expected_out in caplog.text
    assert expected_err in caplog.text
    assert not out
    assert not err