-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathauto_test.py
134 lines (104 loc) · 4.13 KB
/
auto_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
import ast
import unittest
from os.path import splitext
from textwrap import dedent
import pytest
import rbnf.zero as ze
from Redy.Tools.PathLib import Path
from bytecode import Bytecode
from yapypy.extended_python.parser import parse
from yapypy.extended_python.py_compile import py_compile
ze_exp = ze.compile(
r"""
[python] import rbnf.std.common.[recover_codes]
Space := ' '
NL := R'\n'
Keyword := 'test:' 'prepare:' '>>>' 'title:'
NoSwitch ::= ~Keyword
Doctest ::= [(~'title:')* 'title:' name=(~NL)+]
[(~'prepare:')* 'prepare:' (NoSwitch* '>>>' prepare_lines<<((~NL)*) NL+)*]
(~'test:')* 'test:' (NoSwitch* '>>>' test_lines<<((~NL)+))*
->
prepare_lines = recover_codes(sum(prepare_lines, [])) if prepare_lines else ''
test = recover_codes(sum(test_lines, [])) if test_lines else ''
return recover_codes(name) if name else None, prepare_lines, test
lexer := R'.'
TestCase ::= [it=Doctest] _* -> it or None
""",
use='TestCase')
yapypy = Path('yapypy')
def dedent_all(text: str):
while text.startswith(' ') or text.startswith('\t'):
text = dedent(text)
return text
class DocStringsCollector(ast.NodeVisitor):
def __init__(self):
self.docs = []
def _visit_fn(self, node: ast.FunctionDef):
head, *_ = node.body
if isinstance(head, ast.Expr):
value = head.value
if isinstance(value, ast.Str):
res = ze_exp.match(value.s).result
if res:
self.docs.append((node.name, node.lineno, *res))
self.generic_visit(node)
visit_FunctionDef = _visit_fn
class FixLineno(ast.NodeVisitor):
def __init__(self, first_lineno: int):
self.first_lineno = first_lineno
def visit(self, node):
if hasattr(node, 'lineno'):
node.lineno += self.first_lineno
self.generic_visit(node)
class Test(unittest.TestCase):
def test_all(self):
for each in filter(lambda p: p[-1].endswith('.py'), yapypy.collect()):
filename = each.__str__()
if each.parent().exists():
pass
else:
each.parent().mkdir()
with each.open('r') as fr:
collector = DocStringsCollector()
mod = ast.parse(fr.read())
collector.visit(mod)
mod_name, _ = splitext(each.relative())
for idx, [fn_name, lineno, title, prepare_code,
test_code] in enumerate(collector.docs):
print(f'tests of {mod_name}.{title or fn_name} started...')
context = {'self': self}
prepare_code = dedent_all(prepare_code)
test_code = dedent_all(test_code)
fixer = FixLineno(lineno)
try:
node = ast.parse(prepare_code, filename, mode='exec')
fixer.visit(node)
code = compile(node, filename, "exec")
except SyntaxError as exc:
exc.lineno = lineno
exc.filename = filename
raise exc
bc = Bytecode.from_code(code)
bc.filename = filename
bc.first_lineno = lineno
exec(bc.to_code(), context)
# not correct but as a workaround
fixer = FixLineno(lineno + test_code.count('\n'))
try:
node = parse(test_code, filename).result
# pprint(node)
fixer.visit(node)
code = py_compile(node, filename, is_entrypoint=True)
except SyntaxError as exc:
exc.lineno = lineno
exc.filename = filename
raise exc
bc = Bytecode.from_code(code)
bc.filename = filename
bc.first_lineno = lineno
code_obj = bc.to_code()
exec(code_obj, context)
print(f'tests of {mod_name}.{title or fn_name} passed.')
if __name__ == '__main__':
unittest.main()