File: test_type_annotations.py

package info (click to toggle)
python-typeguard 4.4.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 544 kB
  • sloc: python: 6,271; makefile: 5
file content (110 lines) | stat: -rw-r--r-- 2,901 bytes parent folder | download | duplicates (2)
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
import json
import os
import platform
import subprocess

import pytest

POSITIVE_FILE = "positive.py"
NEGATIVE_FILE = "negative.py"

pytestmark = [
    pytest.mark.skipif(
        platform.python_implementation() == "PyPy",
        reason="MyPy does not work with PyPy yet",
    )
]


def get_mypy_cmd(filename: str) -> list[str]:
    return ["mypy", "-O", "json", "--strict", filename]


def get_negative_mypy_output() -> str:
    """
    Get the output from running mypy on the negative examples file.
    """
    process = subprocess.run(
        get_mypy_cmd(NEGATIVE_FILE), stdout=subprocess.PIPE, check=False
    )
    output = process.stdout.decode()
    assert output
    return output


def get_expected_errors() -> dict[int, str]:
    """
    Extract the expected errors from comments in the negative examples file.
    """
    with open(NEGATIVE_FILE) as f:
        lines = f.readlines()

    expected = {}

    for idx, line in enumerate(lines):
        line = line.rstrip()
        if "# error" in line:
            expected[idx + 1] = line[line.index("# error") + 9 :]

    # Sanity check.  Should update if negative.py changes.
    assert len(expected) == 9
    return expected


def get_mypy_errors() -> dict[int, str]:
    """
    Extract the errors from running mypy on the negative examples file.
    """
    mypy_output = get_negative_mypy_output()

    got = {}
    for line in mypy_output.splitlines():
        error = json.loads(line)
        got[error["line"]] = f"{error['message']}  [{error['code']}]"

    return got


@pytest.fixture
def chdir_local() -> None:
    """
    Change to the local directory. This is so that mypy treats imports from
    typeguard as external imports instead of source code (which is handled
    differently by mypy).
    """
    os.chdir(os.path.dirname(__file__))


@pytest.mark.usefixtures("chdir_local")
def test_positive() -> None:
    """
    Run mypy on the positive test file.  There should be no errors.
    """
    subprocess.check_call(get_mypy_cmd(POSITIVE_FILE))


@pytest.mark.usefixtures("chdir_local")
def test_negative() -> None:
    """
    Run mypy on the negative test file. This should fail. The errors from mypy
    should match the comments in the file.
    """
    got_errors = get_mypy_errors()
    expected_errors = get_expected_errors()

    if set(got_errors) != set(expected_errors):
        raise RuntimeError(
            f"Expected error lines {set(expected_errors)} does not "
            + f"match mypy error lines {set(got_errors)}."
        )

    mismatches = [
        (idx, expected_errors[idx], got_errors[idx])
        for idx in expected_errors
        if expected_errors[idx] != got_errors[idx]
    ]
    for idx, expected, got in mismatches:
        print(f"Line {idx}", f"Expected: {expected}", f"Got:      {got}", sep="\n\t")

    if mismatches:
        raise RuntimeError("Error messages changed")