Skip to content

Commit c6ecb10

Browse files
authored
Modified Dockerfile to use Alpine, fixed issue 67, and updated runner tests. (#114)
1 parent 90aed1c commit c6ecb10

File tree

19 files changed

+593
-143
lines changed

19 files changed

+593
-143
lines changed

Dockerfile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
FROM python:3.11.2-slim
1+
FROM python:3.11.5-alpine3.18
22

33
COPY requirements.txt /requirements.txt
44

55
RUN pip install -r /requirements.txt
66

7-
RUN apt-get update \
8-
&& apt-get autoremove -y \
9-
&& rm -rf /var/lib/apt/lists/*
7+
RUN apk update && apk upgrade\
8+
&& apk --no-cache add curl bash\
9+
&& apk cache clean
1010

1111
COPY . /opt/test-runner
1212

1313
WORKDIR /opt/test-runner
1414

15-
ENTRYPOINT [ "/opt/test-runner/bin/run.sh" ]
15+
ENTRYPOINT ["sh", "/opt/test-runner/bin/run.sh" ]

bin/run-in-docker.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
#!/usr/bin/env bash
2-
set -e
1+
#!/usr/bin/env sh
32

43
# Synopsis:
54
# Test runner for run.sh in a docker container
@@ -19,6 +18,9 @@ set -e
1918
# Example:
2019
# ./run-in-docker.sh two-fer ./relative/path/to/two-fer/solution/folder/ ./relative/path/to/output/directory/
2120

21+
# Stop executing when a command returns a non-zero return code
22+
set -e
23+
2224
# If arguments not provided, print usage and exit
2325
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
2426
echo "usage: run-in-docker.sh exercise-slug ./relative/path/to/solution/folder/ ./relative/path/to/output/directory/"

bin/run-tests-in-docker.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ docker run \
2424
--network none \
2525
--read-only \
2626
--mount type=bind,src="${PWD}/test",dst=/opt/test-runner/test \
27-
--mount type=volume,dst=/tmp \
27+
--mount type=tmpfs,dst=/tmp \
2828
--workdir /opt/test-runner \
2929
--entrypoint pytest \
3030
exercism/python-test-runner -vv

bin/run.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
#! /bin/sh
1+
#! /usr/bin/env sh
2+
23
root="$( dirname "$( cd "$( dirname "$0" )" >/dev/null 2>&1 && pwd )" )"
34
export PYTHONPATH="$root:$PYTHONPATH"
4-
python3 bin/run.py "$@"
5+
/usr/bin/env python3 bin/run.py "$@"

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
black<=22.3.0
22
pytest~=7.2.2
3-
pytest-subtests~=0.10.0
3+
pytest-subtests~=0.11.0
44
tomli>=1.1.0; python_full_version < '3.11.2'

runner/__init__.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .sort import TestOrder
1616

1717

18+
1819
class ResultsReporter:
1920
def __init__(self):
2021
self.results = Results()
@@ -37,7 +38,6 @@ def pytest_collection_modifyitems(self, session, config, items):
3738
for mark in item.iter_markers(name='task'):
3839
self.tests[name] = Test(name=name, task_id=mark.kwargs['taskno'])
3940

40-
4141
def _sort_by_lineno(item):
4242
test_id = Hierarchy(item.nodeid)
4343
source = Path(item.fspath)
@@ -50,28 +50,60 @@ def pytest_runtest_logreport(self, report):
5050
Process a test setup / call / teardown report.
5151
"""
5252

53-
name = report.head_line if report.head_line else ".".join(report.nodeid.split("::")[1:])
53+
name = ".".join(report.nodeid.split("::")[1:])
54+
if report.head_line:
55+
name = report.head_line.split(" (")[0]
5456

5557

58+
#Add variation name to test output.
5659
if name not in self.tests:
5760
self.tests[name] = Test(name)
5861

59-
6062
state = self.tests[name]
6163

6264
# ignore successful setup and teardown stages
6365
if report.passed and report.when != "call":
6466
return
6567

66-
# Update tests that have already failed with capstdout and return.
68+
#Update tests that have already failed with capstdout and return.
6769
if not state.is_passing():
68-
if report.capstdout.rstrip('FFFFFFFF ').rstrip('uuuuu'):
69-
state.output = report.capstdout.rstrip('FFFFFFFF ').rstrip('uuuuu')
70+
71+
#Check if a report is a concept exercise subtest parent.
72+
if report.capstdout:
73+
74+
#split up the captured stdout by subtest result.
75+
captures = [item for item in report.capstdout.split('\nu')]
76+
if captures[0].startswith('u'):
77+
captures[0] = captures[0][1:]
78+
79+
parsed_captures = []
80+
81+
# Insert spacers for subtests and stdout entries in correct position.
82+
for item in captures:
83+
empties = len(item) - len(item.lstrip('u'))
84+
if empties > 0:
85+
for number in range(1, empties+1):
86+
parsed_captures.append(' ')
87+
parsed_captures.append(item.lstrip('u'))
88+
else: parsed_captures.append(item)
89+
90+
# Generate variation numbers for each subtest output section.
91+
variants = (f'[variation #{number}]: {item}' for
92+
item, number in zip(parsed_captures, range(1, len(parsed_captures)+1)))
93+
94+
# Go through the variations and match them to self.tests.
95+
# Insert matched variation output into test output field.
96+
for item in variants:
97+
for name in self.tests:
98+
if item.split(":")[0] in name and report.nodeid.split("::")[2] in name:
99+
self.tests[name].output = item.split("]: ")[1]
100+
else:
101+
state.output = report.capstdout
70102
return
71103

72-
# Record captured relevant stdout content for passed tests.
73-
if report.capstdout:
74-
state.output = report.capstdout
104+
else:
105+
if report.capstdout:
106+
state.output = report.capstdout
75107

76108
# Handle details of test failure
77109
if report.failed:
@@ -108,7 +140,6 @@ def pytest_runtest_logreport(self, report):
108140
)
109141
self.tests[parent_test_name].test_code = state.test_code
110142

111-
112143
def pytest_sessionfinish(self, session, exitstatus):
113144
"""
114145
Processes the results into a report.
@@ -209,6 +240,7 @@ def run(slug: Slug, indir: Directory, outdir: Directory, args: List[str]) -> Non
209240

210241
# dump the report
211242
out_file.write_text(reporter.results.as_json())
243+
212244
# remove cache directories
213245
for cache_dir in ['.pytest_cache', '__pycache__']:
214246
dirpath = indir / cache_dir

runner/data.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def output(self, captured: Output) -> None:
7979
return
8080

8181
captured = captured.strip()
82+
8283
truncate_msg = " [Output was truncated. Please limit to 500 chars]"
8384
if len(captured) > 500:
8485
captured = captured[: 500 - len(truncate_msg)] + truncate_msg
@@ -141,9 +142,7 @@ def error(self, message: Message = None) -> None:
141142
def _factory(items):
142143
result = {}
143144
for key, value in items:
144-
if key == "_output" or key in {"message", "output", "subtest"} and value is None:
145-
continue
146-
elif key == "_output" or key in {"message", "output", "subtest"} and "\u001b[31mF\u001b[0m" in value:
145+
if key == "_output" or key in {"message", "output", "subtest"} and value in (None, "", " "):
147146
continue
148147

149148
if isinstance(value, Status):

0 commit comments

Comments
 (0)