-
Notifications
You must be signed in to change notification settings - Fork 16
/
test.py
executable file
·181 lines (154 loc) · 5.71 KB
/
test.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#!/usr/bin/env python
# Python Standard Library
import codeop
import doctest
import glob
import os
import shutil
import platform
import re
import sys
import tempfile
# Third-Party Libraries
import strictyaml
# Test Files
# ------------------------------------------------------------------------------
mkdocs_nav = strictyaml.load(open("mkdocs.yml").read())["nav"].data
test_files = ["mkdocs/" + list(item.values())[0] for item in mkdocs_nav]
# Sandbox the Test Files
# ------------------------------------------------------------------------------
# This is required:
# - to tweak the files before the tests,
# - to avoid the generation of artifacts (generated by the test code)
# in the current directory.
tmp_dir = tempfile.mkdtemp() # TODO: clean-up this directory
for filename in test_files:
target_file = os.path.join(tmp_dir, filename)
target_dir = os.path.dirname(target_file)
os.makedirs(target_dir, exist_ok=True)
shutil.copy(filename, target_file)
# Tweak the Test Files
# ------------------------------------------------------------------------------
# For each file, find the python fences, see if they are in interpreter mode
# or "code" mode. If they are in code mode, add the prompts then remove the
# fences and indent the code lines.
def promptize(src):
"Add >>> or ... prompts to Python code"
cc = codeop.compile_command # symbol="single" (the default here)
# is required to deal with if / else constructs properly
# (without going back to the ">>> " prompt after the if clause).
lines = src.splitlines()
output = []
chunk = []
for line in lines:
if chunk == []: # new start
output.append(">>> " + line)
else:
output.append("... " + line)
chunk.append(line)
try:
code = cc("\n".join(chunk))
if code is not None: # full statement
chunk = [] # start over
except: # pragma: no cover
raise
assert len(lines) == len(output)
return "\n".join(output)
def tweak(src):
# Find code blocks with python fences,
# add prompts when necessary,
# then transform them into indented code blocks.
lines = src.splitlines()
chunks = {}
python, sep, start, end, code = False, None, None, None, []
for i, line in enumerate(lines):
if ( # match at least three backquotes, optional space, then python or pycon
re.match(r"\s*(`|~){3,}\s*py(c|th)on", line)
):
sep = line.strip()[0]
assert sep in "`~"
start = i
code.append("")
python = True
elif python is True and 3 * sep in line:
sep = None
end = i + 1
code.append("")
python = False
assert end - start == len(code)
chunks[(start, end)] = code
code = []
elif code != []:
code.append(line)
for loc, code in chunks.items():
chunk = "\n".join(code[1:-1]) # dont promptize initial and final newline
if not chunk.strip().startswith(">>> "): # prompts are missing
code[1:-1] = promptize(chunk).splitlines()
code = [4 * " " + line for line in code]
chunks[loc] = code
for (i, j), code in chunks.items():
lines[i:j] = code
new_src = "\n".join(lines)
return new_src
cwd = os.getcwd()
os.chdir(tmp_dir)
for filename in test_files:
with open(filename, encoding="utf-8") as file:
src = file.read()
if filename == "mkdocs/markdown.md":
src = "``` python\nimport pandoc\n```\n\n" + src
# Need to fetch markdown content, unident, wrap in ``` python block
# and add the text = """ stuff.
# Then add a ``` pycon block with repr stuff
pattern = r'=== "Markdown"(?:(?:\n)|(?:[ ]{8}.*\n))*'
pattern += r'=== "Python"(?:(?:\n)|(?:[ ]{8}.*\n))*'
found = re.findall(pattern, src)
ritems = []
for item in found:
if "```" in item:
sep = "~~~"
else:
sep = "```"
ritem = item.strip()
ritem = re.sub("^[ ]{8}", "", ritem, flags=re.MULTILINE)
ritem = ritem.replace(
'=== "Markdown"\n\n', f'=== "Markdown"\n\n{sep} python\ntext = \\\nr"""'
)
ritem = ritem.replace(
'\n=== "Python"\n',
f'"""\n{sep}\n\n=== "Python"\n\n{sep} pycon\n>>> pandoc.read(text)',
)
ritem += f"\n{sep}\n\n"
ritems.append(ritem)
for item, ritem in zip(found, ritems):
src = src.replace(item, ritem)
# and in any case, "normal tweak"
src = tweak(src)
with open(filename, "w", encoding="utf-8") as file:
file.write(src)
# Run the Tests
# ------------------------------------------------------------------------------
verbose = "-v" in sys.argv or "--verbose" in sys.argv
fails = 0
tests = 0
for filename in test_files:
options = {"module_relative": False, "verbose": verbose}
# Relax the tests to deal with test files that have a '\n' line break
# (Linux flavor) which does not match the pandoc line break on Windows
# (Windows flavor : '\r\n').
# The proper way to deal with this would be to convert the test files
# beforehand on Windows.
if platform.system() == "Windows":
options["optionflags"] = doctest.NORMALIZE_WHITESPACE
_fails, _tests = doctest.testfile(filename, **options)
fails += _fails
tests += _tests
os.chdir(cwd)
if fails > 0 or verbose: # pragma: no cover
print()
print(60 * "-")
print("Test Suite Report:", end=" ")
print("{0} failures / {1} tests".format(fails, tests))
print(60 * "-")
if fails: # pragma: no cover
sys.exit(1)