Coverage for test.py: 100%
83 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-25 19:58 +0000
« prev ^ index » next coverage.py v6.4, created at 2022-05-25 19:58 +0000
1#!/usr/bin/env python
3# Python Standard Library
4import codeop
5import doctest
6import glob
7import os
8import shutil
9import sys
10import tempfile
12# Third-Party Libraries
13import strictyaml
15# Test Files
16# ------------------------------------------------------------------------------
17# Read mkdocs config file.
18mkdocs_content = strictyaml.load(open("mkdocs.yml").read())["nav"].data
19mkdocs_files = []
20for value in [list(item.values())[0] for item in mkdocs_content]:
21 if isinstance(value, str): # page
22 mkdocs_files.append(value)
23 else: # section
24 mkdocs_files.extend([list(item.values())[0] for item in value])
25mkdocs_files = ["mkdocs/" + file for file in mkdocs_files]
26extra_testfiles = []
27test_files = mkdocs_files + extra_testfiles
29# Sandbox the Test Files
30# ------------------------------------------------------------------------------
31# This is required:
32# - to tweak the files before the tests,
33# - to avoid the generation of artifacts (generated by the test code)
34# in the current directory.
35tmp_dir = tempfile.mkdtemp() # TODO: clean-up this directory
36for filename in test_files:
37 target_file = os.path.join(tmp_dir, filename)
38 target_dir = os.path.dirname(target_file)
39 os.makedirs(target_dir, exist_ok=True)
40 shutil.copy(filename, target_file)
42# Tweak the Test Files
43# ------------------------------------------------------------------------------
44# For each file, find the python fences, see if they are in interpreter mode
45# or "code" mode. If they are in code mode, add the prompts then remove the
46# fences and indent the code lines.
47def promptize(src):
48 "Add >>> or ... prompts to Python code"
49 cc = codeop.compile_command # symbol="single" (the default here)
50 # is required to deal with if / else constructs properly
51 # (without going back to the ">>> " prompt after the if clause).
52 lines = src.splitlines()
53 output = []
54 chunk = []
55 for line in lines:
56 if chunk == []: # new start
57 output.append(">>> " + line)
58 else:
59 output.append("... " + line)
60 chunk.append(line)
61 try:
62 code = cc("\n".join(chunk))
63 if code is not None: # full statement
64 chunk = [] # start over
65 except: # pragma: no cover
66 raise
67 assert len(lines) == len(output)
68 return "\n".join(output)
71def tweak(src):
72 # Find code blocks with python fences,
73 # add prompts when necessary,
74 # then transform them into indented code blocks.
75 lines = src.splitlines()
76 chunks = {}
77 start, end, code = None, None, []
78 for i, line in enumerate(lines):
79 if line.startswith("```python"):
80 start = i
81 code.append("")
82 elif line.startswith("```"):
83 end = i + 1
84 code.append("")
85 assert end - start == len(code)
86 chunks[(start, end)] = code
87 code = []
88 elif code != []:
89 code.append(line)
91 for loc, code in chunks.items():
92 chunk = "\n".join(code[1:-1]) # dont promptize initial and final newline
93 if not chunk.strip().startswith(">>> "): # prompts are missing
94 code[1:-1] = promptize(chunk).splitlines()
95 code = [4 * " " + line for line in code]
96 chunks[loc] = code
98 for (i, j), code in chunks.items():
99 lines[i:j] = code
100 new_src = "\n".join(lines)
101 return new_src
104cwd = os.getcwd()
105os.chdir(tmp_dir)
107for filename in test_files:
108 with open(filename, encoding="utf-8") as file:
109 src = file.read()
110 src = tweak(src)
111 with open(filename, "w", encoding="utf-8") as file:
112 file.write(src)
114# Run the Tests
115# ------------------------------------------------------------------------------
116verbose = "-v" in sys.argv or "--verbose" in sys.argv
117build = "-b" in sys.argv or "--build" in sys.argv # build documentation images
119# Setup and teardown the src code (theming & figure cleanup)
120if build: # pragma: no cover
121 for filename in test_files:
122 with open(filename, encoding="utf-8") as file:
123 src = file.read()
124 src = """
125 >>> import seaborn; seaborn.set_theme(style="whitegrid", font="Roboto")
126 >>> import matplotlib.pyplot
128""" + src + """
129 >>> matplotlib.pyplot.close('all')
131"""
132 with open(filename, "w", encoding="utf-8") as file:
133 file.write(src)
135fails = 0
136tests = 0
137for filename in test_files:
138 options = {"module_relative": False, "verbose": verbose}
139 _fails, _tests = doctest.testfile(filename, **options)
140 fails += _fails
141 tests += _tests
143# Copy the generated images to the images folder
144if build: # pragma: no cover
145 for image in glob.glob("*svg"):
146 dest_fpath = cwd + "/mkdocs/images/" + image
147 os.makedirs(os.path.dirname(dest_fpath), exist_ok=True)
148 shutil.copy(image, dest_fpath)
150os.chdir(cwd)
152if fails > 0 or verbose: # pragma: no cover
153 print()
154 print(60 * "-")
155 print("Test Suite Report:", end=" ")
156 print("{0} failures / {1} tests".format(fails, tests))
157 print(60 * "-")
158if fails: # pragma: no cover
159 sys.exit(1)