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 diff --git a/README.md b/README.md index 77df910..5868de5 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,15 @@ 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`: -To test the exercises, run `./bin/verify-exercises`. +```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 `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 18e83e2..0000000 --- a/bin/verify-exercises +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -temp_dir_base=$(mktemp -d) -echo $(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="$(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)" - 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 - 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