extends SceneTree ## Runs every test script in res://tests whose file name starts with "test_". ## Every method whose name starts with "test_" is one test. A test fails if a ## check fails or if an error is reported while it runs, for example a call ## on a null value. Exits with code 1 if any test failed or if no test ran, ## so that a renamed test folder or misnamed tests cannot pass as a clean run. ## ## From the repository root: ## godot --headless --path game -s res://tests/run_tests.gd const TEST_DIR := "res://tests" ## Counts the errors that are reported while tests run. Warnings do not count. class ErrorCounter extends Logger: var _mutex := Mutex.new() var _errors := 0 func errors() -> int: _mutex.lock() var count := _errors _mutex.unlock() return count func _log_error( _function: String, _file: String, _line: int, _code: String, _rationale: String, _editor_notify: bool, error_type: int, _script_backtraces: Array[ScriptBacktrace] ) -> void: if error_type == ERROR_TYPE_WARNING: return _mutex.lock() _errors += 1 _mutex.unlock() func _initialize() -> void: _run_all.call_deferred() func _run_all() -> void: var counter := ErrorCounter.new() OS.add_logger(counter) var passed := 0 var failed := 0 var files := DirAccess.get_files_at(TEST_DIR) files.sort() for file in files: if not (file.begins_with("test_") and file.ends_with(".gd")): continue var script: GDScript = load(TEST_DIR.path_join(file)) if script == null or not script.can_instantiate(): failed += 1 print("FAIL %s the script does not load; see the error above" % file) continue for method in script.get_script_method_list(): var test_name: String = method["name"] if not test_name.begins_with("test_"): continue var test: TestCase = script.new() test.tree = self var errors_before := counter.errors() await test.call(test_name) var errors := counter.errors() - errors_before if errors > 0: test.failures.append("%d error(s) reported while the test ran; see above" % errors) if test.failures.is_empty(): passed += 1 print("ok %s %s" % [file, test_name]) else: failed += 1 print("FAIL %s %s" % [file, test_name]) for failure in test.failures: print(" " + failure) OS.remove_logger(counter) print("%d passed, %d failed" % [passed, failed]) quit(1 if failed > 0 or passed == 0 else 0)