/
testcmdline.py
148 lines (121 loc) · 4.77 KB
/
testcmdline.py
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
"""Test cases for the command line.
To begin we test that "mypy <directory>[/]" always recurses down the
whole tree.
"""
from __future__ import annotations
import os
import re
import subprocess
import sys
from mypy.test.config import PREFIX, test_temp_dir
from mypy.test.data import DataDrivenTestCase, DataSuite
from mypy.test.helpers import (
assert_string_arrays_equal,
check_test_output_files,
normalize_error_messages,
)
try:
import lxml # type: ignore[import]
except ImportError:
lxml = None
import pytest
# Path to Python 3 interpreter
python3_path = sys.executable
# Files containing test case descriptions.
cmdline_files = ["cmdline.test", "cmdline.pyproject.test", "reports.test", "envvars.test"]
class PythonCmdlineSuite(DataSuite):
files = cmdline_files
native_sep = True
def run_case(self, testcase: DataDrivenTestCase) -> None:
if lxml is None and os.path.basename(testcase.file) == "reports.test":
pytest.skip("Cannot import lxml. Is it installed?")
for step in [1] + sorted(testcase.output2):
test_python_cmdline(testcase, step)
def test_python_cmdline(testcase: DataDrivenTestCase, step: int) -> None:
assert testcase.old_cwd is not None, "test was not properly set up"
# Write the program to a file.
program = "_program.py"
program_path = os.path.join(test_temp_dir, program)
with open(program_path, "w", encoding="utf8") as file:
for s in testcase.input:
file.write(f"{s}\n")
args = parse_args(testcase.input[0])
custom_cwd = parse_cwd(testcase.input[1]) if len(testcase.input) > 1 else None
args.append("--show-traceback")
if "--error-summary" not in args:
args.append("--no-error-summary")
if "--show-error-codes" not in args:
args.append("--hide-error-codes")
if "--disallow-empty-bodies" not in args:
args.append("--allow-empty-bodies")
# Type check the program.
fixed = [python3_path, "-m", "mypy"]
env = os.environ.copy()
env.pop("COLUMNS", None)
extra_path = os.path.join(os.path.abspath(test_temp_dir), "pypath")
env["PYTHONPATH"] = PREFIX
if os.path.isdir(extra_path):
env["PYTHONPATH"] += os.pathsep + extra_path
cwd = os.path.join(test_temp_dir, custom_cwd or "")
args = [arg.replace("$CWD", os.path.abspath(cwd)) for arg in args]
process = subprocess.Popen(
fixed + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env
)
outb, errb = process.communicate()
result = process.returncode
# Split output into lines.
out = [s.rstrip("\n\r") for s in str(outb, "utf8").splitlines()]
err = [s.rstrip("\n\r") for s in str(errb, "utf8").splitlines()]
if "PYCHARM_HOSTED" in os.environ:
for pos, line in enumerate(err):
if line.startswith("pydev debugger: "):
# Delete the attaching debugger message itself, plus the extra newline added.
del err[pos : pos + 2]
break
# Remove temp file.
os.remove(program_path)
# Compare actual output to expected.
if testcase.output_files:
# Ignore stdout, but we insist on empty stderr and zero status.
if err or result:
raise AssertionError(
"Expected zero status and empty stderr%s, got %d and\n%s"
% (" on step %d" % step if testcase.output2 else "", result, "\n".join(err + out))
)
check_test_output_files(testcase, step)
else:
if testcase.normalize_output:
out = normalize_error_messages(err + out)
obvious_result = 1 if out else 0
if obvious_result != result:
out.append(f"== Return code: {result}")
expected_out = testcase.output if step == 1 else testcase.output2[step]
# Strip "tmp/" out of the test so that # E: works...
expected_out = [s.replace("tmp" + os.sep, "") for s in expected_out]
assert_string_arrays_equal(
expected_out,
out,
"Invalid output ({}, line {}){}".format(
testcase.file, testcase.line, " on step %d" % step if testcase.output2 else ""
),
)
def parse_args(line: str) -> list[str]:
"""Parse the first line of the program for the command line.
This should have the form
# cmd: mypy <options>
For example:
# cmd: mypy pkg/
"""
m = re.match("# cmd: mypy (.*)$", line)
if not m:
return [] # No args; mypy will spit out an error.
return m.group(1).split()
def parse_cwd(line: str) -> str | None:
"""Parse the second line of the program for the command line.
This should have the form
# cwd: <directory>
For example:
# cwd: main/subdir
"""
m = re.match("# cwd: (.*)$", line)
return m.group(1) if m else None