Skip to content

Commit fe5a2c0

Browse files
authored
add utility for checking if test suites are up-to-date (exercism#1434)
* add utility for checking if test suites are up-to-date * fix flake8 violations
1 parent 7c015c4 commit fe5a2c0

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed

bin/check-test-version.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
#!/usr/bin/env python
2+
from __future__ import print_function
3+
import json
4+
import re
5+
import argparse
6+
import os
7+
import sys
8+
9+
if sys.version_info[0] == 3:
10+
FileNotFoundError = OSError
11+
else:
12+
FileNotFoundError = IOError
13+
14+
VERSION_PATTERN = '(\d+\.\d+\.\d+)'
15+
CANONICAL_PATTERN = (
16+
'# Tests adapted from `problem-specifications//canonical-data.json` '
17+
'@ v' + VERSION_PATTERN
18+
)
19+
rgx_version = re.compile(VERSION_PATTERN)
20+
rgx_canonical = re.compile(CANONICAL_PATTERN)
21+
DEFAULT_SPEC_PATH = os.path.join(
22+
'..',
23+
'problem-specifications'
24+
)
25+
26+
with open('config.json') as f:
27+
config = json.load(f)
28+
29+
30+
class CustomFormatter(
31+
argparse.ArgumentDefaultsHelpFormatter,
32+
argparse.RawDescriptionHelpFormatter
33+
):
34+
pass
35+
36+
37+
class bcolors:
38+
HEADER = '\033[95m'
39+
OKBLUE = '\033[94m'
40+
OKGREEN = '\033[92m'
41+
WARNING = '\033[93m'
42+
FAIL = '\033[91m'
43+
ENDC = '\033[0m'
44+
BOLD = '\033[1m'
45+
UNDERLINE = '\033[4m'
46+
47+
48+
def get_test_file_path(exercise):
49+
return os.path.join(
50+
'exercises',
51+
exercise,
52+
exercise.replace('-', '_') + '_test.py'
53+
)
54+
55+
56+
def get_canonical_data_path(exercise, spec_path=DEFAULT_SPEC_PATH):
57+
return os.path.join(
58+
spec_path,
59+
'exercises',
60+
exercise,
61+
'canonical-data.json'
62+
)
63+
64+
65+
def get_referenced_version(exercise):
66+
with open(get_test_file_path(exercise)) as f:
67+
for line in f.readlines():
68+
m = rgx_canonical.match(line)
69+
if m is not None:
70+
return m.group(1)
71+
return '0.0.0'
72+
73+
74+
def get_available_version(exercise, spec_path=DEFAULT_SPEC_PATH):
75+
try:
76+
with open(get_canonical_data_path(exercise, spec_path)) as f:
77+
data = json.load(f)
78+
m = rgx_version.match(data['version'])
79+
return m.group(1)
80+
except FileNotFoundError:
81+
return '0.0.0'
82+
83+
84+
def is_deprecated(exercise):
85+
for e in config['exercises']:
86+
if e['slug'] == exercise:
87+
return e.get('deprecated', False)
88+
return False
89+
90+
91+
def check_test_version(
92+
exercise,
93+
spec=DEFAULT_SPEC_PATH,
94+
no_color=True,
95+
print_ok=True,
96+
name_only=False,
97+
has_data=False,
98+
include_deprecated=False,
99+
):
100+
if not include_deprecated and is_deprecated(exercise):
101+
return True
102+
available = get_available_version(exercise, spec)
103+
if available == '0.0.0' and has_data:
104+
return True
105+
referenced = get_referenced_version(exercise)
106+
up_to_date = available == referenced
107+
if up_to_date:
108+
status, status_color = ' OK ', bcolors.OKGREEN
109+
else:
110+
status, status_color = 'NOT OK', bcolors.FAIL
111+
if not no_color:
112+
status = status_color + status + bcolors.ENDC
113+
if not up_to_date or print_ok:
114+
if name_only:
115+
print(exercise)
116+
else:
117+
print('[ {} ] {}: {}{}{}'.format(
118+
status,
119+
exercise,
120+
referenced,
121+
'=' if up_to_date else '!=',
122+
available
123+
))
124+
return up_to_date
125+
126+
127+
if __name__ == '__main__':
128+
parser = argparse.ArgumentParser(
129+
formatter_class=CustomFormatter,
130+
epilog=(
131+
"Results are of the form:\n <exercise>: <referenced>!=<current>"
132+
)
133+
)
134+
parser._optionals.title = 'options'
135+
parser.add_argument(
136+
'--version',
137+
action='store_true',
138+
help='Print version info.'
139+
)
140+
parser.add_argument(
141+
'-o', '--only',
142+
metavar='<exercise>',
143+
help='Check just the exercise specified (by the slug)/'
144+
)
145+
parser.add_argument(
146+
'-p', '--spec-path',
147+
default=DEFAULT_SPEC_PATH,
148+
metavar='<path/to/track>',
149+
help='The location of the problem-specifications directory.'
150+
)
151+
g = parser.add_argument_group('output')
152+
g.add_argument(
153+
'-w', '--no-color',
154+
action='store_true',
155+
help='Disable colored output.'
156+
)
157+
g.add_argument(
158+
'-s', '--has-data',
159+
action='store_true',
160+
help='Only print exercises with existing canonical data.'
161+
)
162+
g.add_argument(
163+
'-d', '--include-deprecated',
164+
action='store_true',
165+
help='Include deprecated exercises'
166+
)
167+
mut_g = g.add_mutually_exclusive_group()
168+
mut_g.add_argument(
169+
'-v', '--verbose',
170+
action='store_true',
171+
help='Enable verbose output.'
172+
)
173+
mut_g.add_argument(
174+
'-n', '--name-only',
175+
action='store_true',
176+
help='Print exercise names only.'
177+
)
178+
opts = parser.parse_args()
179+
kwargs = dict(
180+
spec=opts.spec_path,
181+
no_color=opts.no_color,
182+
print_ok=opts.verbose,
183+
name_only=opts.name_only,
184+
has_data=opts.has_data,
185+
)
186+
if opts.version:
187+
print('check-test-version.py v1.0')
188+
sys.exit(0)
189+
result = True
190+
if opts.only is None:
191+
for exercise in os.listdir('exercises'):
192+
if os.path.isdir(os.path.join('exercises', exercise)):
193+
result = check_test_version(exercise, **kwargs) and result
194+
else:
195+
result = check_test_version(opts.only, **kwargs)
196+
sys.exit(0 if result else 1)

0 commit comments

Comments
 (0)