forked from exercism/python
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcheck-test-version.py
executable file
·331 lines (298 loc) · 8.47 KB
/
check-test-version.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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#!/usr/bin/env python
from __future__ import print_function
import argparse
import json
import os
import re
import sys
from glob import glob
from create_issue import GitHub
if sys.version_info[0] == 3:
FileNotFoundError = OSError
else:
FileNotFoundError = IOError
VERSION_PATTERN = r'(\d+\.\d+\.\d+)'
CANONICAL_PATTERN = (
'# Tests adapted from `?problem-specifications//canonical-data.json`? '
'@ v' + VERSION_PATTERN
)
rgx_version = re.compile(VERSION_PATTERN)
rgx_canonical = re.compile(CANONICAL_PATTERN)
DEFAULT_SPEC_PATH = os.path.join(
'..',
'problem-specifications'
)
gh = None
with open('config.json') as f:
config = json.load(f)
class CustomFormatter(
argparse.ArgumentDefaultsHelpFormatter,
argparse.RawDescriptionHelpFormatter
):
pass
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def cjust(string, width, fillchar=' '):
while len(string) < width:
string = fillchar + string
if len(string) >= width:
break
string += fillchar
return string
def verify_spec_location(path):
with open(os.path.join(path, 'package.json')) as f:
data = json.load(f)
if data['name'] != 'problem-specifications':
raise ValueError(
'{} is not the problem-specifications directory'.format(path)
)
def get_test_file_path(exercise):
return os.path.join(
'exercises',
exercise,
exercise.replace('-', '_') + '_test.py'
)
def get_test_file_url(exercise):
return '/'.join([
'https://github.com',
'exercism',
'python',
'blob',
'master',
'exercises',
exercise,
exercise.replace('-', '_') + '_test.py'
])
def get_canonical_data_path(exercise, spec_path=DEFAULT_SPEC_PATH):
return os.path.join(
spec_path,
'exercises',
exercise,
'canonical-data.json'
)
def get_canonical_data_url(exercise):
return '/'.join([
'https://github.com',
'exercism',
'problem-specifications',
'blob',
'master',
'exercises',
exercise,
'canonical-data.json'
])
def get_referenced_version(exercise):
with open(get_test_file_path(exercise)) as f:
for line in f.readlines():
m = rgx_canonical.match(line)
if m is not None:
return m.group(1)
return '0.0.0'
def get_available_version(exercise, spec_path=DEFAULT_SPEC_PATH):
try:
with open(get_canonical_data_path(exercise, spec_path)) as f:
data = json.load(f)
m = rgx_version.match(data['version'])
return m.group(1)
except FileNotFoundError:
return '0.0.0'
def is_deprecated(exercise):
for e in config['exercises']:
if e['slug'] == exercise:
return e.get('deprecated', False)
return False
def create_issue_for_exercise(
exercise,
available_data_version,
extra_labels=None
):
title = '{}: update tests to v{}'.format(exercise, available_data_version)
body = (
'The [test suite]({}) for {} is out of date and needs updated to '
'conform to the [latest canonical data]({}).'
).format(
get_test_file_url(exercise),
exercise,
get_canonical_data_url(exercise),
)
labels = [
'beginner friendly',
'help wanted',
'enhancement',
]
if extra_labels is not None:
labels = list(set(labels + extra_labels))
issue = gh.create_issue(
'exercism',
'python',
title,
body=body,
labels=labels
)
return issue['number']
def check_test_version(
exercise,
spec=DEFAULT_SPEC_PATH,
no_color=True,
print_ok=True,
name_only=False,
has_data=False,
include_deprecated=False,
create_issue=False,
token=None,
extra_labels=None,
):
if not include_deprecated and is_deprecated(exercise):
return True
available = get_available_version(exercise, spec)
if available == '0.0.0' and has_data:
return True
referenced = get_referenced_version(exercise)
up_to_date = available == referenced
if up_to_date:
status, status_color = 'OK', bcolors.OKGREEN
else:
status, status_color = 'NOT OK', bcolors.FAIL
status = cjust(status, 8)
if not no_color:
status = status_color + status + bcolors.ENDC
if not up_to_date or print_ok:
if create_issue:
issue_number = create_issue_for_exercise(
exercise,
available,
extra_labels
)
issue_info = '(#{})'.format(issue_number)
else:
issue_info = ''
if name_only:
baseline = exercise
else:
baseline = '[{}] {}: {}{}{}'.format(
status,
exercise,
referenced,
'=' if up_to_date else '!=',
available
)
print(' '.join((baseline, issue_info)))
return up_to_date
if __name__ == '__main__':
parser = argparse.ArgumentParser(
formatter_class=CustomFormatter,
epilog=(
"Results are of the form:\n <exercise>: <referenced>!=<current>"
)
)
parser._optionals.title = 'options'
parser.add_argument(
'--version',
action='store_true',
help='Print version info.'
)
parser.add_argument(
'-o', '--only',
default='*',
metavar='<exercise>',
help='Check just the exercise specified (by the slug).',
)
parser.add_argument(
'--ignore',
action='append',
help='Check all except exercise[s] specified (by the slug).',
)
parser.add_argument(
'-p', '--spec-path',
default=DEFAULT_SPEC_PATH,
metavar='<path/to/spec>',
help='The location of the problem-specifications directory.'
)
g = parser.add_argument_group('output')
g.add_argument(
'-w', '--no-color',
action='store_true',
help='Disable colored output.'
)
g.add_argument(
'-s', '--has-data',
action='store_true',
help='Only print exercises with existing canonical data.'
)
g.add_argument(
'-d', '--include-deprecated',
action='store_true',
help='Include deprecated exercises'
)
mut_g = g.add_mutually_exclusive_group()
mut_g.add_argument(
'-v', '--verbose',
action='store_true',
help='Enable verbose output.'
)
mut_g.add_argument(
'-n', '--name-only',
action='store_true',
help='Print exercise names only.'
)
g = parser.add_argument_group('issue creation')
g.add_argument(
'--create-issue',
action='store_true',
help='Create issue for out-of-date exercises'
)
g.add_argument(
'-t', '--token',
help='GitHub personal access token (permissions: repo)'
)
g.add_argument(
'--labels',
nargs='+',
metavar='LABEL',
help=(
'additional issue labels ("beginner friendly", "enhancement", and '
'"help wanted" are always set)'
)
)
opts = parser.parse_args()
verify_spec_location(opts.spec_path)
if opts.create_issue:
if opts.token is None:
if os.path.isfile('.github.api_token'):
with open('.github.api_token') as f:
opts.token = f.read().strip()
if opts.token is not None:
gh = GitHub(api_token=opts.token)
else:
gh = GitHub()
kwargs = dict(
spec=opts.spec_path,
no_color=opts.no_color,
print_ok=opts.verbose,
name_only=opts.name_only,
has_data=opts.has_data,
create_issue=opts.create_issue,
extra_labels=opts.labels,
)
if opts.version:
print('check-test-version.py v1.1')
sys.exit(0)
result = True
for exercise in glob(os.path.join('exercises', opts.only)):
exercise = exercise.split(os.path.sep)[-1]
if opts.ignore and exercise in opts.ignore:
continue
if os.path.isdir(os.path.join('exercises', exercise)):
try:
result = check_test_version(exercise, **kwargs) and result
except FileNotFoundError as e:
print(str(e))
result = False
sys.exit(0 if result else 1)