From 2dfc0b81c484bf825d699c3b479a4cb2c8afb5fb Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Mon, 21 Sep 2026 23:16:32 +1000 Subject: [PATCH] verify approaches We extract the code block at the start of each exercises/practice/*/.approaches/*/content.md and test it as an exercise solution. isogram and rna-transcription also gain long-input test cases, to better exercise SIMD solutions. --- bin/.test-in-docker | 40 +++++++++++++------ bin/run-tests | 13 ++++++ bin/verify-exercises-in-docker | 22 ++++++++++ .../practice/isogram/.meta/supplements.json | 31 ++++++++++++++ exercises/practice/isogram/test_isogram.zig | 12 ++++++ .../rna-transcription/.meta/supplements.json | 13 ++++++ .../test_rna_transcription.zig | 4 ++ 7 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 exercises/practice/isogram/.meta/supplements.json create mode 100644 exercises/practice/rna-transcription/.meta/supplements.json diff --git a/bin/.test-in-docker b/bin/.test-in-docker index 9adbba67..f123cc54 100644 --- a/bin/.test-in-docker +++ b/bin/.test-in-docker @@ -6,6 +6,20 @@ all_slugs="$*" exit_code=0 +# Runs the tests against /tmp/solution/${snake}.zig, recording any failure +# under the given description. +check_solution() { + bin/run.sh "${slug}" /tmp/solution /tmp/solution > /dev/null + if ! jq -e '.status == "pass"' /tmp/solution/results.json > /dev/null; then + # A compile error reports at the top level; failing tests report per test. + errors="${errors}\n\n${1} is incorrect:\n$(jq -r ' + .message + // ([.tests[]? | select(.status != "pass") | "\(.name):\n\(.message // "no message")"] | join("\n\n")) + ' /tmp/solution/results.json)" + local_exit_code=1; + fi +} + for slug in $all_slugs; do snake="${slug//-/_}" local_exit_code=0 @@ -13,22 +27,24 @@ for slug in $all_slugs; do rm -rf /tmp/solution cp -r "/exercises/practice/${slug}" /tmp/solution + errors="" + # Integration test: Check if the example solution passes the tests # as run by the zig-test-runner similiarly to a student submitting # their solution. cp /tmp/solution/.meta/example.zig "/tmp/solution/${snake}.zig" - bin/run.sh "${slug}" /tmp/solution /tmp/solution > /dev/null - - errors="" - - if ! jq -e '.status == "pass"' /tmp/solution/results.json > /dev/null; then - # A compile error reports at the top level; failing tests report per test. - errors="${errors}\n\nSolution is incorrect:\n$(jq -r ' - .message - // ([.tests[]? | select(.status != "pass") | "\(.name):\n\(.message // "no message")"] | join("\n\n")) - ' /tmp/solution/results.json)" - local_exit_code=1; - fi + check_solution "Solution" + + # Approach docs: by convention, the first ```zig block in each + # approach's content.md is a complete solution, so it must also pass. + for content in "/exercises/practice/${slug}"/.approaches/*/content.md; do + [ -e "${content}" ] || continue + approach=$(basename "$(dirname "${content}")") + awk '/^```zig$/ && !found { in_block = 1; found = 1; next } + in_block && /^```$/ { exit } + in_block { print }' "${content}" > "/tmp/solution/${snake}.zig" + check_solution "Approach ${approach} solution" + done if [ $local_exit_code = 0 ]; then echo -e "${slug}: \e[32mPASSED\e[0m" diff --git a/bin/run-tests b/bin/run-tests index a62bc9d6..03f1e3f4 100755 --- a/bin/run-tests +++ b/bin/run-tests @@ -20,6 +20,19 @@ execute_test() { printf '%s\n' "Error: ${exercise_name} lacks its '.meta/example.zig' file" >&2 exit 1 fi + + # Approach docs: by convention, the first ```zig block in each + # approach's content.md is a complete solution, so it must also pass. + local content + for content in .approaches/*/content.md; do + [[ -e "${content}" ]] || continue + printf '%s\n' "Running tests for ${1} approach $(basename "$(dirname "${content}")")..." >&2 + awk '/^```zig$/ && !found { in_block = 1; found = 1; next } + in_block && /^```$/ { exit } + in_block { print }' "${content}" > "${exercise_name}".zig + zig test "test_${exercise_name}.zig" + printf '\n' >&2 + done ) } diff --git a/bin/verify-exercises-in-docker b/bin/verify-exercises-in-docker index 594dd181..35ed7456 100755 --- a/bin/verify-exercises-in-docker +++ b/bin/verify-exercises-in-docker @@ -49,6 +49,27 @@ run_tests() { jq -e '.status == "pass"' "${PWD}/results.json" >/dev/null 2>&1 } +first_zig_block() { + awk '/^```zig$/ && !found { in_block = 1; found = 1; next } + in_block && /^```$/ { exit } + in_block { print }' "${1}" +} + +verify_approaches() { + local slug content approach solution + slug="${1}" + solution=$(jq -r '.files.solution[0]' .meta/config.json) + + # Approach docs: by convention, the first ```zig block in each + # approach's content.md is a complete solution, so it must also pass. + for content in .approaches/*/content.md; do + approach=$(basename "$(dirname "${content}")") + echo "Verifying ${slug} approach: ${approach}..." + first_zig_block "${content}" > "${solution}" + run_tests "${slug}" || { cat results.json; return 1; } + done +} + verify_exercise() { local dir local slug @@ -66,6 +87,7 @@ verify_exercise() { copy_example_or_examplar_to_solution run_tests "${slug}" || { cat "${tmp_dir}/results.json"; exit 1; } + verify_approaches "${slug}" || exit 1 ) } diff --git a/exercises/practice/isogram/.meta/supplements.json b/exercises/practice/isogram/.meta/supplements.json new file mode 100644 index 00000000..fce0d975 --- /dev/null +++ b/exercises/practice/isogram/.meta/supplements.json @@ -0,0 +1,31 @@ +{ + "cases": [ + { + "description": "long isogram with spaces and hyphens", + "property": "isIsogram", + "comment": "151 bytes: more than two SIMD blocks of 64 bytes (AVX-512), with a partial block left over for 8, 16, 32 and 64 lanes.", + "input": { + "phrase": "a - - B - - c - - D - - e - - F - - g - - H - - i - - J - - k - - L - - m - - N - - o - - P - - q - - R - - s - - T - - u - - V - - w - - X - - y - - Z" + }, + "expected": true + }, + { + "description": "long phrase with duplicated character in mixed case", + "property": "isIsogram", + "comment": "The duplicates are at bytes 0 and 102, so a SIMD solution sees them in different full blocks for 8, 16, 32 and 64 lanes.", + "input": { + "phrase": "a - - B - - c - - D - - e - - F - - g - - H - - i - - J - - k - - L - - m - - N - - o - - P - - q - - A - - s - - T - - u - - V - - w - - X - - y - - Z" + }, + "expected": false + }, + { + "description": "long phrase with first letter repeated at the end", + "property": "isIsogram", + "comment": "The duplicates are at bytes 0 and 150, so a SIMD solution sees the second one in the final partial block for 8, 16, 32 and 64 lanes.", + "input": { + "phrase": "a - - B - - c - - D - - e - - F - - g - - H - - i - - J - - k - - L - - m - - N - - o - - P - - q - - R - - s - - T - - u - - V - - w - - X - - y - - A" + }, + "expected": false + } + ] +} diff --git a/exercises/practice/isogram/test_isogram.zig b/exercises/practice/isogram/test_isogram.zig index 8a08fc6d..4fd870ea 100644 --- a/exercises/practice/isogram/test_isogram.zig +++ b/exercises/practice/isogram/test_isogram.zig @@ -62,3 +62,15 @@ test "same first and last characters" { test "word with duplicated character and with two hyphens" { try testIsIsogram("up-to-date", false); } + +test "long isogram with spaces and hyphens" { + try testIsIsogram("a - - B - - c - - D - - e - - F - - g - - H - - i - - J - - k - - L - - m - - N - - o - - P - - q - - R - - s - - T - - u - - V - - w - - X - - y - - Z", true); +} + +test "long phrase with duplicated character in mixed case" { + try testIsIsogram("a - - B - - c - - D - - e - - F - - g - - H - - i - - J - - k - - L - - m - - N - - o - - P - - q - - A - - s - - T - - u - - V - - w - - X - - y - - Z", false); +} + +test "long phrase with first letter repeated at the end" { + try testIsIsogram("a - - B - - c - - D - - e - - F - - g - - H - - i - - J - - k - - L - - m - - N - - o - - P - - q - - R - - s - - T - - u - - V - - w - - X - - y - - A", false); +} diff --git a/exercises/practice/rna-transcription/.meta/supplements.json b/exercises/practice/rna-transcription/.meta/supplements.json new file mode 100644 index 00000000..f20fc7eb --- /dev/null +++ b/exercises/practice/rna-transcription/.meta/supplements.json @@ -0,0 +1,13 @@ +{ + "cases": [ + { + "description": "long rna complement", + "property": "toRna", + "comment": "108 nucleotides: longer than a SIMD block of 64 bytes (AVX-512), with a partial block left over for 8, 16, 32 and 64 lanes.", + "input": { + "dna": "ACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAA" + }, + "expected": "UGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUU" + } + ] +} diff --git a/exercises/practice/rna-transcription/test_rna_transcription.zig b/exercises/practice/rna-transcription/test_rna_transcription.zig index 98fb5e18..94eeff51 100644 --- a/exercises/practice/rna-transcription/test_rna_transcription.zig +++ b/exercises/practice/rna-transcription/test_rna_transcription.zig @@ -33,3 +33,7 @@ test "rna complement of adenine is uracil" { test "rna complement" { try testTranscription("ACGTGGTCTTAA", "UGCACCAGAAUU"); } + +test "long rna complement" { + try testTranscription("ACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAAACGTGGTCTTAA", "UGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUUUGCACCAGAAUU"); +}