-
Notifications
You must be signed in to change notification settings - Fork 0
/
tester.py
executable file
·162 lines (123 loc) · 4.93 KB
/
tester.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
#! /usr/bin/env python3
"""
Validate astmonkey library on python files from given github repository.
"""
import argparse, ast, os, re, tempfile, sys
from astmonkey import visitors
import urllib.request
from zipfile import ZipFile
assert sys.version_info >= (3, 6, 0), "Python 3.6.0 required to run script"
def args_parse():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('path', help='GitHub repo url or name')
parser.add_argument('-b', '--branch', help='Branch name', default='master')
parser.add_argument('-i', '--interactive', action='store_true', help='Run in interactive mode, allow investigating bugs')
parser.add_argument('-l', '--local', action='store_true', help='Interpret given url as local file path')
return parser.parse_args()
def extract_repo_from_url(repo):
repo = re.sub(r'^(https?://github.com/|git@github.com:)', '', repo) #git@github.com:TheAlgorithms/Python.git
repo = re.sub(r'\.git$', '', repo)
return repo
def compare_files(text_orig, text_gen, command):
if isinstance(text_orig, str):
text_orig = text_orig.encode('utf-8')
if isinstance(text_gen, str):
text_gen = text_gen.encode('utf-8')
with tempfile.NamedTemporaryFile('wb', suffix='.orig') as file_orig:
file_orig.write(text_orig)
file_orig.flush()
with tempfile.NamedTemporaryFile('wb', suffix='.gen') as file_gen:
file_gen.write(text_gen)
file_gen.flush()
os.system(command.format(file_orig=file_orig.name, file_gen=file_gen.name))
def meld(text_orig, text_gen):
compare_files(text_orig, text_gen, "meld {file_orig} {file_gen}")
def diff(text_orig, text_gen):
compare_files(text_orig, text_gen, "diff -y -B -d --color=always {file_orig} {file_gen} | less")
def h(text):
"""Highliht text with ANSI markup"""
return re.sub('_(\w)_', r'\033[1;33m\1\033[0m', text)
def enter_interactive(code_orig, node_orig, code_gen, node_gen):
while True:
a = input(h(' Compare results [_n_ext / _d_iff code / _m_eld code / diff _a_st / _q_uit]: '))
if a == 'n':
break
elif a == 'd':
diff(code_orig, code_gen)
elif a == 'm':
meld(code_orig, code_gen)
elif a == 'a':
if node_gen is None:
print(' Generated code is not parsable')
else:
dump_orig = ast.dump(node_orig)
dump_gen = ast.dump(node_gen)
brackets = re.compile(r'([()\[\]])')
meld(brackets.sub(r'\1\n', dump_orig), brackets.sub(r'\1\n', dump_gen))
elif a == 'q':
exit()
print()
def test_code_generation(file_name, code_orig, interactive=False):
print(f"{file_name}: ", end='')
try:
node_orig = ast.parse(code_orig)
except SyntaxError:
print("\033[1;34mSkipping\033[0m")
return True
code_gen = ''
node_gen = None
try:
code_gen = visitors.to_source(node_orig)
node_gen = ast.parse(code_gen)
assert ast.dump(node_orig) == ast.dump(node_gen)
except (SyntaxError, AssertionError, KeyError) as e:
print("\033[1;31m%s\033[0m \033[31m[%s]\033[0m" % (type(e).__name__, str(e)))
if interactive:
enter_interactive(code_orig, node_orig, code_gen, node_gen)
return False
else:
print("\033[1;32mOK\033[0m")
return True
def summarize(fails, total):
print('\nSuccess: %d / %d = %.2f%%' % (total-fails, total, 100.0*(total-fails)/total))
def test_repo(args):
repo, branch = extract_repo_from_url(args.path), args.branch
zip_url = f"https://codeload.github.com/{repo}/zip/{branch}"
local_name = f"{repo}/{branch}.zip".replace('/', '_')
if not os.path.exists(local_name):
with urllib.request.urlopen(zip_url) as zip_request, open(local_name, 'wb') as output:
output.write(zip_request.read())
total = fails = 0
with ZipFile(local_name) as zip_file:
for file_name in zip_file.namelist():
if not file_name.endswith('.py'):
continue
code = zip_file.open(file_name).read()
if not test_code_generation(file_name, code, args.interactive):
fails += 1
total += 1
summarize(fails, total)
def test_local(args):
path = args.path
total = fails = 0
if os.path.isdir(path):
import glob
files = glob.glob(os.path.join(path,'**','*.py'), recursive=True)
else:
files = [path]
for file_name in files:
with open(file_name) as file:
content = file.read()
total += 1
if not test_code_generation(file_name, content, args.interactive):
fails += 1
summarize(fails, total)
if __name__ == '__main__':
args = args_parse()
try:
if args.local:
test_local(args)
else:
test_repo(args)
except KeyboardInterrupt:
exit()