-
Notifications
You must be signed in to change notification settings - Fork 252
/
caveats.py
132 lines (100 loc) · 3.75 KB
/
caveats.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
import io
import re
import sys
def parse(fname):
"""Return blocks of code as list of dicts
Arguments:
fname (str): Relative name of caveats file
"""
blocks = list()
with io.open(fname, "r", encoding="utf-8") as f:
in_block = False
current_block = None
current_header = ""
for line in f:
# Doctests are within a quadruple hashtag header.
if line.startswith("#### "):
current_header = line.rstrip()
# The actuat test is within a fenced block.
if line.startswith("```"):
in_block = False
if in_block:
current_block.append(line)
if line.startswith("```python"):
in_block = True
current_block = list()
current_block.append(current_header)
blocks.append(current_block)
tests = list()
for block in blocks:
header = (
block[0].strip("# ") # Remove Markdown
.rstrip() # Remove newline
.lower() # PEP08
)
# Remove unsupported characters
header = re.sub(r"\W", "_", header)
# Adding "untested" anywhere in the first line of
# the doctest excludes it from the test.
if "untested" in block[1].lower():
continue
data = re.sub(" ", "", block[1]) # Remove spaces
data = (
data.strip("#")
.rstrip() # Remove newline
.split(",")
)
binding, doctest_version = (data + [None])[:2]
# Run tests on both Python 2 and 3, unless explicitly stated
if doctest_version is not None:
if doctest_version not in ("Python2", "Python3"):
raise SyntaxError(
"Invalid Python version:\n%s\n"
"Python version must follow binding, e.g.\n"
"# PyQt5, Python3" % doctest_version)
active_version = "Python%i" % sys.version_info[0]
if doctest_version != active_version:
continue
tests.append({
"header": header,
"binding": binding,
"body": block[2:]
})
return tests
def format_(blocks):
"""Produce Python module from blocks of tests
Arguments:
blocks (list): Blocks of tests from func:`parse()`
"""
tests = list()
function_count = 0 # For each test to have a unique name
for block in blocks:
# Validate docstring format of body
if not any(line[:3] == ">>>" for line in block["body"]):
# A doctest requires at least one `>>>` directive.
block["body"].insert(0, ">>> assert False, "
"'Body must be in docstring format'\n")
# Validate binding on first line
if not block["binding"] in ("PySide", "PySide2", "PyQt5", "PyQt4"):
block["body"].insert(0, ">>> assert False, "
"'Invalid binding'\n")
if sys.version_info > (3, 4) and block["binding"] in ("PySide"):
# Skip caveat test if it requires PySide on Python > 3.4
continue
else:
function_count += 1
block["header"] = block["header"]
block["count"] = str(function_count)
block["body"] = " ".join(block["body"])
tests.append("""\
def test_{count}_{header}():
'''Test {header}
>>> import os, sys
>>> PYTHON = sys.version_info[0]
>>> long = int if PYTHON == 3 else long
>>> _ = os.environ.pop("QT_VERBOSE", None) # Disable debug output
>>> os.environ["QT_PREFERRED_BINDING"] = "{binding}"
{body}
'''
""".format(**block))
return tests