Skip to content

Commit d8af7ce

Browse files
authored
Add template status script (exercism#1902)
* run template_status.py in CANONICAL_SYNC job * install requirements-generator.txt in CANONICAL_SYNC job
1 parent dd698d1 commit d8af7ce

2 files changed

Lines changed: 139 additions & 1 deletion

File tree

.travis.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ matrix:
3131
python: 3.7
3232
install:
3333
- pip install requests
34+
- pip install -r requirements-generator.txt
3435
- git clone https://github.com/exercism/problem-specifications spec
3536
before_script: []
36-
script: ./bin/check-test-version.py -v -p spec
37+
script:
38+
- echo "Test Version Status" && ./bin/check-test-version.py -p spec
39+
- echo "Test Template Status" && ./bin/template_status.py -v -p spec
3740
allow_failures:
3841
- python: nightly
3942
- env: JOB=CANONICAL_SYNC

bin/template_status.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env python3.7
2+
import argparse
3+
from enum import Enum, auto
4+
from fnmatch import fnmatch
5+
import json
6+
import logging
7+
import os
8+
import shlex
9+
from subprocess import check_call, DEVNULL, CalledProcessError
10+
import sys
11+
12+
DEFAULT_SPEC_LOCATION = os.path.join("..", "problem-specifications")
13+
14+
logging.basicConfig(format="%(levelname)s:%(message)s")
15+
logger = logging.getLogger("generator")
16+
logger.setLevel(logging.WARN)
17+
18+
19+
class TemplateStatus(Enum):
20+
OK = auto()
21+
MISSING = auto()
22+
INVALID = auto()
23+
TEST_FAILURE = auto()
24+
25+
def __lt__(self, other):
26+
return self.value < other.value
27+
28+
29+
with open("config.json") as f:
30+
config = json.load(f)
31+
32+
exercises_dir = os.path.abspath("exercises")
33+
34+
35+
def get_template_path(exercise):
36+
return os.path.join(exercises_dir, exercise, ".meta", "template.j2")
37+
38+
39+
def exec_cmd(cmd):
40+
try:
41+
args = shlex.split(cmd)
42+
if logger.isEnabledFor(logging.DEBUG):
43+
check_call(args)
44+
else:
45+
check_call(args, stderr=DEVNULL, stdout=DEVNULL)
46+
return True
47+
except CalledProcessError as e:
48+
logger.debug(str(e))
49+
return False
50+
51+
52+
def generate_template(exercise, spec_path):
53+
script = os.path.abspath("bin/generate_tests.py")
54+
return exec_cmd(f'{script} --verbose --spec-path "{spec_path}" {exercise}')
55+
56+
57+
def run_tests(exercise):
58+
script = os.path.abspath("test/check-exercises.py")
59+
return exec_cmd(f"{script} {exercise}")
60+
61+
62+
def get_status(exercise, spec_path):
63+
template_path = get_template_path(exercise)
64+
if os.path.isfile(template_path):
65+
if generate_template(exercise, spec_path):
66+
if run_tests(exercise):
67+
logging.info(f"{exercise}: OK")
68+
return TemplateStatus.OK
69+
else:
70+
return TemplateStatus.TEST_FAILURE
71+
else:
72+
return TemplateStatus.INVALID
73+
else:
74+
return TemplateStatus.MISSING
75+
76+
77+
if __name__ == "__main__":
78+
parser = argparse.ArgumentParser()
79+
parser.add_argument("exercise_pattern", nargs="?", default="*", metavar="EXERCISE")
80+
parser.add_argument("-v", "--verbose", action="count", default=0)
81+
parser.add_argument("-q", "--quiet", action="store_true")
82+
parser.add_argument("--stop-on-failure", action="store_true")
83+
parser.add_argument(
84+
"-p",
85+
"--spec-path",
86+
default=DEFAULT_SPEC_LOCATION,
87+
help=(
88+
"path to clone of exercism/problem-specifications " "(default: %(default)s)"
89+
),
90+
)
91+
opts = parser.parse_args()
92+
if opts.quiet:
93+
logger.setLevel(logging.FATAL)
94+
elif opts.verbose >= 2:
95+
logger.setLevel(logging.DEBUG)
96+
elif opts.verbose >= 1:
97+
logger.setLevel(logging.INFO)
98+
99+
if not os.path.isdir(opts.spec_path):
100+
logger.error(f"{opts.spec_path} is not a directory")
101+
sys.exit(1)
102+
opts.spec_path = os.path.abspath(opts.spec_path)
103+
logger.debug(f"problem-specifications path is {opts.spec_path}")
104+
105+
result = True
106+
buckets = {
107+
TemplateStatus.MISSING: [],
108+
TemplateStatus.INVALID: [],
109+
TemplateStatus.TEST_FAILURE: [],
110+
}
111+
for exercise in filter(
112+
lambda e: fnmatch(e["slug"], opts.exercise_pattern), config["exercises"]
113+
):
114+
if exercise.get('deprecated', False):
115+
continue
116+
slug = exercise["slug"]
117+
status = get_status(slug, opts.spec_path)
118+
if status == TemplateStatus.OK:
119+
logger.info(f"{slug}: {status.name}")
120+
else:
121+
buckets[status].append(slug)
122+
result = False
123+
if opts.stop_on_failure:
124+
logger.error(f"{slug}: {status.name}")
125+
break
126+
127+
if not opts.quiet and not opts.stop_on_failure:
128+
for status, bucket in sorted(buckets.items()):
129+
if bucket:
130+
print(f"The following exercises have status '{status.name}'")
131+
for exercise in sorted(bucket):
132+
print(f' {exercise}')
133+
134+
if not result:
135+
sys.exit(1)

0 commit comments

Comments
 (0)