summaryrefslogtreecommitdiff
path: root/tests/testutils/test_lint_module_output_update.py
blob: a8bfc5fde706fc587207e0b9f7de5d2c6645196e (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
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt

# pylint: disable=redefined-outer-name

from __future__ import annotations

import shutil
from collections.abc import Callable
from pathlib import Path

import pytest

from pylint.constants import IS_PYPY, PY38_PLUS, PY39_PLUS
from pylint.testutils import FunctionalTestFile, LintModuleTest
from pylint.testutils.functional import LintModuleOutputUpdate

FIXTURE_DIRECTORY = Path(__file__).parent / "data/functional"
DIRECTORIES = list(FIXTURE_DIRECTORY.iterdir())


@pytest.fixture()
def lint_module_fixture(
    tmp_path: Path,
) -> Callable[[str], tuple[Path, Path, LintModuleOutputUpdate]]:
    def inner(base: str) -> tuple[Path, Path, LintModuleOutputUpdate]:
        filename = tmp_path / f"{base}.py"
        expected_output_file = tmp_path / f"{base}.txt"
        lmou = LintModuleOutputUpdate(
            test_file=FunctionalTestFile(str(tmp_path), str(filename))
        )
        return filename, expected_output_file, lmou

    return inner


@pytest.mark.skipif(PY38_PLUS, reason="Requires python 3.7 or lower")
def test_not_py38(tmp_path: Path) -> None:
    with pytest.raises(RuntimeError, match="new AST parser"):
        LintModuleOutputUpdate(
            test_file=FunctionalTestFile(str(tmp_path), str(tmp_path / "filename.py"))
        )


@pytest.mark.skipif(not PY38_PLUS, reason="Requires python 3.8 or superior")
def test_lint_module_output_update_fail_before(
    lint_module_fixture: Callable[[str], tuple[Path, Path, LintModuleOutputUpdate]]
) -> None:
    """There is a fail before the output need to be updated."""
    filename, expected_output_file, lmou = lint_module_fixture("foo")
    filename.write_text("", encoding="utf8")
    assert not expected_output_file.exists()
    with pytest.raises(AssertionError, match="1: disallowed-name"):
        lmou.runTest()
    assert not expected_output_file.exists()


@pytest.mark.skipif(not PY38_PLUS, reason="Requires python 3.8 or superior")
def test_lint_module_output_update_effective(
    lint_module_fixture: Callable[[str], tuple[Path, Path, LintModuleOutputUpdate]]
) -> None:
    """The file is updated following a successful tests with wrong output."""
    filename, expected_output_file, lmou = lint_module_fixture("foo")
    filename.write_text("# [disallowed-name]\n", encoding="utf8")
    lmou.runTest()
    assert (expected_output_file).exists()
    assert (
        expected_output_file.read_text(encoding="utf8")
        == 'disallowed-name:1:0:None:None::"Disallowed name ""foo""":HIGH\n'
    )


@pytest.mark.skipif(not PY38_PLUS, reason="Requires python 3.8 or superior")
def test_lint_module_output_update_remove_useless_txt(
    lint_module_fixture: Callable[[str], tuple[Path, Path, LintModuleOutputUpdate]]
) -> None:
    """The file is updated following a successful tests with wrong output."""
    filename, expected_output_file, lmou = lint_module_fixture("fine_name")
    expected_output_file.write_text("", encoding="utf8")
    filename.write_text("", encoding="utf8")
    lmou.runTest()
    assert not expected_output_file.exists()


@pytest.mark.skipif(
    not PY38_PLUS or (IS_PYPY and not PY39_PLUS),
    reason="Requires accurate 'end_col' value to update output",
)
@pytest.mark.parametrize(
    "directory_path", DIRECTORIES, ids=[str(p) for p in DIRECTORIES]
)
def test_update_of_functional_output(directory_path: Path, tmp_path: Path) -> None:
    """Functional test for the functional tests' helper."""

    def _check_expected_output(_ftf: FunctionalTestFile) -> None:
        new_output_path = _ftf.expected_output
        assert Path(
            new_output_path
        ).exists(), "The expected output file does not exists"
        with open(new_output_path, encoding="utf8") as f:
            new_output = f.read()
        assert (
            new_output == "exec-used:7:0:7:14::Use of exec:UNDEFINED\n"
        ), f"The content was wrongly updated in {new_output_path}"

    def _assert_behavior_is_correct(
        _ftf: FunctionalTestFile,
        _lint_module: LintModuleTest,
        _lint_module_output_update: LintModuleOutputUpdate,
        _new_path: Path,
    ) -> None:
        new_path_str = str(_new_path)
        if "wrong_test" in new_path_str:
            expected = r'Wrong message\(s\) raised for "exec_used.py"'
            with pytest.raises(AssertionError, match=expected):
                _lint_module.runTest()
            # When the tests are wrong we do not update the output at all
            # and the test should fail
            with pytest.raises(AssertionError, match=expected):
                _lint_module_output_update.runTest()
        elif "ok_test" in new_path_str:
            if any(f"{x}_output" in new_path_str for x in ("wrong", "no", "broken")):
                with pytest.raises(
                    AssertionError, match='Wrong output for "exec_used.txt"'
                ):
                    _lint_module.runTest()
            elif "ok_output" in new_path_str:
                _lint_module.runTest()
                _check_expected_output(_ftf)
            else:
                raise AssertionError(f"Unhandled test case: {new_path_str}")

            # When the tests are ok we update the output whatever it's state
            # was originally
            _lint_module_output_update.runTest()
            _check_expected_output(_ftf)
        else:
            raise AssertionError(
                f"Do not pollute '{FIXTURE_DIRECTORY}' with unrelated "
                f"or badly named test files."
            )

    new_path = tmp_path / directory_path.name
    shutil.copytree(directory_path, new_path)
    for filename in new_path.iterdir():
        if filename.suffix != ".py":
            continue
        ftf = FunctionalTestFile(directory=str(new_path), filename=filename.name)
        # Standard functional test helper
        lint_module = LintModuleTest(ftf)
        # Functional test helper that automatically update the output
        lint_module_output_update = LintModuleOutputUpdate(ftf)

        _assert_behavior_is_correct(
            ftf, lint_module, lint_module_output_update, new_path
        )