Skip to content

Commit 006913c

Browse files
authored
Odin test runner, conforms to test runner interface version 1 (#7)
* Odin test runner, conforms to test runner interface version 1 * send the correct status to jq * remove INFO messages from test output * patch test runner JSON output bug
1 parent c6ae850 commit 006913c

File tree

13 files changed

+164
-28
lines changed

13 files changed

+164
-28
lines changed

Dockerfile

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,49 @@
1-
FROM alpine:3.18
1+
# dockerfile recipe from https://github.com/SnappyBeeBit/docker-odin/blob/main/Dockerfile.odin-dev-latest
22

3-
# install packages required to run the tests
4-
RUN apk add --no-cache jq coreutils
3+
ARG ODIN_REF=dev-2025-10
4+
5+
# ---------- Build stage ----------
6+
FROM ubuntu:24.04 AS build
7+
8+
ENV DEBIAN_FRONTEND=noninteractive
9+
RUN apt-get update && apt-get install -y --no-install-recommends \
10+
git build-essential make ca-certificates \
11+
clang-18 llvm-18-dev \
12+
&& update-ca-certificates \
13+
&& rm -rf /var/lib/apt/lists/*
14+
15+
WORKDIR /src
16+
ARG ODIN_REF
17+
RUN git clone --depth=1 --branch "${ODIN_REF}" https://github.com/odin-lang/Odin.git
18+
19+
WORKDIR /src/Odin
20+
# Tell the build which llvm-config to use to avoid symlink hacks
21+
RUN make -j"$(nproc)" release-native LLVM_CONFIG=llvm-config-18
22+
23+
# Fix a bug in this Odin release. When we upgrade, revisit this.
24+
RUN sed -E -i '983,984s/\<err\>/marshall_err/g' /src/Odin/core/testing/runner.odin
25+
26+
# ---------- Runtime stage ----------
27+
FROM ubuntu:24.04 AS runtime
28+
29+
ENV DEBIAN_FRONTEND=noninteractive
30+
RUN apt-get update && apt-get install -y --no-install-recommends \
31+
ca-certificates libstdc++6 libllvm18 \
32+
clang lld build-essential \
33+
jq gawk locales \
34+
&& update-ca-certificates \
35+
&& locale-gen en_US.UTF-8 \
36+
&& rm -rf /var/lib/apt/lists/*
37+
38+
# Odin binary and libraries
39+
COPY --from=build /src/Odin/odin /usr/local/bin/odin
40+
COPY --from=build /src/Odin/core /usr/local/lib/odin/core
41+
COPY --from=build /src/Odin/vendor /usr/local/lib/odin/vendor
42+
COPY --from=build /src/Odin/base /usr/local/lib/odin/base
43+
44+
ENV LC_ALL=en_US.UTF-8
45+
ENV ODIN_ROOT=/usr/local/lib/odin
546

647
WORKDIR /opt/test-runner
7-
COPY . .
48+
COPY ./bin/ bin/
849
ENTRYPOINT ["/opt/test-runner/bin/run.sh"]

bin/run.sh

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/env sh
1+
#!/usr/bin/env bash
22

33
# Synopsis:
44
# Run the test runner on a solution.
@@ -17,7 +17,7 @@
1717

1818
# If any required arguments is missing, print the usage and exit
1919
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
20-
echo "usage: ./bin/run.sh exercise-slug path/to/solution/folder/ path/to/output/directory/"
20+
echo "usage: $0 exercise-slug path/to/solution/folder/ path/to/output/directory/"
2121
exit 1
2222
fi
2323

@@ -26,26 +26,51 @@ solution_dir=$(realpath "${2%/}")
2626
output_dir=$(realpath "${3%/}")
2727
results_file="${output_dir}/results.json"
2828

29+
cd "${solution_dir}" || exit 1
30+
2931
# Create the output directory if it doesn't exist
3032
mkdir -p "${output_dir}"
3133

3234
echo "${slug}: testing..."
3335

3436
# Run the tests for the provided implementation file and redirect stdout and
3537
# stderr to capture it
36-
test_output=$(false)
37-
# TODO: substitute "false" with the actual command to run the test:
38-
# test_output=$(command_to_run_tests 2>&1)
38+
compile_options=(
39+
# ref https://odin-lang.org/docs/testing/#compile-time-options
40+
-define:ODIN_TEST_LOG_LEVEL=warning
41+
-define:ODIN_TEST_SHORT_LOGS=true
42+
-define:ODIN_TEST_FANCY=false
43+
-define:ODIN_TEST_RANDOM_SEED=1234567890
44+
-define:ODIN_TEST_TRACK_MEMORY=false
45+
)
46+
47+
raw_output=$( odin test . "${compile_options[@]}" 2>&1 )
48+
rc=$?
3949

4050
# Write the results.json file based on the exit code of the command that was
4151
# just executed that tested the implementation file
42-
if [ $? -eq 0 ]; then
43-
jq -n '{version: 1, status: "pass"}' > ${results_file}
52+
if [ $rc -eq 0 ]; then
53+
jq -n '{version: 1, status: "pass"}' > "${results_file}"
4454
else
45-
# OPTIONAL: Sanitize the output
46-
# In some cases, the test output might be overly verbose, in which case stripping
47-
# the unneeded information can be very helpful to the student
48-
# sanitized_test_output=$(printf "${test_output}" | sed -n '/Test results:/,$p')
55+
# Sanitize the output:
56+
# remove text that can change from run to run, or from system to system.
57+
test_output=$(
58+
gawk -v pwd="${PWD}/" '
59+
/To run only the failed test,/ {exit}
60+
/Finished [[:digit:]]+ tests in / { sub(/ in [[:digit:].]+.s/, "") }
61+
{
62+
gsub(pwd, "") # trim full paths from filenames
63+
print
64+
}
65+
' <<< "$raw_output"
66+
)
67+
68+
if [[ $test_output =~ .*$'\nFinished '[[:digit:]]+' tests.'.* ]]; then
69+
# successfully compiled, but test failures
70+
status='fail'
71+
else
72+
status='error'
73+
fi
4974

5075
# OPTIONAL: Manually add colors to the output to help scanning the output for errors
5176
# If the test output does not contain colors to help identify failing (or passing)
@@ -54,7 +79,9 @@ else
5479
# | GREP_COLOR='01;31' grep --color=always -E -e '^(ERROR:.*|.*failed)$|$' \
5580
# | GREP_COLOR='01;32' grep --color=always -E -e '^.*passed$|$')
5681

57-
jq -n --arg output "${test_output}" '{version: 1, status: "fail", message: $output}' > ${results_file}
82+
jq -n --arg output "${test_output}" \
83+
--arg status "${status}" \
84+
'{version: 1, status: $status, message: $output}' > "${results_file}"
5885
fi
5986

6087
echo "${slug}: done"

tests/all-fail/expected_results.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/empty-file/expected_results.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/partial-fail/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
partial-fail
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"version": 1,
33
"status": "fail",
4-
"message": "TODO: replace with correct output"
4+
"message": "[ERROR] --- [partial_fail_test.odin:7] expected two_fer() to be One for you, one for me., got One for failure, one for me.\n\nFinished 3 tests. 1 test failed.\n - partial_fail.test_no_name_given \texpected two_fer() to be One for you, one for me., got One for failure, one for me."
55
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package partial_fail
2+
3+
import "core:fmt"
4+
5+
two_fer :: proc(name: string = "failure") -> string {
6+
return fmt.tprintf("One for {}, one for me.", name)
7+
}
8+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package partial_fail
2+
3+
import "core:testing"
4+
5+
@(test)
6+
test_no_name_given :: proc(t: ^testing.T) {
7+
testing.expect_value(t, two_fer(), "One for you, one for me.")
8+
}
9+
10+
@(test)
11+
test_a_name_given :: proc(t: ^testing.T) {
12+
testing.expect_value(t, two_fer("Alice"), "One for Alice, one for me.")
13+
}
14+
15+
@(test)
16+
test_another_name_given :: proc(t: ^testing.T) {
17+
testing.expect_value(t, two_fer("Bob"), "One for Bob, one for me.")
18+
}
19+

tests/success/success.odin

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package success
2+
3+
import "core:fmt"
4+
5+
two_fer :: proc(name: string = "you") -> string {
6+
return fmt.tprintf("One for {}, one for me.", name)
7+
}

tests/success/success_test.odin

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package success
2+
3+
import "core:testing"
4+
5+
@(test)
6+
test_no_name_given :: proc(t: ^testing.T) {
7+
testing.expect_value(t, two_fer(), "One for you, one for me.")
8+
}
9+
10+
@(test)
11+
test_a_name_given :: proc(t: ^testing.T) {
12+
testing.expect_value(t, two_fer("Alice"), "One for Alice, one for me.")
13+
}
14+
15+
@(test)
16+
test_another_name_given :: proc(t: ^testing.T) {
17+
testing.expect_value(t, two_fer("Bob"), "One for Bob, one for me.")
18+
}

0 commit comments

Comments
 (0)