Skip to content

Commit bdc5604

Browse files
authored
Fixes to reports.py -- don't crash on control characters, ensure directories earlier (#5885)
Fixes #5884 (both parts). Also fixes testcmdline.py to report unexpected stderr or exit code even when there's an expected output file.
1 parent 74c5070 commit bdc5604

File tree

3 files changed

+24
-17
lines changed

3 files changed

+24
-17
lines changed

mypy/report.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ def finish(self) -> None:
9090
class AbstractReporter(metaclass=ABCMeta):
9191
def __init__(self, reports: Reports, output_dir: str) -> None:
9292
self.output_dir = output_dir
93+
if output_dir != '<memory>':
94+
stats.ensure_dir_exists(output_dir)
9395

9496
@abstractmethod
9597
def on_file(self, tree: MypyFile, type_map: Dict[Expression, Type], options: Options) -> None:
@@ -124,8 +126,6 @@ def __init__(self, reports: Reports, output_dir: str) -> None:
124126
super().__init__(reports, output_dir)
125127
self.counts = {} # type: Dict[str, Tuple[int, int, int, int]]
126128

127-
stats.ensure_dir_exists(output_dir)
128-
129129
def on_file(self,
130130
tree: MypyFile,
131131
type_map: Dict[Expression, Type],
@@ -170,7 +170,6 @@ def __init__(self, reports: Reports, output_dir: str) -> None:
170170
super().__init__(reports, output_dir)
171171
self.counts = {} # type: Dict[str, Tuple[int, int]]
172172
self.any_types_counter = {} # type: Dict[str, typing.Counter[int]]
173-
stats.ensure_dir_exists(output_dir)
174173

175174
def on_file(self,
176175
tree: MypyFile,
@@ -361,8 +360,6 @@ def __init__(self, reports: Reports, output_dir: str) -> None:
361360
super().__init__(reports, output_dir)
362361
self.lines_covered = {} # type: Dict[str, List[int]]
363362

364-
stats.ensure_dir_exists(output_dir)
365-
366363
def on_file(self,
367364
tree: MypyFile,
368365
type_map: Dict[Expression, Type],
@@ -418,6 +415,10 @@ def __init__(self, reports: Reports, output_dir: str) -> None:
418415
self.last_xml = None # type: Optional[Any]
419416
self.files = [] # type: List[FileInfo]
420417

418+
# XML doesn't like control characters, but they are sometimes
419+
# legal in source code (e.g. comments, string literals).
420+
control_fixer = str.maketrans(''.join(chr(i) for i in range(32)), '?' * 32)
421+
421422
def on_file(self,
422423
tree: MypyFile,
423424
type_map: Dict[Expression, Type],
@@ -446,7 +447,7 @@ def on_file(self,
446447
etree.SubElement(root, 'line',
447448
number=str(lineno),
448449
precision=stats.precision_names[status],
449-
content=line_text.rstrip('\n'),
450+
content=line_text.rstrip('\n').translate(self.control_fixer),
450451
any_info=self._get_any_info_for_line(visitor, lineno))
451452
# Assumes a layout similar to what XmlReporter uses.
452453
xslt_path = os.path.relpath('mypy-html.xslt', path)
@@ -737,7 +738,6 @@ def on_finish(self) -> None:
737738
last_xml = self.memory_xml.last_xml
738739
assert last_xml is not None
739740
out_path = os.path.join(self.output_dir, 'index.txt')
740-
stats.ensure_dir_exists(os.path.dirname(out_path))
741741
transformed_txt = bytes(self.xslt_txt(last_xml))
742742
with open(out_path, 'wb') as out_file:
743743
out_file.write(transformed_txt)

mypy/test/testcmdline.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,31 @@ def test_python_cmdline(testcase: DataDrivenTestCase) -> None:
5252
env['PYTHONPATH'] = PREFIX
5353
process = subprocess.Popen(fixed + args,
5454
stdout=subprocess.PIPE,
55-
stderr=subprocess.STDOUT,
55+
stderr=subprocess.PIPE,
5656
cwd=test_temp_dir,
5757
env=env)
58-
outb = process.stdout.read()
58+
outb, errb = process.communicate()
59+
result = process.returncode
5960
# Split output into lines.
6061
out = [s.rstrip('\n\r') for s in str(outb, 'utf8').splitlines()]
62+
err = [s.rstrip('\n\r') for s in str(errb, 'utf8').splitlines()]
6163

6264
if "PYCHARM_HOSTED" in os.environ:
63-
pos = next((p for p, i in enumerate(out) if i.startswith('pydev debugger: ')), None)
64-
if pos is not None:
65-
del out[pos] # the attaching debugger message itself
66-
del out[pos] # plus the extra new line added
65+
for pos, line in enumerate(err):
66+
if line.startswith('pydev debugger: '):
67+
# Delete the attaching debugger message itself, plus the extra newline added.
68+
del err[pos:pos + 2]
69+
break
6770

68-
result = process.wait()
6971
# Remove temp file.
7072
os.remove(program_path)
7173
# Compare actual output to expected.
7274
if testcase.output_files:
75+
# Ignore stdout, but we insist on empty stderr and zero status.
76+
if err or result:
77+
raise AssertionError(
78+
'Expected zero status and empty stderr, got %d and\n%s' %
79+
(result, '\n'.join(err + out)))
7380
for path, expected_content in testcase.output_files:
7481
if not os.path.exists(path):
7582
raise AssertionError(
@@ -85,7 +92,7 @@ def test_python_cmdline(testcase: DataDrivenTestCase) -> None:
8592
'Output file {} did not match its expected output'.format(
8693
path))
8794
else:
88-
out = normalize_error_messages(out)
95+
out = normalize_error_messages(err + out)
8996
obvious_result = 1 if out else 0
9097
if obvious_result != result:
9198
out.append('== Return code: {}'.format(result))

test-data/unit/reports.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def untyped_function():
7777
# cmd: mypy --html-report report n.py
7878
[file n.py]
7979
class A(object):
80-
pass
80+
pass # hex 1f here: ()
8181

8282
[file report/mypy-html.css]
8383
[file report/index.html]
@@ -96,7 +96,7 @@ class A(object):
9696
<span id="L2" class="lineno"><a class="lineno" href="#L2">2</a></span>
9797
</pre></td>
9898
<td class="table-code"><pre><span class="line-empty" title="No Anys on this line!">class A(object):</span>
99-
<span class="line-empty" title="No Anys on this line!"> pass</span>
99+
<span class="line-empty" title="No Anys on this line!"> pass # hex 1f here: (?)</span>
100100
</pre></td>
101101
</tr></tbody>
102102
</table>

0 commit comments

Comments
 (0)