forked from HypothesisWorks/hypothesis
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfix_doctests.py
126 lines (108 loc) · 4.52 KB
/
fix_doctests.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
#!/usr/bin/env python3
# coding=utf-8
#
# This file is part of Hypothesis, which may be found at
# https://github.com/HypothesisWorks/hypothesis-python
#
# Most of this work is copyright (C) 2013-2018 David R. MacIver
# (david@drmaciver.com), but it contains contributions by others. See
# CONTRIBUTING.rst for a full list of people who may hold copyright, and
# consult the git log if you need to determine who owns an individual
# contribution.
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at http://mozilla.org/MPL/2.0/.
#
# END HEADER
from __future__ import division, print_function, absolute_import
import os
import re
import sys
from subprocess import PIPE, run
from collections import defaultdict
import hypothesistooling as tools
class FailingExample(object):
def __init__(self, chunk):
"""Turn a chunk of text into an object representing the test."""
location, *lines = [l + '\n' for l in chunk.split('\n') if l.strip()]
self.location = location.strip()
pattern = 'File "(.+?)", line (\d+?), in .+'
file, line = re.match(pattern, self.location).groups()
self.file = os.path.join('docs', file)
self.line = int(line) + 1
got = lines.index('Got:\n')
self.expected_lines = lines[lines.index('Expected:\n') + 1:got]
self.got_lines = lines[got + 1:]
self.checked_ok = None
self.adjust()
@property
def indices(self):
return slice(self.line, self.line + len(self.expected_lines))
def adjust(self):
with open(self.file) as f:
lines = f.readlines()
# The raw line number is the first line of *input*, so adjust to
# first line of output by skipping lines which start with a prompt
while self.line < len(lines):
if lines[self.line].strip()[:4] not in ('>>> ', '... '):
break
self.line += 1
# Sadly the filename and line number for doctests in docstrings is
# wrong - see https://github.com/sphinx-doc/sphinx/issues/4223
# Luckily, we can just cheat because they're all in one file for now!
# (good luck if this changes without an upstream fix...)
if lines[self.indices] != self.expected_lines:
self.file = 'src/hypothesis/strategies.py'
with open(self.file) as f:
lines = f.readlines()
self.line = 0
while self.expected_lines[0] in lines:
self.line = lines[self.line:].index(self.expected_lines[0])
if lines[self.indices] == self.expected_lines:
break
# Finally, set the flag for location quality
self.checked_ok = lines[self.indices] == self.expected_lines
def __repr__(self):
return '{}\nExpected: {!r:.60}\nGot: {!r:.60}'.format(
self.location, self.expected, self.got)
def get_doctest_output():
# Return a dict of filename: list of examples, sorted from last to first
# so that replacing them in sequence works
command = run(['sphinx-build', '-b', 'doctest', 'docs', 'docs/_build'],
stdout=PIPE, stderr=PIPE, encoding='utf-8')
output = [FailingExample(c) for c in command.stdout.split('*' * 70)
if c.strip().startswith('File "')]
if not all(ex.checked_ok for ex in output):
broken = '\n'.join(ex.location for ex in output if not ex.checked_ok)
print('Could not find some tests:\n' + broken)
sys.exit(1)
tests = defaultdict(set)
for ex in output:
tests[ex.file].add(ex)
return {fname: sorted(examples, key=lambda x: x.line, reverse=True)
for fname, examples in tests.items()}
def main():
os.chdir(tools.ROOT)
failing = get_doctest_output()
if not failing:
print('All doctests are OK')
sys.exit(0)
if tools.has_uncommitted_changes('.'):
print('Cannot fix doctests in place with uncommited changes')
sys.exit(1)
for fname, examples in failing.items():
with open(fname) as f:
lines = f.readlines()
for ex in examples:
lines[ex.indices] = ex.got_lines
with open(fname, 'w') as f:
f.writelines(lines)
still_failing = get_doctest_output()
if still_failing:
print('Fixes failed: script broken or flaky tests.\n', still_failing)
sys.exit(1)
print('All failing doctests have been fixed.')
sys.exit(0)
if __name__ == '__main__':
main()