-
Notifications
You must be signed in to change notification settings - Fork 3
/
test.py
executable file
·159 lines (139 loc) · 4.99 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
#!/usr/bin/env python
# Python Standard Library
import codeop
import doctest
import glob
import os
import shutil
import sys
import tempfile
# Third-Party Libraries
import strictyaml
# Test Files
# ------------------------------------------------------------------------------
# Read mkdocs config file.
mkdocs_content = strictyaml.load(open("mkdocs.yml").read())["nav"].data
mkdocs_files = []
for value in [list(item.values())[0] for item in mkdocs_content]:
if isinstance(value, str): # page
mkdocs_files.append(value)
else: # section
mkdocs_files.extend([list(item.values())[0] for item in value])
mkdocs_files = ["mkdocs/" + file for file in mkdocs_files]
extra_testfiles = []
test_files = mkdocs_files + extra_testfiles
# 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 = {}
start, end, code = None, None, []
for i, line in enumerate(lines):
if line.startswith("```python"):
start = i
code.append("")
elif line.startswith("```"):
end = i + 1
code.append("")
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()
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
build = "-b" in sys.argv or "--build" in sys.argv # build documentation images
# Setup and teardown the src code (theming & figure cleanup)
if build: # pragma: no cover
for filename in test_files:
with open(filename, encoding="utf-8") as file:
src = file.read()
src = """
>>> import seaborn; seaborn.set_theme(style="whitegrid", font="Roboto")
>>> import matplotlib.pyplot
""" + src + """
>>> matplotlib.pyplot.close('all')
"""
with open(filename, "w", encoding="utf-8") as file:
file.write(src)
fails = 0
tests = 0
for filename in test_files:
options = {"module_relative": False, "verbose": verbose}
_fails, _tests = doctest.testfile(filename, **options)
fails += _fails
tests += _tests
# Copy the generated images to the images folder
if build: # pragma: no cover
for image in glob.glob("*svg"):
dest_fpath = cwd + "/mkdocs/images/" + image
os.makedirs(os.path.dirname(dest_fpath), exist_ok=True)
shutil.copy(image, dest_fpath)
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)