From 4121c8556ca6f9561e91406cd30b6b494a664b24 Mon Sep 17 00:00:00 2001 From: Kevin Bloch Date: Sat, 28 Jun 2025 20:11:50 +0200 Subject: [PATCH 1/6] Remove jq dependency --- bin/verify-exercises | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bin/verify-exercises b/bin/verify-exercises index 18e83e2..748e9d5 100755 --- a/bin/verify-exercises +++ b/bin/verify-exercises @@ -8,16 +8,15 @@ run_test() { temp_dir=${temp_dir_base}/${slug} mkdir -p ${temp_dir} # Copy relevant files to the temp directory (replace solution with example) - solution_file_name="$(jq -r '.files.solution[0]' $1/.meta/config.json)" - test_file="$1/$(jq -r '.files.test[0]' $1/.meta/config.json)" - example_file="$1/$(jq -r '.files.example[0]' $1/.meta/config.json)" + solution_file_name="$slug.gd" + test_file="${slug}_test.gd" + example_file=".meta/example.gd" cp ${test_file} ${temp_dir} cp ${example_file} "${temp_dir}/${solution_file_name}" # Run the tests (cd /opt/test-runner && bin/run.sh $slug $temp_dir $temp_dir) || exit 1 # Check status - test_status="$(jq -r '.status' $temp_dir/results.json)" - if [ "$test_status" != "pass" ]; then + if [[ `cat $temp_dir/results.json` =~ ' "status": "fail",' ]]; then echo "Tests for $slug have failed:" cat $temp_dir/results.json exit 1 From 5fac842bc31ecfb43addab626999c074d6961d31 Mon Sep 17 00:00:00 2001 From: Kevin Bloch Date: Sat, 28 Jun 2025 20:13:16 +0200 Subject: [PATCH 2/6] Update test runner directory to align with test runner repo Cf. https://github.com/exercism/gdscript-test-runner/compare/remove-jq-dep-from-local-test-runner --- README.md | 8 +++++++- bin/verify-exercises | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 77df910..4a138a7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,13 @@ Exercism exercises in GDScript. ## Testing -To set up for testing, clone https://github.com/exercism/gdscript-test-runner and move its contents to `/opt/test-runner`. +To set up for testing, clone https://github.com/exercism/gdscript-test-runner and move its contents to `/opt/exercism/gdscript/test-runner`: + +```sh +git clone https://github.com/exercism/gdscript-test-runner.git +sudo mkdir -p /opt/exercism/gdscript/ +sudo mv gdscript-test-runner/ /opt/exercism/gdscript/test-runner/ +``` To test the exercises, run `./bin/verify-exercises`. This command will iterate over all exercises and check to see if their exemplar/example implementation passes all the tests. diff --git a/bin/verify-exercises b/bin/verify-exercises index 748e9d5..cb50748 100755 --- a/bin/verify-exercises +++ b/bin/verify-exercises @@ -14,7 +14,7 @@ run_test() { cp ${test_file} ${temp_dir} cp ${example_file} "${temp_dir}/${solution_file_name}" # Run the tests - (cd /opt/test-runner && bin/run.sh $slug $temp_dir $temp_dir) || exit 1 + (cd /opt/exercism/gdscript/test-runner && bin/run.sh $slug $temp_dir $temp_dir) || exit 1 # Check status if [[ `cat $temp_dir/results.json` =~ ' "status": "fail",' ]]; then echo "Tests for $slug have failed:" From d4d4a27c4e04ab1a67f6ba7e287af4535d647ce5 Mon Sep 17 00:00:00 2001 From: Kevin Bloch Date: Sat, 28 Jun 2025 20:15:30 +0200 Subject: [PATCH 3/6] Fixes thanks to shellcheck --- bin/verify-exercises | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/bin/verify-exercises b/bin/verify-exercises index cb50748..1782bce 100755 --- a/bin/verify-exercises +++ b/bin/verify-exercises @@ -1,40 +1,40 @@ #!/usr/bin/env bash temp_dir_base=$(mktemp -d) -echo $(godot --version) +godot --version run_test() { - slug=$(basename $1) + slug=$(basename "$1") temp_dir=${temp_dir_base}/${slug} - mkdir -p ${temp_dir} + mkdir -p "${temp_dir}" # Copy relevant files to the temp directory (replace solution with example) solution_file_name="$slug.gd" test_file="${slug}_test.gd" example_file=".meta/example.gd" - cp ${test_file} ${temp_dir} + cp "${test_file}" "${temp_dir}" cp ${example_file} "${temp_dir}/${solution_file_name}" # Run the tests - (cd /opt/exercism/gdscript/test-runner && bin/run.sh $slug $temp_dir $temp_dir) || exit 1 + (cd /opt/exercism/gdscript/test-runner && bin/run.sh "$slug" "$temp_dir" "$temp_dir") || exit 1 # Check status - if [[ `cat $temp_dir/results.json` =~ ' "status": "fail",' ]]; then + if [[ $(cat "$temp_dir"/results.json) =~ ' "status": "fail",' ]]; then echo "Tests for $slug have failed:" - cat $temp_dir/results.json + cat "$temp_dir"/results.json exit 1 fi } # Verify the Concept Exercises for concept_exercise_dir in ./exercises/concept/*/; do - if [ -d $concept_exercise_dir ]; then + if [ -d "$concept_exercise_dir" ]; then echo "Checking $(basename "${concept_exercise_dir}") exercise..." - run_test $concept_exercise_dir + run_test "$concept_exercise_dir" fi done # Verify the Practice Exercises for practice_exercise_dir in ./exercises/practice/*/; do - if [ -d $practice_exercise_dir ]; then + if [ -d "$practice_exercise_dir" ]; then echo "Checking $(basename "${practice_exercise_dir}") exercise..." - run_test $practice_exercise_dir + run_test "$practice_exercise_dir" fi done From 8c69e6f0a2144d28feaa3cfea8acee4e860a1fa3 Mon Sep 17 00:00:00 2001 From: Kevin Bloch Date: Sat, 5 Jul 2025 17:56:16 +0200 Subject: [PATCH 4/6] Don't use ${} where simpler $ suffices --- bin/verify-exercises | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/verify-exercises b/bin/verify-exercises index 1782bce..efcfca5 100755 --- a/bin/verify-exercises +++ b/bin/verify-exercises @@ -5,14 +5,14 @@ godot --version run_test() { slug=$(basename "$1") - temp_dir=${temp_dir_base}/${slug} - mkdir -p "${temp_dir}" + temp_dir="$temp_dir_base/$slug" + mkdir -p "$temp_dir" # Copy relevant files to the temp directory (replace solution with example) solution_file_name="$slug.gd" test_file="${slug}_test.gd" example_file=".meta/example.gd" - cp "${test_file}" "${temp_dir}" - cp ${example_file} "${temp_dir}/${solution_file_name}" + cp "$test_file" "$temp_dir" + cp "$example_file" "$temp_dir/$solution_file_name" # Run the tests (cd /opt/exercism/gdscript/test-runner && bin/run.sh "$slug" "$temp_dir" "$temp_dir") || exit 1 # Check status @@ -26,7 +26,7 @@ run_test() { # Verify the Concept Exercises for concept_exercise_dir in ./exercises/concept/*/; do if [ -d "$concept_exercise_dir" ]; then - echo "Checking $(basename "${concept_exercise_dir}") exercise..." + echo "Checking $(basename "$concept_exercise_dir") exercise..." run_test "$concept_exercise_dir" fi done @@ -34,7 +34,7 @@ done # Verify the Practice Exercises for practice_exercise_dir in ./exercises/practice/*/; do if [ -d "$practice_exercise_dir" ]; then - echo "Checking $(basename "${practice_exercise_dir}") exercise..." + echo "Checking $(basename "$practice_exercise_dir") exercise..." run_test "$practice_exercise_dir" fi done From ab00e2d443c7f1595ce49a5927793e93e12b98e5 Mon Sep 17 00:00:00 2001 From: Kevin Bloch Date: Sat, 12 Jul 2025 10:59:08 +0200 Subject: [PATCH 5/6] Convert verify-exercises to GDScript to still properly process config.json but without jq --- README.md | 2 +- bin/verify-exercises | 40 ------------------ bin/verify-exercises.gd | 90 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 41 deletions(-) delete mode 100755 bin/verify-exercises create mode 100755 bin/verify-exercises.gd diff --git a/README.md b/README.md index 4a138a7..5868de5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ sudo mkdir -p /opt/exercism/gdscript/ sudo mv gdscript-test-runner/ /opt/exercism/gdscript/test-runner/ ``` -To test the exercises, run `./bin/verify-exercises`. +To test the exercises, run `godot --headless -s bin/verify-exercises.gd`. This command will iterate over all exercises and check to see if their exemplar/example implementation passes all the tests. ### Track linting diff --git a/bin/verify-exercises b/bin/verify-exercises deleted file mode 100755 index efcfca5..0000000 --- a/bin/verify-exercises +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash - -temp_dir_base=$(mktemp -d) -godot --version - -run_test() { - slug=$(basename "$1") - temp_dir="$temp_dir_base/$slug" - mkdir -p "$temp_dir" - # Copy relevant files to the temp directory (replace solution with example) - solution_file_name="$slug.gd" - test_file="${slug}_test.gd" - example_file=".meta/example.gd" - cp "$test_file" "$temp_dir" - cp "$example_file" "$temp_dir/$solution_file_name" - # Run the tests - (cd /opt/exercism/gdscript/test-runner && bin/run.sh "$slug" "$temp_dir" "$temp_dir") || exit 1 - # Check status - if [[ $(cat "$temp_dir"/results.json) =~ ' "status": "fail",' ]]; then - echo "Tests for $slug have failed:" - cat "$temp_dir"/results.json - exit 1 - fi -} - -# Verify the Concept Exercises -for concept_exercise_dir in ./exercises/concept/*/; do - if [ -d "$concept_exercise_dir" ]; then - echo "Checking $(basename "$concept_exercise_dir") exercise..." - run_test "$concept_exercise_dir" - fi -done - -# Verify the Practice Exercises -for practice_exercise_dir in ./exercises/practice/*/; do - if [ -d "$practice_exercise_dir" ]; then - echo "Checking $(basename "$practice_exercise_dir") exercise..." - run_test "$practice_exercise_dir" - fi -done diff --git a/bin/verify-exercises.gd b/bin/verify-exercises.gd new file mode 100755 index 0000000..b4594a6 --- /dev/null +++ b/bin/verify-exercises.gd @@ -0,0 +1,90 @@ +extends SceneTree + +# godot --headless -s bin/verify-exercises.gd + +func _init(): + # Calling `quit(1)` doesn't stop the program immediately, so a `return` is necessary. + # That's why errors are checked directly in `_init()`, instead of calling `quit(1)` + # in each method. + var temp_dir_base = DirAccess.create_temp("gdscript-verify-exercises") + if temp_dir_base == null: + push_error("Failed to create base temporary directory: %s", DirAccess.get_open_error()) + quit(1) + return + var temp_dir_base_path = temp_dir_base.get_current_dir() + + for exercise_base in [ + "exercises/practice", + "exercises/concept" + ]: + if run_tests(exercise_base, temp_dir_base_path) != OK: + quit(1) + return + quit() + +func run_tests(exercise_base: String, temp_dir_base_path: String) -> Error: + for slug in DirAccess.get_directories_at(exercise_base): + var result = run_test(slug, exercise_base + "/" + slug, temp_dir_base_path) + if result != OK: + return result + return OK + + +func run_test(slug: String, exercise_dir: String, temp_dir_base_path: String) -> Error: + var temp_dir: String = temp_dir_base_path + "/" + slug + DirAccess.make_dir_recursive_absolute(temp_dir) + + var config_path = exercise_dir + "/.meta/config.json" + var config_file = FileAccess.open(config_path, FileAccess.READ) + if not config_file: + push_error("Failed to read config: " + config_path) + return DirAccess.get_open_error() + + var json = JSON.new() + var parse_return_value = json.parse(config_file.get_as_text()) + var config + if parse_return_value == OK: + config = json.data + if typeof(config) != TYPE_DICTIONARY: + push_error("Expected TYPE_DICTIONARY for JSON in: " + config_path) + return ERR_UNCONFIGURED + else: + push_error("JSON Parse Error: ", json.get_error_message(), " in ", config_path, " at line ", json.get_error_line()) + return parse_return_value + + var solution_name: String = config.get("files", {}).get("solution", [])[0] + var test_path = exercise_dir + "/" + config.get("files", {}).get("test", [])[0] + var example_path = exercise_dir + "/" + config.get("files", {}).get("example", [])[0] + var dest_solution = temp_dir + "/" + solution_name + + # Copy test and example files into temp dir + DirAccess.copy_absolute(test_path, temp_dir + "/" + test_path.get_file()) + DirAccess.copy_absolute(example_path, dest_solution) + + # Run external test script + var args = [slug, temp_dir, temp_dir] + var output = [] + var exit_code = OS.execute("/opt/exercism/gdscript/test-runner/bin/run.sh", args, output, true) + if exit_code != 0: + push_error("Test runner failed for ", slug, " with ", output) + return ERR_SCRIPT_FAILED + print(output[0]) + + # Check test result + var results_path = temp_dir + "/results.json" + if not FileAccess.file_exists(results_path): + push_error("Missing results.json for ", slug, " at ", results_path) + return ERR_FILE_NOT_FOUND + var results_file = FileAccess.open(results_path, FileAccess.READ) + parse_return_value = json.parse(results_file.get_as_text()) + var results + if parse_return_value == OK: + results = json.data + if results.get("status") != "pass": + push_error("Tests for ", slug, " have failed:") + push_error(JSON.stringify(results, "\t")) + return FAILED + else: + push_error("JSON Parse Error: ", json.get_error_message(), " in ", config_path, " at line ", json.get_error_line()) + return parse_return_value + return OK From 205d2c038ae11ba014fa0738d8da8e94df8dc174 Mon Sep 17 00:00:00 2001 From: Kevin Bloch Date: Sat, 12 Jul 2025 11:02:30 +0200 Subject: [PATCH 6/6] Update test runner to use GDScript --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3bb9e8e..fe51230 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,4 +32,4 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Verify all exercises - run: bin/verify-exercises + run: godot --headless -s bin/verify-exercises.gd