diff --git a/addons/beehave/libs/windows/beehave.windows.editor.x86_64.dll b/addons/beehave/libs/windows/beehave.windows.editor.x86_64.dll index 11f8c916..93434dad 100644 Binary files a/addons/beehave/libs/windows/beehave.windows.editor.x86_64.dll and b/addons/beehave/libs/windows/beehave.windows.editor.x86_64.dll differ diff --git a/addons/beehave/libs/windows/~beehave.windows.editor.x86_64.dll b/addons/beehave/libs/windows/~beehave.windows.editor.x86_64.dll index 11f8c916..93434dad 100644 Binary files a/addons/beehave/libs/windows/~beehave.windows.editor.x86_64.dll and b/addons/beehave/libs/windows/~beehave.windows.editor.x86_64.dll differ diff --git a/addons/gdUnit4/GdUnitRunner.cfg b/addons/gdUnit4/GdUnitRunner.cfg index 31365dab..59dd7687 100644 --- a/addons/gdUnit4/GdUnitRunner.cfg +++ b/addons/gdUnit4/GdUnitRunner.cfg @@ -1 +1 @@ -{"included":{"res://test/beehave_tree_test.gd":[]},"server_port":31002,"skipped":{},"version":"1.0"} \ No newline at end of file +{"included":{"res://test/":[]},"server_port":31002,"skipped":{},"version":"1.0"} \ No newline at end of file diff --git a/addons/gdUnit4/bin/GdUnitBuildTool.gd b/addons/gdUnit4/bin/GdUnitBuildTool.gd index 21eeb5a1..70709920 100644 --- a/addons/gdUnit4/bin/GdUnitBuildTool.gd +++ b/addons/gdUnit4/bin/GdUnitBuildTool.gd @@ -2,7 +2,9 @@ extends SceneTree enum { - INIT, PROCESSING, EXIT + INIT, + PROCESSING, + EXIT } const RETURN_SUCCESS = 0 @@ -11,9 +13,19 @@ const RETURN_WARNING = 101 var _console := CmdConsole.new() var _cmd_options: = CmdOptions.new([ - CmdOption.new("-scp, --src_class_path", "-scp ", "The full class path of the source file.", TYPE_STRING), - CmdOption.new("-scl, --src_class_line", "-scl ", "The selected line number to generate test case.", TYPE_INT) - ]) + CmdOption.new( + "-scp, --src_class_path", + "-scp ", + "The full class path of the source file.", + TYPE_STRING + ), + CmdOption.new( + "-scl, --src_class_line", + "-scl ", + "The selected line number to generate test case.", + TYPE_INT + ) +]) var _status := INIT var _source_file :String = "" @@ -49,13 +61,11 @@ func _idle(_delta): var script := ResourceLoader.load(_source_file) as Script if script == null: exit(RETURN_ERROR, "Can't load source file %s!" % _source_file) - var result := GdUnitTestSuiteBuilder.create(script, _source_line) if result.is_error(): print_json_error(result.error_message()) exit(RETURN_ERROR, result.error_message()) return - _console.prints_color("Added testcase: %s" % result.value(), Color.CORNFLOWER_BLUE) print_json_result(result.value()) exit(RETURN_SUCCESS) @@ -86,7 +96,8 @@ func print_json_error(error :String) -> void: func show_options() -> void: _console.prints_color(" Usage:", Color.DARK_SALMON) _console.prints_color(" build -scp -scl ", Color.DARK_SALMON) - _console.prints_color("-- Options ---------------------------------------------------------------------------------------", Color.DARK_SALMON).new_line() + _console.prints_color("-- Options ---------------------------------------------------------------------------------------", + Color.DARK_SALMON).new_line() for option in _cmd_options.default_options(): descripe_option(option) diff --git a/addons/gdUnit4/bin/GdUnitCmdTool.gd b/addons/gdUnit4/bin/GdUnitCmdTool.gd index 6ab26f88..00d5ad95 100644 --- a/addons/gdUnit4/bin/GdUnitCmdTool.gd +++ b/addons/gdUnit4/bin/GdUnitCmdTool.gd @@ -3,9 +3,11 @@ extends SceneTree const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") + #warning-ignore-all:return_value_discarded -class CLIRunner extends Node: - +class CLIRunner: + extends Node + enum { READY, INIT, @@ -13,61 +15,111 @@ class CLIRunner extends Node: STOP, EXIT } - + const DEFAULT_REPORT_COUNT = 20 - const RETURN_SUCCESS = 0 - const RETURN_ERROR = 100 - const RETURN_ERROR_HEADLESS_NOT_SUPPORTED = 103 - const RETURN_WARNING = 101 + const RETURN_SUCCESS = 0 + const RETURN_ERROR = 100 + const RETURN_ERROR_HEADLESS_NOT_SUPPORTED = 103 + const RETURN_ERROR_GODOT_VERSION_NOT_SUPPORTED = 104 + const RETURN_WARNING = 101 var _state = READY - var _test_suites_to_process :Array + var _test_suites_to_process: Array var _executor var _cs_executor - var _report :GdUnitHtmlReport + var _report: GdUnitHtmlReport var _report_dir: String var _report_max: int = DEFAULT_REPORT_COUNT var _runner_config := GdUnitRunnerConfig.new() var _console := CmdConsole.new() - var _cmd_options: = CmdOptions.new([ - CmdOption.new("-a, --add", "-a ", "Adds the given test suite or directory to the execution pipeline.", TYPE_STRING), - CmdOption.new("-i, --ignore", "-i ", "Adds the given test suite or test case to the ignore list.", TYPE_STRING), - CmdOption.new("-c, --continue", "", "By default GdUnit will abort checked first test failure to be fail fast, instead of stop after first failure you can use this option to run the complete test set."), - CmdOption.new("-conf, --config", "-conf [testconfiguration.cfg]", "Run all tests by given test configuration. Default is 'GdUnitRunner.cfg'", TYPE_STRING, true), - CmdOption.new("-help", "", "Shows this help message."), - CmdOption.new("--help-advanced", "", "Shows advanced options.") - ], [ + var _cmd_options := CmdOptions.new([ + CmdOption.new( + "-a, --add", + "-a ", + "Adds the given test suite or directory to the execution pipeline.", + TYPE_STRING + ), + CmdOption.new( + "-i, --ignore", + "-i ", + "Adds the given test suite or test case to the ignore list.", + TYPE_STRING + ), + CmdOption.new( + "-c, --continue", + "", + """By default GdUnit will abort checked first test failure to be fail fast, + instead of stop after first failure you can use this option to run the complete test set.""".dedent() + ), + CmdOption.new( + "-conf, --config", + "-conf [testconfiguration.cfg]", + "Run all tests by given test configuration. Default is 'GdUnitRunner.cfg'", + TYPE_STRING, + true + ), + CmdOption.new( + "-help", "", + "Shows this help message." + ), + CmdOption.new("--help-advanced", "", + "Shows advanced options." + ) + ], + [ # advanced options - CmdOption.new("-rd, --report-directory", "-rd ", "Specifies the output directory in which the reports are to be written. The default is res://reports/.", TYPE_STRING, true), - CmdOption.new("-rc, --report-count", "-rc ", "Specifies how many reports are saved before they are deleted. The default is "+str(DEFAULT_REPORT_COUNT)+".", TYPE_INT, true), + CmdOption.new( + "-rd, --report-directory", + "-rd ", + "Specifies the output directory in which the reports are to be written. The default is res://reports/.", + TYPE_STRING, + true + ), + CmdOption.new( + "-rc, --report-count", + "-rc ", + "Specifies how many reports are saved before they are deleted. The default is %s." % str(DEFAULT_REPORT_COUNT), + TYPE_INT, + true + ), #CmdOption.new("--list-suites", "--list-suites [directory]", "Lists all test suites located in the given directory.", TYPE_STRING), #CmdOption.new("--describe-suite", "--describe-suite ", "Shows the description of selected test suite.", TYPE_STRING), - CmdOption.new("--info", "", "Shows the GdUnit version info"), - CmdOption.new("--selftest", "", "Runs the GdUnit self test"), + CmdOption.new( + "--info", "", + "Shows the GdUnit version info" + ), + CmdOption.new( + "--selftest", "", + "Runs the GdUnit self test" + ) ]) - - + + func _ready(): _state = INIT - _report_dir = GdUnitTools.current_dir() + "reports" + _report_dir = GdUnitFileAccess.current_dir() + "reports" _executor = load("res://addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd").new() # stop checked first test failure to fail fast _executor.fail_fast(true) - - if GdUnit4MonoApiLoader.is_mono_supported(): - prints("GdUnit4Mono Version %s loaded." % GdUnit4MonoApiLoader.version()) - _cs_executor = GdUnit4MonoApiLoader.create_executor(self) - var err = GdUnitSignals.instance().gdunit_event.connect(Callable(self, "_on_gdunit_event")) + if GdUnit4CSharpApiLoader.is_mono_supported(): + prints("GdUnit4Mono Version %s loaded." % GdUnit4CSharpApiLoader.version()) + _cs_executor = GdUnit4CSharpApiLoader.create_executor(self) + var err := GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event) if err != OK: prints("gdUnitSignals failed") push_error("Error checked startup, can't connect executor for 'send_event'") quit(RETURN_ERROR) - - - func _process(_delta): + + + func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + prints("Finallize .. done") + + + func _process(_delta :float) -> void: match _state: INIT: - gdUnitInit() + init_gd_unit() _state = RUN RUN: # all test suites executed @@ -87,98 +139,151 @@ class CLIRunner extends Node: _state = EXIT _on_gdunit_event(GdUnitStop.new()) quit(report_exit_code(_report)) - - - func quit(code :int) -> void: + + + func quit(code: int) -> void: GdUnitTools.dispose_all() await GdUnitMemoryObserver.gc_on_guarded_instances() await get_tree().physics_frame get_tree().quit(code) - - - func set_report_dir(path :String) -> void: - _report_dir = ProjectSettings.globalize_path(GdUnitTools.make_qualified_path(path)) - _console.prints_color("Set write reports to %s" % _report_dir, Color.DEEP_SKY_BLUE) - - - func set_report_count(count :String) -> void: + + + func set_report_dir(path: String) -> void: + _report_dir = ProjectSettings.globalize_path(GdUnitFileAccess.make_qualified_path(path)) + _console.prints_color( + "Set write reports to %s" % _report_dir, + Color.DEEP_SKY_BLUE + ) + + + func set_report_count(count: String) -> void: var report_count := count.to_int() if report_count < 1: - _console.prints_error("Invalid report history count '%s' set back to default %d" % [count, DEFAULT_REPORT_COUNT]) + _console.prints_error( + "Invalid report history count '%s' set back to default %d" + % [count, DEFAULT_REPORT_COUNT] + ) _report_max = DEFAULT_REPORT_COUNT else: - _console.prints_color("Set report history count to %s" % count, Color.DEEP_SKY_BLUE) + _console.prints_color( + "Set report history count to %s" % count, + Color.DEEP_SKY_BLUE + ) _report_max = report_count - - + + func disable_fail_fast() -> void: - _console.prints_color("Disabled fail fast!", Color.DEEP_SKY_BLUE) + _console.prints_color( + "Disabled fail fast!", + Color.DEEP_SKY_BLUE + ) _executor.fail_fast(false) - - + + func run_self_test() -> void: - _console.prints_color("Run GdUnit4 self tests.", Color.DEEP_SKY_BLUE) + _console.prints_color( + "Run GdUnit4 self tests.", + Color.DEEP_SKY_BLUE + ) disable_fail_fast() _runner_config.self_test() - - + + func show_version() -> void: - _console.prints_color("Godot %s" % Engine.get_version_info().get("string"), Color.DARK_SALMON) - var config = ConfigFile.new() - config.load('addons/gdUnit4/plugin.cfg') - _console.prints_color("GdUnit4 %s" % config.get_value('plugin', 'version'), Color.DARK_SALMON) + _console.prints_color( + "Godot %s" % Engine.get_version_info().get("string"), + Color.DARK_SALMON + ) + var config := ConfigFile.new() + config.load("addons/gdUnit4/plugin.cfg") + _console.prints_color( + "GdUnit4 %s" % config.get_value("plugin", "version"), + Color.DARK_SALMON + ) quit(RETURN_SUCCESS) - - - func show_options(show_advanced :bool = false) -> void: - _console.prints_color(" Usage:", Color.DARK_SALMON) - _console.prints_color(" runtest -a ", Color.DARK_SALMON) - _console.prints_color(" runtest -a -i ", Color.DARK_SALMON).new_line() - _console.prints_color("-- Options ---------------------------------------------------------------------------------------", Color.DARK_SALMON).new_line() + + + func show_options(show_advanced: bool = false) -> void: + _console.prints_color( + """ + Usage: + runtest -a + runtest -a -i + """.dedent(), + Color.DARK_SALMON + ).prints_color( + "-- Options ---------------------------------------------------------------------------------------", + Color.DARK_SALMON + ).new_line() for option in _cmd_options.default_options(): descripe_option(option) if show_advanced: - _console.prints_color("-- Advanced options --------------------------------------------------------------------------", Color.DARK_SALMON).new_line() + _console.prints_color( + "-- Advanced options --------------------------------------------------------------------------", + Color.DARK_SALMON + ).new_line() for option in _cmd_options.advanced_options(): descripe_option(option) - - - func descripe_option(cmd_option :CmdOption) -> void: - _console.print_color(" %-40s" % str(cmd_option.commands()), Color.CORNFLOWER_BLUE) - _console.prints_color(cmd_option.description(), Color.LIGHT_GREEN) + + + func descripe_option(cmd_option: CmdOption) -> void: + _console.print_color( + " %-40s" % str(cmd_option.commands()), + Color.CORNFLOWER_BLUE + ) + _console.prints_color( + cmd_option.description(), + Color.LIGHT_GREEN + ) if not cmd_option.help().is_empty(): - _console.prints_color("%-4s %s" % ["", cmd_option.help()], Color.DARK_TURQUOISE) + _console.prints_color( + "%-4s %s" % ["", cmd_option.help()], + Color.DARK_TURQUOISE + ) _console.new_line() - - + + func load_test_config(path := GdUnitRunnerConfig.CONFIG_FILE) -> void: - _console.print_color("Loading test configuration %s\n" % path, Color.CORNFLOWER_BLUE) + _console.print_color( + "Loading test configuration %s\n" % path, + Color.CORNFLOWER_BLUE + ) _runner_config.load_config(path) - - + + func show_help() -> void: show_options() quit(RETURN_SUCCESS) - - + + func show_advanced_help() -> void: show_options(true) quit(RETURN_SUCCESS) - - - func gdUnitInit() -> void: - _console.prints_color("----------------------------------------------------------------------------------------------", Color.DARK_SALMON) - _console.prints_color(" GdUnit4 Comandline Tool", Color.DARK_SALMON) - _console.new_line() - + + + func init_gd_unit() -> void: + _console.prints_color( + """ + -------------------------------------------------------------------------------------------------- + GdUnit4 Comandline Tool + --------------------------------------------------------------------------------------------------""".dedent(), + Color.DARK_SALMON + ).new_line() + if DisplayServer.get_name() == "headless": - _console.prints_error("Headless mode is not supported!").new_line() - _console.print_color("Tests that use UI interaction do not work in headless mode because 'InputEvents' are not transported by the Godot engine and thus have no effect!", Color.CORNFLOWER_BLUE)\ - .new_line().new_line() - _console.prints_error("Abnormal exit with %d" % RETURN_ERROR_HEADLESS_NOT_SUPPORTED) + _console.prints_error( + "Headless mode is not supported!" + ).print_color(""" + Tests that use UI interaction do not work in headless mode because 'InputEvents' are not transported + by the Godot engine and thus have no effect! + """.dedent(), + Color.CORNFLOWER_BLUE + ).prints_error( + "Abnormal exit with %d" % RETURN_ERROR_HEADLESS_NOT_SUPPORTED + ) quit(RETURN_ERROR_HEADLESS_NOT_SUPPORTED) return - + var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitCmdTool.gd") var result := cmd_parser.parse(OS.get_cmdline_args()) if result.is_error(): @@ -188,57 +293,56 @@ class CLIRunner extends Node: _state = STOP quit(RETURN_ERROR) return - if result.is_empty(): show_help() return - # build runner config by given commands - result = CmdCommandHandler.new(_cmd_options)\ - .register_cb("-help", Callable(self, "show_help"))\ - .register_cb("--help-advanced", Callable(self, "show_advanced_help"))\ - .register_cb("-a", Callable(_runner_config, "add_test_suite"))\ - .register_cbv("-a", Callable(_runner_config, "add_test_suites"))\ - .register_cb("-i", Callable(_runner_config, "skip_test_suite"))\ - .register_cbv("-i", Callable(_runner_config, "skip_test_suites"))\ - .register_cb("-rd", Callable(self, "set_report_dir"))\ - .register_cb("-rc", Callable(self, "set_report_count"))\ - .register_cb("--selftest", Callable(self, "run_self_test"))\ - .register_cb("-c", Callable(self, "disable_fail_fast"))\ - .register_cb("-conf", Callable(self, "load_test_config"))\ - .register_cb("--info", Callable(self, "show_version"))\ - .execute(result.value()) + result = ( + CmdCommandHandler.new(_cmd_options) + .register_cb("-help", Callable(self, "show_help")) + .register_cb("--help-advanced", Callable(self, "show_advanced_help")) + .register_cb("-a", Callable(_runner_config, "add_test_suite")) + .register_cbv("-a", Callable(_runner_config, "add_test_suites")) + .register_cb("-i", Callable(_runner_config, "skip_test_suite")) + .register_cbv("-i", Callable(_runner_config, "skip_test_suites")) + .register_cb("-rd", Callable(self, "set_report_dir")) + .register_cb("-rc", Callable(self, "set_report_count")) + .register_cb("--selftest", Callable(self, "run_self_test")) + .register_cb("-c", Callable(self, "disable_fail_fast")) + .register_cb("-conf", Callable(self, "load_test_config")) + .register_cb("--info", Callable(self, "show_version")) + .execute(result.value()) + ) if result.is_error(): _console.prints_error(result.error_message()) _state = STOP quit(RETURN_ERROR) - + _test_suites_to_process = load_testsuites(_runner_config) if _test_suites_to_process.is_empty(): _console.prints_warning("No test suites found, abort test run!") - _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) + _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) _state = STOP quit(RETURN_SUCCESS) - - var total_test_count = _collect_test_case_count(_test_suites_to_process) + var total_test_count := _collect_test_case_count(_test_suites_to_process) _on_gdunit_event(GdUnitInit.new(_test_suites_to_process.size(), total_test_count)) - - - func load_testsuites(config :GdUnitRunnerConfig) -> Array[Node]: - var test_suites_to_process :Array[Node] = [] + + + func load_testsuites(config: GdUnitRunnerConfig) -> Array[Node]: + var test_suites_to_process: Array[Node] = [] var to_execute := config.to_execute() # scan for the requested test suites - var _scanner := GdUnitTestSuiteScanner.new() - for resource_path_ in to_execute.keys(): - var selected_tests :PackedStringArray = to_execute.get(resource_path_) - var scaned_suites := _scanner.scan(resource_path_) + var ts_scanner := GdUnitTestSuiteScanner.new() + for as_resource_path in to_execute.keys(): + var selected_tests: PackedStringArray = to_execute.get(as_resource_path) + var scaned_suites := ts_scanner.scan(as_resource_path) skip_test_case(scaned_suites, selected_tests) test_suites_to_process.append_array(scaned_suites) skip_suites(test_suites_to_process, config) return test_suites_to_process - - - func skip_test_case(test_suites :Array, test_case_names :Array) -> void: + + + func skip_test_case(test_suites: Array, test_case_names: Array) -> void: if test_case_names.is_empty(): return for test_suite in test_suites: @@ -246,15 +350,15 @@ class CLIRunner extends Node: if not test_case_names.has(test_case.get_name()): test_suite.remove_child(test_case) test_case.free() - - - func skip_suites(test_suites :Array, config :GdUnitRunnerConfig) -> void: + + + func skip_suites(test_suites: Array, config: GdUnitRunnerConfig) -> void: var skipped := config.skipped() for test_suite in test_suites: skip_suite(test_suite, skipped) - - - func skip_suite(test_suite :Node, skipped :Dictionary) -> void: + + + func skip_suite(test_suite: Node, skipped: Dictionary) -> void: var skipped_suites := skipped.keys() if skipped_suites.is_empty(): return @@ -262,11 +366,17 @@ class CLIRunner extends Node: # skipp c# testsuites for now if test_suite.get_script() == null: return - var test_suite_path :String = test_suite.get_meta("ResourcePath") if test_suite.get_script() == null else test_suite.get_script().resource_path + var test_suite_path: String = ( + test_suite.get_meta("ResourcePath") if test_suite.get_script() == null + else test_suite.get_script().resource_path + ) for suite_to_skip in skipped_suites: # if suite skipped by path or name - if suite_to_skip == test_suite_path or (suite_to_skip.is_valid_filename() and suite_to_skip == suite_name): - var skipped_tests :Array = skipped.get(suite_to_skip) + if ( + suite_to_skip == test_suite_path + or (suite_to_skip.is_valid_filename() and suite_to_skip == suite_name) + ): + var skipped_tests: Array = skipped.get(suite_to_skip) # if no tests skipped test the complete suite is skipped if skipped_tests.is_empty(): _console.prints_warning("Skip test suite %s:%s" % suite_to_skip) @@ -274,26 +384,30 @@ class CLIRunner extends Node: else: # skip tests for test_to_skip in skipped_tests: - var test_case :_TestCase = test_suite.find_child(test_to_skip, true, false) + var test_case: _TestCase = test_suite.find_child(test_to_skip, true, false) if test_case: test_case.skip(true) _console.prints_warning("Skip test case %s:%s" % [suite_to_skip, test_to_skip]) else: - _console.prints_error("Can't skip test '%s' checked test suite '%s', no test with given name exists!" % [test_to_skip, suite_to_skip]) - - - func _collect_test_case_count(testSuites :Array) -> int: - var total :int = 0 - for test_suite in testSuites: + _console.prints_error( + "Can't skip test '%s' checked test suite '%s', no test with given name exists!" + % [test_to_skip, suite_to_skip] + ) + + + func _collect_test_case_count(test_suites: Array) -> int: + var total: int = 0 + for test_suite in test_suites: total += (test_suite as Node).get_child_count() return total - - - func PublishEvent(data :Dictionary) -> void: + + + # gdlint: disable=function-name + func PublishEvent(data: Dictionary) -> void: _on_gdunit_event(GdUnitEvent.new().deserialize(data)) - - - func _on_gdunit_event(event :GdUnitEvent): + + + func _on_gdunit_event(event: GdUnitEvent): match event.type(): GdUnitEvent.INIT: _report = GdUnitHtmlReport.new(_report_dir) @@ -301,12 +415,23 @@ class CLIRunner extends Node: var report_path := _report.write() _report.delete_history(_report_max) JUnitXmlReport.new(_report._report_path, _report.iteration()).write(_report) - _console.prints_color("Total test suites: %s" % _report.suite_count(), Color.DARK_SALMON) - _console.prints_color("Total test cases: %s" % _report.test_count(), Color.DARK_SALMON) - _console.prints_color("Total time: %s" % LocalTime.elapsed(_report.duration()), Color.DARK_SALMON) - _console.prints_color("Open Report at: file://%s" % report_path, Color.CORNFLOWER_BLUE) + _console.prints_color( + "Total test suites: %s" % _report.suite_count(), + Color.DARK_SALMON + ).prints_color( + "Total test cases: %s" % _report.test_count(), + Color.DARK_SALMON + ).prints_color( + "Total time: %s" % LocalTime.elapsed(_report.duration()), + Color.DARK_SALMON + ).prints_color( + "Open Report at: file://%s" % report_path, + Color.CORNFLOWER_BLUE + ) GdUnitEvent.TESTSUITE_BEFORE: - _report.add_testsuite_report(GdUnitTestSuiteReport.new(event.resource_path(), event.suite_name())) + _report.add_testsuite_report( + GdUnitTestSuiteReport.new(event.resource_path(), event.suite_name()) + ) GdUnitEvent.TESTSUITE_AFTER: _report.update_test_suite_report( event.resource_path(), @@ -318,9 +443,17 @@ class CLIRunner extends Node: event.skipped_count(), event.failed_count(), event.orphan_nodes(), - event.reports()) + event.reports() + ) GdUnitEvent.TESTCASE_BEFORE: - _report.add_testcase_report(event.resource_path(), GdUnitTestCaseReport.new(event.resource_path(), event.suite_name(), event.test_name())) + _report.add_testcase_report( + event.resource_path(), + GdUnitTestCaseReport.new( + event.resource_path(), + event.suite_name(), + event.test_name() + ) + ) GdUnitEvent.TESTCASE_AFTER: var test_report := GdUnitTestCaseReport.new( event.resource_path(), @@ -332,72 +465,110 @@ class CLIRunner extends Node: event.orphan_nodes(), event.is_skipped(), event.reports(), - event.elapsed_time()) + event.elapsed_time() + ) _report.update_testcase_report(event.resource_path(), test_report) print_status(event) - - - func report_exit_code(report :GdUnitHtmlReport) -> int: + + + func report_exit_code(report: GdUnitHtmlReport) -> int: if report.error_count() + report.failure_count() > 0: _console.prints_color("Exit code: %d" % RETURN_ERROR, Color.FIREBRICK) return RETURN_ERROR if report.orphan_count() > 0: _console.prints_color("Exit code: %d" % RETURN_WARNING, Color.GOLDENROD) return RETURN_WARNING - _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) + _console.prints_color("Exit code: %d" % RETURN_SUCCESS, Color.DARK_SALMON) return RETURN_SUCCESS - - - func print_status(event :GdUnitEvent) -> void: + + + func print_status(event: GdUnitEvent) -> void: match event.type(): GdUnitEvent.TESTSUITE_BEFORE: - _console.prints_color("Run Test Suite %s " % event.resource_path(), Color.ANTIQUE_WHITE) + _console.prints_color( + "Run Test Suite %s " % event.resource_path(), + Color.ANTIQUE_WHITE + ) GdUnitEvent.TESTCASE_BEFORE: - _console.print_color(" Run Test: %s > %s :" % [event.resource_path(), event.test_name()], Color.ANTIQUE_WHITE)\ - .prints_color("STARTED", Color.FOREST_GREEN) + _console.print_color( + " Run Test: %s > %s :" % [event.resource_path(), event.test_name()], + Color.ANTIQUE_WHITE + ).prints_color( + "STARTED", + Color.FOREST_GREEN + ).save_cursor() GdUnitEvent.TESTCASE_AFTER: - _console.print_color(" Run Test: %s > %s :" % [event.resource_path(), event.test_name()], Color.ANTIQUE_WHITE) + #_console.restore_cursor() + _console.print_color( + " Run Test: %s > %s :" % [event.resource_path(), event.test_name()], + Color.ANTIQUE_WHITE + ) _print_status(event) _print_failure_report(event.reports()) GdUnitEvent.TESTSUITE_AFTER: _print_failure_report(event.reports()) _print_status(event) - _console.prints_color(" | %d total | %d error | %d failed | %d skipped | %d orphans |\n" % [_report.test_count(), _report.error_count(), _report.failure_count(), _report.skipped_count(), _report.orphan_count()], Color.ANTIQUE_WHITE) - - - func _print_failure_report(reports :Array) -> void: + _console.prints_color( + "Statistics: | %d tests cases | %d error | %d failed | %d skipped | %d orphans |\n" + % [ + _report.test_count(), + _report.error_count(), + _report.failure_count(), + _report.skipped_count(), + _report.orphan_count() + ], + Color.ANTIQUE_WHITE + ) + + + func _print_failure_report(reports: Array) -> void: for report in reports: - if report.is_failure() or report.is_error() or report.is_warning() or report.is_skipped(): - _console.prints_color(" Report:", Color.DARK_TURQUOISE, CmdConsole.BOLD|CmdConsole.UNDERLINE) - var text = GdUnitTools.richtext_normalize(report._to_string()) + if ( + report.is_failure() + or report.is_error() + or report.is_warning() + or report.is_skipped() + ): + _console.prints_color( + " Report:", + Color.DARK_TURQUOISE, CmdConsole.BOLD | CmdConsole.UNDERLINE + ) + var text = GdUnitTools.richtext_normalize(str(report)) for line in text.split("\n"): _console.prints_color(" %s" % line, Color.DARK_TURQUOISE) _console.new_line() - - - func _print_status(event :GdUnitEvent) -> void: + + + func _print_status(event: GdUnitEvent) -> void: if event.is_skipped(): - _console.print_color("SKIPPED", Color.GOLDENROD, CmdConsole.BOLD|CmdConsole.ITALIC) + _console.print_color("SKIPPED", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.ITALIC) elif event.is_failed() or event.is_error(): - _console.print_color("FAILED", Color.CRIMSON, CmdConsole.BOLD) + _console.print_color("FAILED", Color.FIREBRICK, CmdConsole.BOLD) elif event.orphan_nodes() > 0: - _console.print_color("PASSED", Color.GOLDENROD, CmdConsole.BOLD|CmdConsole.UNDERLINE) + _console.print_color("PASSED", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.UNDERLINE) else: _console.print_color("PASSED", Color.FOREST_GREEN, CmdConsole.BOLD) - _console.prints_color(" %s" % LocalTime.elapsed(event.elapsed_time()), Color.CORNFLOWER_BLUE) + _console.prints_color( + " %s" % LocalTime.elapsed(event.elapsed_time()), Color.CORNFLOWER_BLUE + ) -var _cli_runner :CLIRunner +var _cli_runner: CLIRunner func _initialize(): + if Engine.get_version_info().hex < 0x40100: + prints("GdUnit4 requires a minimum of Godot 4.1.x Version!") + quit(CLIRunner.RETURN_ERROR_GODOT_VERSION_NOT_SUPPORTED) + return DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED) _cli_runner = CLIRunner.new() root.add_child(_cli_runner) -func _finalize(): - prints("Finallize ..") - prints("-Orphan nodes report-----------------------") - Window.print_orphan_nodes() - prints("Finallize .. done") +# do not use print statements on _finalize it results in random crashes +#func _finalize(): +# prints("Finallize ..") +# prints("-Orphan nodes report-----------------------") +# Window.print_orphan_nodes() +# prints("Finallize .. done") diff --git a/addons/gdUnit4/bin/GdUnitCopyLog.gd b/addons/gdUnit4/bin/GdUnitCopyLog.gd index 5f57806f..f7bb475b 100644 --- a/addons/gdUnit4/bin/GdUnitCopyLog.gd +++ b/addons/gdUnit4/bin/GdUnitCopyLog.gd @@ -3,6 +3,7 @@ extends MainLoop const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") +# gdlint: disable=max-line-length const NO_LOG_TEMPLATE = """ @@ -23,14 +24,22 @@ const NO_LOG_TEMPLATE = """ """ #warning-ignore-all:return_value_discarded -var _cmd_options: = CmdOptions.new([ - CmdOption.new("-rd, --report-directory", "-rd ", "Specifies the output directory in which the reports are to be written. The default is res://reports/.", TYPE_STRING, true), -]) +var _cmd_options := CmdOptions.new([ + CmdOption.new( + "-rd, --report-directory", + "-rd ", + "Specifies the output directory in which the reports are to be written. The default is res://reports/.", + TYPE_STRING, + true + ) + ]) + +var _report_root_path: String -var _report_root_path :String func _init(): - _report_root_path = GdUnitTools.current_dir() + "reports" + _report_root_path = GdUnitFileAccess.current_dir() + "reports" + func _process(_delta): # check if reports exists @@ -38,81 +47,95 @@ func _process(_delta): prints("no reports found") return true # scan for latest report path - var iteration = GdUnitTools.find_last_path_index(_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX) + var iteration := GdUnitFileAccess.find_last_path_index( + _report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX + ) var report_path = "%s/%s%d" % [_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX, iteration] - # only process if godot logging is enabled if not GdUnitSettings.is_log_enabled(): _patch_report(report_path, "") return true - - # parse possible custom report path, + # parse possible custom report path, var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitCmdTool.gd") # ignore erros and exit quitly if cmd_parser.parse(OS.get_cmdline_args(), true).is_error(): return true - CmdCommandHandler.new(_cmd_options).register_cb("-rd", Callable(self, "set_report_directory")) - + CmdCommandHandler.new(_cmd_options).register_cb("-rd", set_report_directory) # scan for latest godot log and copy to report var godot_log := _scan_latest_godot_log() var result := _copy_and_pach(godot_log, report_path) if result.is_error(): push_error(result.error_message()) return true - _patch_report(report_path, godot_log) return true -func set_report_directory(path :String) -> void: + +func set_report_directory(path: String) -> void: _report_root_path = path + func _scan_latest_godot_log() -> String: var path := GdUnitSettings.get_log_path().get_base_dir() var files_sorted := Array() - for file in GdUnitTools.scan_dir(path): - var file_name := "%s/%s" % [path,file] + for file in GdUnitFileAccess.scan_dir(path): + var file_name := "%s/%s" % [path, file] files_sorted.append(file_name) # sort by name, the name contains the timestamp so we sort at the end by timestamp files_sorted.sort() return files_sorted[-1] -func _patch_report(report_path :String, godot_log :String) -> void: + +func _patch_report(report_path: String, godot_log: String) -> void: var index_file := FileAccess.open("%s/index.html" % report_path, FileAccess.READ_WRITE) if index_file == null: - push_error("Can't add log path to index.html. Error: %s" % GdUnitTools.error_as_string(FileAccess.get_open_error())) + push_error( + "Can't add log path to index.html. Error: %s" + % error_string(FileAccess.get_open_error()) + ) return # if no log file available than add a information howto enable it if godot_log.is_empty(): - FileAccess.open("%s/logging_not_available.html" % report_path, FileAccess.WRITE)\ - .store_string(NO_LOG_TEMPLATE) + FileAccess.open( + "%s/logging_not_available.html" % report_path, + FileAccess.WRITE).store_string(NO_LOG_TEMPLATE) var log_file = "logging_not_available.html" if godot_log.is_empty() else godot_log.get_file() var content := index_file.get_as_text().replace("${log_file}", log_file) # overide it index_file.seek(0) index_file.store_string(content) - + + func _copy_and_pach(from_file: String, to_dir: String) -> GdUnitResult: - var result := GdUnitTools.copy_file(from_file, to_dir) + var result := GdUnitFileAccess.copy_file(from_file, to_dir) if result.is_error(): return result var file := FileAccess.open(from_file, FileAccess.READ) if file == null: - return GdUnitResult.error("Can't find file '%s'. Error: %s" % [from_file, GdUnitTools.error_as_string(FileAccess.get_open_error())]) + return GdUnitResult.error( + "Can't find file '%s'. Error: %s" + % [from_file, error_string(FileAccess.get_open_error())] + ) var content := file.get_as_text() # patch out console format codes for color_index in range(0, 256): var to_replace := "[38;5;%dm" % color_index content = content.replace(to_replace, "") - content = content.replace("", "")\ + content = content\ + .replace("", "")\ .replace(CmdConsole.__CSI_BOLD, "")\ .replace(CmdConsole.__CSI_ITALIC, "")\ .replace(CmdConsole.__CSI_UNDERLINE, "") var to_file := to_dir + "/" + from_file.get_file() file = FileAccess.open(to_file, FileAccess.WRITE) if file == null: - return GdUnitResult.error("Can't open to write '%s'. Error: %s" % [to_file, GdUnitTools.error_as_string(FileAccess.get_open_error())]) + return GdUnitResult.error( + "Can't open to write '%s'. Error: %s" + % [to_file, error_string(FileAccess.get_open_error())] + ) file.store_string(content) return GdUnitResult.empty() + func reports_available() -> bool: return DirAccess.dir_exists_absolute(_report_root_path) diff --git a/addons/gdUnit4/bin/ProjectScanner.gd b/addons/gdUnit4/bin/ProjectScanner.gd index 74bbbcf4..c125d1bb 100644 --- a/addons/gdUnit4/bin/ProjectScanner.gd +++ b/addons/gdUnit4/bin/ProjectScanner.gd @@ -4,87 +4,96 @@ extends SceneTree const CmdConsole = preload("res://addons/gdUnit4/src/cmd/CmdConsole.gd") -var scanner := SourceScanner.new() func _initialize(): set_auto_accept_quit(false) + var scanner := SourceScanner.new(self) root.add_child(scanner) -func _finalize(): - prints("__finalize") - - - +# gdlint: disable=trailing-whitespace class SourceScanner extends Node: enum { INIT, + STARTUP, SCAN, QUIT, DONE } - - var _counter = 0 - var WAIT_TIME_IN_MS = 5.000 var _state = INIT var _console := CmdConsole.new() + var _elapsed_time := 0.0 + var _plugin: EditorPlugin + var _fs: EditorFileSystem + var _scene: SceneTree - func _init(): - _state = SCAN + func _init(scene :SceneTree) -> void: + _scene = scene + _console.prints_color(""" + ======================================================================== + Running project scan:""".dedent(), + Color.CORNFLOWER_BLUE + ) + _state = INIT - func _process(delta): - if _state != SCAN: - return - _counter += delta - if _state == SCAN: - set_process(false) - _console.prints_color("======================================", Color.CORNFLOWER_BLUE) - _console.prints_color("Running project scan:", Color.CORNFLOWER_BLUE) - await scan_project() - set_process(true) - _state = QUIT - if _state == QUIT or _counter >= WAIT_TIME_IN_MS: - _state = DONE - _console.prints_color("Scan project done.", Color.CORNFLOWER_BLUE) - _console.prints_color("======================================", Color.CORNFLOWER_BLUE) - _console.new_line() - await get_tree().process_frame - get_tree().quit(0) + func _process(delta :float) -> void: + _elapsed_time += delta + set_process(false) + await_inital_scan() + await scan_project() + set_process(true) - func scan_project() -> void: - var plugin := EditorPlugin.new() - var fs := plugin.get_editor_interface().get_resource_filesystem() + # !! don't use any await in this phase otherwise the editor will be instable !! + func await_inital_scan() -> void: + if _state == INIT: + _console.prints_color("Wait initial scanning ...", Color.DARK_GREEN) + _plugin = EditorPlugin.new() + _fs = _plugin.get_editor_interface().get_resource_filesystem() + _plugin.get_editor_interface().set_plugin_enabled("gdUnit4", false) + _state = STARTUP - if fs.has_method("reimport_files--"): - _console.prints_color("Reimport images :", Color.SANDY_BROWN) - for source in ["res://addons/gdUnit4/src/ui/assets/orphan", "res://addons/gdUnit4/src/ui/assets/spinner", "res://addons/gdUnit4/src/ui/assets/"]: - var image_files := Array(DirAccess.get_files_at(source)) - #_console.prints_color("%s" % image_files, Color.SANDY_BROWN) - var files := image_files.map(func full_path(file_name): - return "%s/%s" % [source, file_name] )\ - .filter(func filter_import_files(path :String): - return path.get_extension() != "import") - prints(files) - fs.reimport_files(files) - - _console.prints_color("Scan sources: ", Color.SANDY_BROWN) - fs.scan_sources() - await get_tree().create_timer(5).timeout - await get_tree().process_frame + if _state == STARTUP: + if _fs.is_scanning(): + _console.progressBar(_fs.get_scanning_progress() * 100 as int) + # we wait 10s in addition to be on the save site the scanning is done + if _elapsed_time > 10.0: + _console.progressBar(100) + _console.new_line() + _console.prints_color("initial scanning ... done", Color.DARK_GREEN) + _state = SCAN + + func scan_project() -> void: + if _state != SCAN: + return + _console.prints_color("Scan project: ", Color.SANDY_BROWN) + await get_tree().process_frame + _fs.scan_sources() + await get_tree().create_timer(5).timeout _console.prints_color("Scan: ", Color.SANDY_BROWN) - fs.scan() + _console.progressBar(0) await get_tree().process_frame - while fs.is_scanning(): + _fs.scan() + while _fs.is_scanning(): await get_tree().process_frame - _console.progressBar(fs.get_scanning_progress() * 100 as int) + _console.progressBar(_fs.get_scanning_progress() * 100 as int) + await get_tree().create_timer(10).timeout _console.progressBar(100) _console.new_line() + _plugin.free() + _console.prints_color(""" + Scan project done. + ========================================================================""".dedent(), + Color.CORNFLOWER_BLUE + ) await get_tree().process_frame - plugin.queue_free() - await get_tree().process_frame + await get_tree().physics_frame + queue_free() + # force quit editor + _state = DONE + _scene.quit(0) diff --git a/addons/gdUnit4/plugin.cfg b/addons/gdUnit4/plugin.cfg index 56c4c0f6..90c99c20 100644 --- a/addons/gdUnit4/plugin.cfg +++ b/addons/gdUnit4/plugin.cfg @@ -3,5 +3,5 @@ name="gdUnit4" description="Unit Testing Framework for Godot Scripts" author="Mike Schulze" -version="4.2.0" +version="4.2.1" script="plugin.gd" diff --git a/addons/gdUnit4/plugin.gd b/addons/gdUnit4/plugin.gd index 065e0318..963673ef 100644 --- a/addons/gdUnit4/plugin.gd +++ b/addons/gdUnit4/plugin.gd @@ -4,11 +4,14 @@ extends EditorPlugin const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") var _gd_inspector :Node -var _server_node +var _server_node :Node var _gd_console :Node -func _enter_tree(): +func _enter_tree() -> void: + if Engine.get_version_info().hex < 0x40100: + prints("GdUnit4 plugin requires a minimum of Godot 4.1.x Version!") + return Engine.set_meta("GdUnitEditorPlugin", self) GdUnitSettings.setup() # install the GdUnit inspector @@ -21,13 +24,13 @@ func _enter_tree(): add_child(_server_node) prints("Loading GdUnit4 Plugin success") if GdUnitSettings.is_update_notification_enabled(): - var update_tool = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate() + var update_tool :Node = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate() Engine.get_main_loop().root.call_deferred("add_child", update_tool) - if GdUnit4MonoApiLoader.is_mono_supported(): - prints("GdUnit4Mono Version %s loaded." % GdUnit4MonoApiLoader.version()) + if GdUnit4CSharpApiLoader.is_mono_supported(): + prints("GdUnit4Mono Version %s loaded." % GdUnit4CSharpApiLoader.version()) -func _exit_tree(): +func _exit_tree() -> void: if is_instance_valid(_gd_inspector): remove_control_from_docks(_gd_inspector) _gd_inspector.free() diff --git a/addons/gdUnit4/runtest.sh b/addons/gdUnit4/runtest.sh index c1e55bd1..20980a97 100644 --- a/addons/gdUnit4/runtest.sh +++ b/addons/gdUnit4/runtest.sh @@ -8,5 +8,8 @@ fi $GODOT_BIN --path . -s -d ./addons/gdUnit4/bin/GdUnitCmdTool.gd $* exit_code=$? +echo "Run tests ends with $exit_code" + $GODOT_BIN --headless --path . --quiet -s -d ./addons/gdUnit4/bin/GdUnitCopyLog.gd $* > /dev/null +exit_code2=$? exit $exit_code diff --git a/addons/gdUnit4/src/GdUnitAssert.gd b/addons/gdUnit4/src/GdUnitAssert.gd index 04ba5263..1674d26c 100644 --- a/addons/gdUnit4/src/GdUnitAssert.gd +++ b/addons/gdUnit4/src/GdUnitAssert.gd @@ -3,32 +3,6 @@ class_name GdUnitAssert extends RefCounted -# Scans the current stack trace for the root cause to extract the line number -static func _get_line_number() -> int: - var stack_trace := get_stack() - if stack_trace == null or stack_trace.is_empty(): - return -1 - for stack_info in stack_trace: - var function :String = stack_info.get("function") - # we catch helper asserts to skip over to return the correct line number - if function.begins_with("assert_"): - continue - if function.begins_with("test_"): - return stack_info.get("line") - var source :String = stack_info.get("source") - if source.is_empty() \ - or source.begins_with("user://") \ - or source.ends_with("GdUnitAssert.gd") \ - or source.ends_with("AssertImpl.gd") \ - or source.ends_with("GdUnitTestSuite.gd") \ - or source.ends_with("GdUnitSceneRunnerImpl.gd") \ - or source.ends_with("GdUnitObjectInteractions.gd") \ - or source.ends_with("GdUnitAwaiter.gd"): - continue - return stack_info.get("line") - return -1 - - ## Verifies that the current value is null. func is_null(): return self diff --git a/addons/gdUnit4/src/GdUnitAwaiter.gd b/addons/gdUnit4/src/GdUnitAwaiter.gd index c1fc2ccf..cc6bf3fb 100644 --- a/addons/gdUnit4/src/GdUnitAwaiter.gd +++ b/addons/gdUnit4/src/GdUnitAwaiter.gd @@ -11,7 +11,7 @@ const GdUnitAssertImpl = preload("res://addons/gdUnit4/src/asserts/GdUnitAssertI # timeout: the timeout in ms, default is set to 2000ms func await_signal_on(source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> Variant: # fail fast if the given source instance invalid - var line_number := GdUnitAssert._get_line_number() + var line_number := GdUnitAssertions.get_line_number() if not is_instance_valid(source): GdUnitAssertImpl.new(signal_name)\ .report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number) @@ -35,7 +35,7 @@ func await_signal_on(source :Object, signal_name :String, args :Array = [], time # args: the expected signal arguments as an array # timeout: the timeout in ms, default is set to 2000ms func await_signal_idle_frames(source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> Variant: - var line_number := GdUnitAssert._get_line_number() + var line_number := GdUnitAssertions.get_line_number() # fail fast if the given source instance invalid if not is_instance_valid(source): GdUnitAssertImpl.new(signal_name)\ diff --git a/addons/gdUnit4/src/GdUnitSceneRunner.gd b/addons/gdUnit4/src/GdUnitSceneRunner.gd index 62c2dc90..9601c899 100644 --- a/addons/gdUnit4/src/GdUnitSceneRunner.gd +++ b/addons/gdUnit4/src/GdUnitSceneRunner.gd @@ -56,10 +56,21 @@ func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner: ## Simulates a mouse move to the relative coordinates (offset).[br] -## [member relative] : The relative position, e.g. the mouse position offset[br] -## [member speed] : The mouse speed in pixels per second.[br] +## [member relative] : The relative position, indicating the mouse position offset.[br] +## [member time] : The time to move the mouse by the relative position in seconds (default is 1 second).[br] +## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br] @warning_ignore("unused_parameter") -func simulate_mouse_move_relative(relative :Vector2, speed :Vector2 = Vector2.ONE) -> GdUnitSceneRunner: +func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: + await Engine.get_main_loop().process_frame + return self + + +## Simulates a mouse move to the absolute coordinates.[br] +## [member position] : The final position of the mouse.[br] +## [member time] : The time to move the mouse to the final position in seconds (default is 1 second).[br] +## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br] +@warning_ignore("unused_parameter") +func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: await Engine.get_main_loop().process_frame return self diff --git a/addons/gdUnit4/src/GdUnitTestSuite.gd b/addons/gdUnit4/src/GdUnitTestSuite.gd index 30734918..7b4a691d 100644 --- a/addons/gdUnit4/src/GdUnitTestSuite.gd +++ b/addons/gdUnit4/src/GdUnitTestSuite.gd @@ -24,7 +24,7 @@ var __skip_reason :String = "Unknow." var __active_test_case :String var __awaiter := __gdunit_awaiter() # holds the actual execution context -var __execution_context +var __execution_context :RefCounted ### We now load all used asserts and tool scripts into the cache according to the principle of "lazy loading" @@ -43,15 +43,19 @@ func __gdunit_tools() -> GDScript: return __lazy_load("res://addons/gdUnit4/src/core/GdUnitTools.gd") +func __gdunit_file_access() -> GDScript: + return __lazy_load("res://addons/gdUnit4/src/core/GdUnitFileAccess.gd") + + func __gdunit_awaiter() -> Object: return __lazy_load("res://addons/gdUnit4/src/GdUnitAwaiter.gd").new() -func __gdunit_argument_matchers(): +func __gdunit_argument_matchers() -> GDScript: return __lazy_load("res://addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd") -func __gdunit_object_interactions(): +func __gdunit_object_interactions() -> GDScript: return __lazy_load("res://addons/gdUnit4/src/core/GdUnitObjectInteractions.gd") @@ -91,11 +95,11 @@ func set_active_test_case(test_case :String) -> void: # Mapps Godot error number to a readable error message. See at ERROR # https://docs.godotengine.org/de/stable/classes/class_@globalscope.html#enum-globalscope-error func error_as_string(error_number :int) -> String: - return __gdunit_tools().error_as_string(error_number) + return error_string(error_number) ## A litle helper to auto freeing your created objects after test execution -func auto_free(obj) -> Variant: +func auto_free(obj :Variant) -> Variant: return __execution_context.register_auto_free(obj) @@ -117,35 +121,35 @@ func discard_error_interupted_by_timeout() -> void: ## Useful for storing data during test execution. [br] ## The directory is automatically deleted after test suite execution func create_temp_dir(relative_path :String) -> String: - return __gdunit_tools().create_temp_dir(relative_path) + return __gdunit_file_access().create_temp_dir(relative_path) ## Deletes the temporary base directory[br] ## Is called automatically after each execution of the test suite -func clean_temp_dir(): - __gdunit_tools().clear_tmp() +func clean_temp_dir() -> void: + __gdunit_file_access().clear_tmp() ## Creates a new file under the temporary directory *user://tmp* + [br] ## with given name and given file (default = File.WRITE)[br] ## If success the returned File is automatically closed after the execution of the test suite func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess: - return __gdunit_tools().create_temp_file(relative_path, file_name, mode) + return __gdunit_file_access().create_temp_file(relative_path, file_name, mode) ## Reads a resource by given path into a PackedStringArray. func resource_as_array(resource_path :String) -> PackedStringArray: - return __gdunit_tools().resource_as_array(resource_path) + return __gdunit_file_access().resource_as_array(resource_path) ## Reads a resource by given path and returned the content as String. func resource_as_string(resource_path :String) -> String: - return __gdunit_tools().resource_as_string(resource_path) + return __gdunit_file_access().resource_as_string(resource_path) ## Reads a resource by given path and return Variand translated by str_to_var -func resource_as_var(resource_path :String): - return str_to_var(__gdunit_tools().resource_as_string(resource_path)) +func resource_as_var(resource_path :String) -> Variant: + return str_to_var(__gdunit_file_access().resource_as_string(resource_path)) ## clears the debuger error list[br] @@ -164,7 +168,7 @@ func await_signal_on(source :Object, signal_name :String, args :Array = [], time ## Waits until the next idle frame -func await_idle_frame(): +func await_idle_frame() -> void: await __awaiter.await_idle_frame() @@ -175,7 +179,7 @@ func await_idle_frame(): ## await await_millis(myNode, 100).completed ## [/codeblock][br] ## use this waiter and not `await get_tree().create_timer().timeout to prevent errors when a test case is timed out -func await_millis(timeout :int): +func await_millis(timeout :int) -> void: await __awaiter.await_millis(timeout) @@ -190,7 +194,7 @@ func await_millis(timeout :int): ## # or simply creates a runner by using the scene resource path ## var runner := scene_runner("res://foo/my_scne.tscn") ## [/codeblock] -func scene_runner(scene, verbose := false) -> GdUnitSceneRunner: +func scene_runner(scene :Variant, verbose := false) -> GdUnitSceneRunner: return auto_free(__lazy_load("res://addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd").new(scene, verbose)) @@ -206,12 +210,12 @@ const RETURN_DEEP_STUB = GdUnitMock.RETURN_DEEP_STUB ## Creates a mock for given class name -func mock(clazz, mock_mode := RETURN_DEFAULTS) -> Object: +func mock(clazz :Variant, mock_mode := RETURN_DEFAULTS) -> Object: return __lazy_load("res://addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd").build(clazz, mock_mode) ## Creates a spy checked given object instance -func spy(instance) -> Object: +func spy(instance :Variant) -> Object: return __lazy_load("res://addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd").build(instance) @@ -221,27 +225,27 @@ func spy(instance) -> Object: ## # overrides the return value of myMock.is_selected() to false ## do_return(false).on(myMock).is_selected() ## [/codeblock] -func do_return(value) -> GdUnitMock: +func do_return(value :Variant) -> GdUnitMock: return GdUnitMock.new(value) ## Verifies certain behavior happened at least once or exact number of times -func verify(obj, times := 1): +func verify(obj :Variant, times := 1) -> Variant: return __gdunit_object_interactions().verify(obj, times) ## Verifies no interactions is happen checked this mock or spy -func verify_no_interactions(obj) -> GdUnitAssert: +func verify_no_interactions(obj :Variant) -> GdUnitAssert: return __gdunit_object_interactions().verify_no_interactions(obj) ## Verifies the given mock or spy has any unverified interaction. -func verify_no_more_interactions(obj) -> GdUnitAssert: +func verify_no_more_interactions(obj :Variant) -> GdUnitAssert: return __gdunit_object_interactions().verify_no_more_interactions(obj) ## Resets the saved function call counters checked a mock or spy -func reset(obj) -> void: +func reset(obj :Variant) -> void: __gdunit_object_interactions().reset(obj) @@ -363,16 +367,16 @@ func any_basis() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_BASIS) -## Argument matcher to match any Transform3D value -func any_transform() -> GdUnitArgumentMatcher: - return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM3D) - - ## Argument matcher to match any Transform2D value func any_transform_2d() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM2D) +## Argument matcher to match any Transform3D value +func any_transform_3d() -> GdUnitArgumentMatcher: + return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM3D) + + ## Argument matcher to match any NodePath value func any_node_path() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_NODE_PATH) @@ -399,37 +403,47 @@ func any_array() -> GdUnitArgumentMatcher: ## Argument matcher to match any PackedByteArray value -func any_pool_byte_array() -> GdUnitArgumentMatcher: +func any_packed_byte_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_BYTE_ARRAY) ## Argument matcher to match any PackedInt32Array value -func any_pool_int_array() -> GdUnitArgumentMatcher: +func any_packed_int32_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT32_ARRAY) +## Argument matcher to match any PackedInt64Array value +func any_packed_int64_array() -> GdUnitArgumentMatcher: + return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT64_ARRAY) + + ## Argument matcher to match any PackedFloat32Array value -func any_pool_float_array() -> GdUnitArgumentMatcher: +func any_packed_float32_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT32_ARRAY) +## Argument matcher to match any PackedFloat64Array value +func any_packed_float64_array() -> GdUnitArgumentMatcher: + return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT64_ARRAY) + + ## Argument matcher to match any PackedStringArray value -func any_pool_string_array() -> GdUnitArgumentMatcher: +func any_packed_string_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_STRING_ARRAY) ## Argument matcher to match any PackedVector2Array value -func any_pool_vector2_array() -> GdUnitArgumentMatcher: +func any_packed_vector2_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR2_ARRAY) ## Argument matcher to match any PackedVector3Array value -func any_pool_vector3_array() -> GdUnitArgumentMatcher: +func any_packed_vector3_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR3_ARRAY) ## Argument matcher to match any PackedColorArray value -func any_pool_color_array() -> GdUnitArgumentMatcher: +func any_packed_color_array() -> GdUnitArgumentMatcher: return __gdunit_argument_matchers().by_type(TYPE_PACKED_COLOR_ARRAY) @@ -462,7 +476,7 @@ func tuple(arg0 :Variant, ## The common assertion tool to verify values. ## It checks the given value by type to fit to the best assert -func assert_that(current) -> GdUnitAssert: +func assert_that(current :Variant) -> GdUnitAssert: match typeof(current): TYPE_BOOL: return assert_bool(current) @@ -487,22 +501,22 @@ func assert_that(current) -> GdUnitAssert: ## An assertion tool to verify boolean values. -func assert_bool(current) -> GdUnitBoolAssert: +func assert_bool(current :Variant) -> GdUnitBoolAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd").new(current) ## An assertion tool to verify String values. -func assert_str(current) -> GdUnitStringAssert: +func assert_str(current :Variant) -> GdUnitStringAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd").new(current) ## An assertion tool to verify integer values. -func assert_int(current) -> GdUnitIntAssert: +func assert_int(current :Variant) -> GdUnitIntAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd").new(current) ## An assertion tool to verify float values. -func assert_float(current) -> GdUnitFloatAssert: +func assert_float(current :Variant) -> GdUnitFloatAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd").new(current) @@ -512,31 +526,31 @@ func assert_float(current) -> GdUnitFloatAssert: ## [codeblock] ## assert_vector(Vector2(1.2, 1.000001)).is_equal(Vector2(1.2, 1.000001)) ## [/codeblock] -func assert_vector(current) -> GdUnitVectorAssert: +func assert_vector(current :Variant) -> GdUnitVectorAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd").new(current) ## An assertion tool to verify arrays. -func assert_array(current) -> GdUnitArrayAssert: +func assert_array(current :Variant) -> GdUnitArrayAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd").new(current) ## An assertion tool to verify dictionaries. -func assert_dict(current) -> GdUnitDictionaryAssert: +func assert_dict(current :Variant) -> GdUnitDictionaryAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd").new(current) ## An assertion tool to verify FileAccess. -func assert_file(current) -> GdUnitFileAssert: +func assert_file(current :Variant) -> GdUnitFileAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd").new(current) ## An assertion tool to verify Objects. -func assert_object(current) -> GdUnitObjectAssert: +func assert_object(current :Variant) -> GdUnitObjectAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd").new(current) -func assert_result(current) -> GdUnitResultAssert: +func assert_result(current :Variant) -> GdUnitResultAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd").new(current) @@ -588,11 +602,11 @@ func assert_error(current :Callable) -> GdUnitGodotErrorAssert: return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd").new(current) -func assert_not_yet_implemented(): +func assert_not_yet_implemented() -> void: __gdunit_assert().new(null).test_fail() -func fail(message :String): +func fail(message :String) -> void: __gdunit_assert().new(null).report_error(message) diff --git a/addons/gdUnit4/src/GdUnitTuple.gd b/addons/gdUnit4/src/GdUnitTuple.gd index a6e3c41b..86c16d4f 100644 --- a/addons/gdUnit4/src/GdUnitTuple.gd +++ b/addons/gdUnit4/src/GdUnitTuple.gd @@ -16,7 +16,7 @@ func _init(arg0:Variant, arg6 :Variant=NO_ARG, arg7 :Variant=NO_ARG, arg8 :Variant=NO_ARG, - arg9 :Variant=NO_ARG): + arg9 :Variant=NO_ARG) -> void: __values = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG) @@ -24,5 +24,5 @@ func values() -> Array: return __values -func _to_string(): +func _to_string() -> String: return "tuple(%s)" % str(__values) diff --git a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd index 0b4a72cf..27242e80 100644 --- a/addons/gdUnit4/src/asserts/CallBackValueProvider.gd +++ b/addons/gdUnit4/src/asserts/CallBackValueProvider.gd @@ -1,5 +1,5 @@ # a value provider unsing a callback to get `next` value from a certain function -class_name CallBackValueProvider +class_name CallBackValueProvider extends ValueProvider var _cb :Callable diff --git a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd index aa35d8f0..036a482d 100644 --- a/addons/gdUnit4/src/asserts/DefaultValueProvider.gd +++ b/addons/gdUnit4/src/asserts/DefaultValueProvider.gd @@ -1,11 +1,12 @@ # default value provider, simple returns the initial value -class_name DefaultValueProvider +class_name DefaultValueProvider extends ValueProvider var _value func _init(value): _value = value - + + func get_value(): return _value diff --git a/addons/gdUnit4/src/asserts/GdAssertMessages.gd b/addons/gdUnit4/src/asserts/GdAssertMessages.gd index 42a8ceb5..3411f42f 100644 --- a/addons/gdUnit4/src/asserts/GdAssertMessages.gd +++ b/addons/gdUnit4/src/asserts/GdAssertMessages.gd @@ -8,7 +8,7 @@ const SUB_COLOR := Color(1, 0, 0, .3) const ADD_COLOR := Color(0, 1, 0, .3) -static func _format_dict(value :Dictionary) -> String: +static func format_dict(value :Dictionary) -> String: if value.is_empty(): return "{ }" var as_rows := var_to_str(value).split("\n") @@ -22,28 +22,30 @@ static func _format_dict(value :Dictionary) -> String: static func input_event_as_text(event :InputEvent) -> String: var text := "" if event is InputEventKey: - text += "InputEventKey : key='%s', pressed=%s, keycode=%d, physical_keycode=%s" % [event.as_text(), event.pressed, event.keycode, event.physical_keycode] + text += "InputEventKey : key='%s', pressed=%s, keycode=%d, physical_keycode=%s" % [ + event.as_text(), event.pressed, event.keycode, event.physical_keycode] else: text += event.as_text() if event is InputEventMouse: text += ", global_position %s" % event.global_position if event is InputEventWithModifiers: - text += ", shift=%s, alt=%s, control=%s, meta=%s, command=%s" % [event.shift_pressed, event.alt_pressed, event.ctrl_pressed, event.meta_pressed, event.command_or_control_autoremap] + text += ", shift=%s, alt=%s, control=%s, meta=%s, command=%s" % [ + event.shift_pressed, event.alt_pressed, event.ctrl_pressed, event.meta_pressed, event.command_or_control_autoremap] return text static func _colored_string_div(characters :String) -> String: - return _colored_array_div(characters.to_ascii_buffer()) + return colored_array_div(characters.to_ascii_buffer()) -static func _colored_array_div(characters :PackedByteArray) -> String: +static func colored_array_div(characters :PackedByteArray) -> String: if characters.is_empty(): return "" var result = PackedByteArray() var index = 0 var missing_chars := PackedByteArray() var additional_chars := PackedByteArray() - + while index < characters.size(): var character := characters[index] match character: @@ -62,7 +64,7 @@ static func _colored_array_div(characters :PackedByteArray) -> String: additional_chars = PackedByteArray() result.append(character) index += 1 - + result.append_array(format_chars(missing_chars, SUB_COLOR)) result.append_array(format_chars(additional_chars, ADD_COLOR)) return result.get_string_from_utf8() @@ -106,10 +108,10 @@ static func _colored_value(value, _delimiter ="\n") -> String: if value is InputEvent: return "[color=%s]<%s>[/color]" % [VALUE_COLOR, input_event_as_text(value)] if value.has_method("_to_string"): - return "[color=%s]<%s>[/color]" % [VALUE_COLOR, value._to_string()] + return "[color=%s]<%s>[/color]" % [VALUE_COLOR, str(value)] return "[color=%s]<%s>[/color]" % [VALUE_COLOR, value.get_class()] TYPE_DICTIONARY: - return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _format_dict(value)] + return "'[color=%s]%s[/color]'" % [VALUE_COLOR, format_dict(value)] _: if GdArrayTools.is_array_type(value): return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _typed_value(value)] @@ -146,9 +148,9 @@ static func orphan_detected_on_test(count :int): static func fuzzer_interuped(iterations: int, error: String) -> String: return "%s %s %s\n %s" % [ - _error("Found an error after"), - _colored_value(iterations + 1), - _error("test iterations"), + _error("Found an error after"), + _colored_value(iterations + 1), + _error("test iterations"), error] @@ -156,6 +158,7 @@ static func test_timeout(timeout :int) -> String: return "%s\n %s" % [_error("Timeout !"), _colored_value("Test timed out after %s" % LocalTime.elapsed(timeout))] +# gdlint:disable = mixed-tabs-and-spaces static func test_suite_skipped(hint :String, skip_count) -> String: return """ %s @@ -201,7 +204,8 @@ static func error_not_equal(current, expected) -> String: static func error_not_equal_case_insensetiv(current, expected) -> String: - return "%s\n %s\n not equal to (case insensitiv)\n %s" % [_error("Expecting:"), _colored_value(expected, true), _colored_value(current, true)] + return "%s\n %s\n not equal to (case insensitiv)\n %s" % [ + _error("Expecting:"), _colored_value(expected, true), _colored_value(current, true)] static func error_is_empty(current) -> String: @@ -217,7 +221,7 @@ static func error_is_same(current, expected) -> String: @warning_ignore("unused_parameter") -static func error_not_same(current, expected) -> String: +static func error_not_same(_current, expected) -> String: return "%s\n %s" % [_error("Expecting not same:"), _colored_value(expected)] @@ -285,9 +289,11 @@ static func error_is_value(operation, current, expected, expected2=null) -> Stri Comparator.GREATER_EQUAL: return "%s\n %s but was '%s'" % [_error("Expecting to be greater than or equal:"), _colored_value(expected), _nerror(current)] Comparator.BETWEEN_EQUAL: - return "%s\n %s\n in range between\n %s <> %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] + return "%s\n %s\n in range between\n %s <> %s" % [ + _error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] Comparator.NOT_BETWEEN_EQUAL: - return "%s\n %s\n not in range between\n %s <> %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] + return "%s\n %s\n not in range between\n %s <> %s" % [ + _error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)] return "TODO create expected message" @@ -332,15 +338,23 @@ static func error_has_length(current, expected: int, compare_operator) -> String var current_length = current.length() if current != null else null match compare_operator: Comparator.EQUAL: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] Comparator.LESS_THAN: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be less than:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be less than:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] Comparator.LESS_EQUAL: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be less than or equal:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be less than or equal:"), _colored_value(expected), + _nerror(current_length), _colored_value(current)] Comparator.GREATER_THAN: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be greater than:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be greater than:"), _colored_value(expected), + _nerror(current_length), _colored_value(current)] Comparator.GREATER_EQUAL: - return "%s\n %s but was '%s' in\n %s" % [_error("Expecting size to be greater than or equal:"), _colored_value(expected), _nerror(current_length), _colored_value(current)] + return "%s\n %s but was '%s' in\n %s" % [ + _error("Expecting size to be greater than or equal:"), _colored_value(expected), + _nerror(current_length), _colored_value(current)] return "TODO create expected message" @@ -348,7 +362,8 @@ static func error_has_length(current, expected: int, compare_operator) -> String static func error_arr_contains(current, expected :Array, not_expect :Array, not_found :Array, by_reference :bool) -> String: var failure_message = "Expecting contains SAME elements:" if by_reference else "Expecting contains elements:" - var error := "%s\n %s\n do contains (in any order)\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] + var error := "%s\n %s\n do contains (in any order)\n %s" % [ + _error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] if not not_expect.is_empty(): error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect, ", ") if not not_found.is_empty(): @@ -358,12 +373,17 @@ static func error_arr_contains(current, expected :Array, not_expect :Array, not_ static func error_arr_contains_exactly(current, expected, not_expect, not_found, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure_message = "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME exactly elements:" + var failure_message = ( + "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME exactly elements:" + ) if not_expect.is_empty() and not_found.is_empty(): var diff := _find_first_diff(current, expected) - return "%s\n %s\n do contains (in same order)\n %s\n but has different order %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", "), diff] - - var error := "%s\n %s\n do contains (in same order)\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] + return "%s\n %s\n do contains (in same order)\n %s\n but has different order %s" % [ + _error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", "), diff] + + var error := "%s\n %s\n do contains (in same order)\n %s" % [ + _error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] if not not_expect.is_empty(): error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect, ", ") if not not_found.is_empty(): @@ -372,9 +392,19 @@ static func error_arr_contains_exactly(current, expected, not_expect, not_found, return error -static func error_arr_contains_exactly_in_any_order(current, expected :Array, not_expect :Array, not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure_message = "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME exactly elements:" - var error := "%s\n %s\n do contains exactly (in any order)\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] +static func error_arr_contains_exactly_in_any_order( + current, + expected :Array, + not_expect :Array, + not_found :Array, + compare_mode :GdObjects.COMPARE_MODE) -> String: + + var failure_message = ( + "Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME exactly elements:" + ) + var error := "%s\n %s\n do contains exactly (in any order)\n %s" % [ + _error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] if not not_expect.is_empty(): error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect, ", ") if not not_found.is_empty(): @@ -385,7 +415,8 @@ static func error_arr_contains_exactly_in_any_order(current, expected :Array, no static func error_arr_not_contains(current :Array, expected :Array, found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: var failure_message = "Expecting:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting SAME:" - var error := "%s\n %s\n do not contains\n %s" % [_error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] + var error := "%s\n %s\n do not contains\n %s" % [ + _error(failure_message), _colored_value(current, ", "), _colored_value(expected, ", ")] if not found.is_empty(): error += "\n but found elements:\n %s" % _colored_value(found, ", ") return error @@ -393,18 +424,30 @@ static func error_arr_not_contains(current :Array, expected :Array, found :Array # - DictionaryAssert specific messages ---------------------------------------------- static func error_contains_keys(current :Array, expected :Array, keys_not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure := "Expecting contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME keys:" - return "%s\n %s\n to contains:\n %s\n but can't find key's:\n %s" % [_error(failure), _colored_value(current, ", "), _colored_value(expected, ", "), _colored_value(keys_not_found, ", ")] + var failure := ( + "Expecting contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME keys:" + ) + return "%s\n %s\n to contains:\n %s\n but can't find key's:\n %s" % [ + _error(failure), _colored_value(current, ", "), _colored_value(expected, ", "), _colored_value(keys_not_found, ", ")] static func error_not_contains_keys(current :Array, expected :Array, keys_not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure := "Expecting NOT contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting NOT contains SAME keys" - return "%s\n %s\n do not contains:\n %s\n but contains key's:\n %s" % [_error(failure), _colored_value(current, ", "), _colored_value(expected, ", "), _colored_value(keys_not_found, ", ")] + var failure := ( + "Expecting NOT contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting NOT contains SAME keys" + ) + return "%s\n %s\n do not contains:\n %s\n but contains key's:\n %s" % [ + _error(failure), _colored_value(current, ", "), _colored_value(expected, ", "), _colored_value(keys_not_found, ", ")] static func error_contains_key_value(key, value, current_value, compare_mode :GdObjects.COMPARE_MODE) -> String: - var failure := "Expecting contains key and value:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting contains SAME key and value:" - return "%s\n %s : %s\n but contains\n %s : %s" % [_error(failure), _colored_value(key), _colored_value(value), _colored_value(key), _colored_value(current_value)] + var failure := ( + "Expecting contains key and value:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST + else "Expecting contains SAME key and value:" + ) + return "%s\n %s : %s\n but contains\n %s : %s" % [ + _error(failure), _colored_value(key), _colored_value(value), _colored_value(key), _colored_value(current_value)] # - ResultAssert specific errors ---------------------------------------------------- @@ -454,17 +497,22 @@ static func error_interrupted(func_name :String, expected, elapsed :String) -> S static func error_wait_signal(signal_name :String, args :Array, elapsed :String) -> String: if args.is_empty(): - return "%s %s but timed out after %s" % [_error("Expecting emit signal:"), _colored_value(signal_name + "()"), elapsed] - return "%s %s but timed out after %s" % [_error("Expecting emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] + return "%s %s but timed out after %s" % [ + _error("Expecting emit signal:"), _colored_value(signal_name + "()"), elapsed] + return "%s %s but timed out after %s" % [ + _error("Expecting emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] static func error_signal_emitted(signal_name :String, args :Array, elapsed :String) -> String: if args.is_empty(): - return "%s %s but is emitted after %s" % [_error("Expecting do not emit signal:"), _colored_value(signal_name + "()"), elapsed] - return "%s %s but is emitted after %s" % [_error("Expecting do not emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] + return "%s %s but is emitted after %s" % [ + _error("Expecting do not emit signal:"), _colored_value(signal_name + "()"), elapsed] + return "%s %s but is emitted after %s" % [ + _error("Expecting do not emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed] static func error_await_signal_on_invalid_instance(source, signal_name :String, args :Array) -> String: - return "%s\n await_signal_on(%s, %s, %s)" % [_error("Invalid source! Can't await on signal:"), _colored_value(source), signal_name, args] + return "%s\n await_signal_on(%s, %s, %s)" % [ + _error("Invalid source! Can't await on signal:"), _colored_value(source), signal_name, args] static func result_type(type :int) -> String: match type: @@ -499,7 +547,8 @@ static func error_validate_interactions(current_interactions :Dictionary, expect var times :int = current_interactions[args] interactions.append(_format_arguments(args, times)) var expected_interaction := _format_arguments(expected_interactions.keys()[0], expected_interactions.values()[0]) - return "%s\n%s\n%s\n%s" % [_error("Expecting interaction on:"), expected_interaction, _error("But found interactions on:"), "\n".join(interactions)] + return "%s\n%s\n%s\n%s" % [ + _error("Expecting interaction on:"), expected_interaction, _error("But found interactions on:"), "\n".join(interactions)] static func _format_arguments(args :Array, times :int) -> String: @@ -545,7 +594,8 @@ static func format_chars(characters :PackedByteArray, type :Color) -> PackedByte if characters.size() == 0:# or characters[0] == 10: return characters var result := PackedByteArray() - var message := "[bgcolor=#%s][color=with]%s[/color][/bgcolor]" % [type.to_html(), characters.get_string_from_utf8().replace("\n", "")] + var message := "[bgcolor=#%s][color=with]%s[/color][/bgcolor]" % [ + type.to_html(), characters.get_string_from_utf8().replace("\n", "")] result.append_array(message.to_utf8_buffer()) return result diff --git a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd index 419f0172..e488ac88 100644 --- a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd @@ -7,10 +7,11 @@ var _current_value_provider :ValueProvider func _init(current): _current_value_provider = DefaultValueProvider.new(current) - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not __validate_value_type(current): + if not _validate_value_type(current): report_error("GdUnitArrayAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -30,7 +31,7 @@ func report_error(error :String) -> GdUnitArrayAssert: _base.report_error(error) return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -39,14 +40,11 @@ func override_failure_message(message :String) -> GdUnitArrayAssert: return self -func __validate_value_type(value) -> bool: - return ( - value == null - or GdArrayTools.is_array_type(value) - ) +func _validate_value_type(value) -> bool: + return value == null or GdArrayTools.is_array_type(value) -func __current() -> Variant: +func current_value() -> Variant: return _current_value_provider.get_value() @@ -57,27 +55,27 @@ func max_length(left, right) -> int: func _array_equals_div(current, expected, case_sensitive :bool = false) -> Array: - var current_ := PackedStringArray(Array(current)) - var expected_ := PackedStringArray(Array(expected)) - var index_report_ := Array() - for index in current_.size(): - var c := current_[index] - if index < expected_.size(): - var e := expected_[index] + var current_value := PackedStringArray(Array(current)) + var expected_value := PackedStringArray(Array(expected)) + var index_report := Array() + for index in current_value.size(): + var c := current_value[index] + if index < expected_value.size(): + var e := expected_value[index] if not GdObjects.equals(c, e, case_sensitive): var length := max_length(c, e) - current_[index] = GdAssertMessages.format_invalid(c.lpad(length)) - expected_[index] = e.lpad(length) - index_report_.push_back({"index" : index, "current" :c, "expected": e}) + current_value[index] = GdAssertMessages.format_invalid(c.lpad(length)) + expected_value[index] = e.lpad(length) + index_report.push_back({"index" : index, "current" :c, "expected": e}) else: - current_[index] = GdAssertMessages.format_invalid(c) - index_report_.push_back({"index" : index, "current" :c, "expected": ""}) - - for index in range(current.size(), expected_.size()): - var value := expected_[index] - expected_[index] = GdAssertMessages.format_invalid(value) - index_report_.push_back({"index" : index, "current" : "", "expected": value}) - return [current_, expected_, index_report_] + current_value[index] = GdAssertMessages.format_invalid(c) + index_report.push_back({"index" : index, "current" :c, "expected": ""}) + + for index in range(current.size(), expected_value.size()): + var value := expected_value[index] + expected_value[index] = GdAssertMessages.format_invalid(value) + index_report.push_back({"index" : index, "current" : "", "expected": value}) + return [current_value, expected_value, index_report] func _array_div(compare_mode :GdObjects.COMPARE_MODE, left :Array[Variant], right :Array[Variant], _same_order := false) -> Array[Variant]: @@ -96,66 +94,66 @@ func _array_div(compare_mode :GdObjects.COMPARE_MODE, left :Array[Variant], righ func _contains(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) var by_reference := compare_mode == GdObjects.COMPARE_MODE.OBJECT_REFERENCE - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains(current_, expected, [], expected, by_reference)) - var diffs := _array_div(compare_mode, current_, expected) + var current_value = current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains(current_value, expected, [], expected, by_reference)) + var diffs := _array_div(compare_mode, current_value, expected) #var not_expect := diffs[0] as Array var not_found := diffs[1] as Array if not not_found.is_empty(): - return report_error(GdAssertMessages.error_arr_contains(current_, expected, [], not_found, by_reference)) + return report_error(GdAssertMessages.error_arr_contains(current_value, expected, [], not_found, by_reference)) return report_success() func _contains_exactly(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains_exactly(current_, expected, [], expected, compare_mode)) + var current_value = current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, [], expected, compare_mode)) # has same content in same order - if GdObjects.equals(Array(current_), Array(expected), false, compare_mode): + if GdObjects.equals(Array(current_value), Array(expected), false, compare_mode): return report_success() # check has same elements but in different order - if GdObjects.equals_sorted(Array(current_), Array(expected), false, compare_mode): - return report_error(GdAssertMessages.error_arr_contains_exactly(current_, expected, [], [], compare_mode)) + if GdObjects.equals_sorted(Array(current_value), Array(expected), false, compare_mode): + return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, [], [], compare_mode)) # find the difference - var diffs := _array_div(compare_mode, current_, expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) + var diffs := _array_div(compare_mode, current_value, expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) var not_expect := diffs[0] as Array[Variant] var not_found := diffs[1] as Array[Variant] - return report_error(GdAssertMessages.error_arr_contains_exactly(current_, expected, not_expect, not_found, compare_mode)) + return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, not_expect, not_found, compare_mode)) func _contains_exactly_in_any_order(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_, expected, [], expected, compare_mode)) + var current_value = current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, [], expected, compare_mode)) # find the difference - var diffs := _array_div(compare_mode, current_, expected, false) + var diffs := _array_div(compare_mode, current_value, expected, false) var not_expect := diffs[0] as Array var not_found := diffs[1] as Array if not_expect.is_empty() and not_found.is_empty(): return report_success() - return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_, expected, not_expect, not_found, compare_mode)) + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, not_expect, not_found, compare_mode)) func _not_contains(expected, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null: - return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_, expected, [], expected, compare_mode)) - var diffs := _array_div(compare_mode, current_, expected) + var current_value = current_value() + if current_value == null: + return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected, [], expected, compare_mode)) + var diffs := _array_div(compare_mode, current_value, expected) var found := diffs[0] as Array - if found.size() == current_.size(): + if found.size() == current_value.size(): return report_success() var diffs2 := _array_div(compare_mode, expected, diffs[1]) - return report_error(GdAssertMessages.error_arr_not_contains(current_, expected, diffs2[0], compare_mode)) + return report_error(GdAssertMessages.error_arr_not_contains(current_value, expected, diffs2[0], compare_mode)) func is_null() -> GdUnitArrayAssert: @@ -170,13 +168,13 @@ func is_not_null() -> GdUnitArrayAssert: # Verifies that the current String is equal to the given one. func is_equal(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null and expected != null: + var current_value = current_value() + if current_value == null and expected != null: return report_error(GdAssertMessages.error_equal(null, expected)) - if not GdObjects.equals(current_, expected): - var diff := _array_equals_div(current_, expected) + if not GdObjects.equals(current_value, expected): + var diff := _array_equals_div(current_value, expected) var expected_as_list = GdArrayTools.as_string(diff[0], false) var current_as_list = GdArrayTools.as_string(diff[1], false) var index_report = diff[2] @@ -186,13 +184,13 @@ func is_equal(expected) -> GdUnitArrayAssert: # Verifies that the current Array is equal to the given one, ignoring case considerations. func is_equal_ignoring_case(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if current_ == null and expected != null: + var current_value = current_value() + if current_value == null and expected != null: return report_error(GdAssertMessages.error_equal(null, GdArrayTools.as_string(expected))) - if not GdObjects.equals(current_, expected, true): - var diff := _array_equals_div(current_, expected, true) + if not GdObjects.equals(current_value, expected, true): + var diff := _array_equals_div(current_value, expected, true) var expected_as_list := GdArrayTools.as_string(diff[0]) var current_as_list := GdArrayTools.as_string(diff[1]) var index_report = diff[2] @@ -201,62 +199,62 @@ func is_equal_ignoring_case(expected) -> GdUnitArrayAssert: func is_not_equal(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if GdObjects.equals(current_, expected): - return report_error(GdAssertMessages.error_not_equal(current_, expected)) + var current_value = current_value() + if GdObjects.equals(current_value, expected): + return report_error(GdAssertMessages.error_not_equal(current_value, expected)) return report_success() func is_not_equal_ignoring_case(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current_ = __current() - if GdObjects.equals(current_, expected, true): - var c := GdArrayTools.as_string(current_) + var current_value = current_value() + if GdObjects.equals(current_value, expected, true): + var c := GdArrayTools.as_string(current_value) var e := GdArrayTools.as_string(expected) return report_error(GdAssertMessages.error_not_equal_case_insensetiv(c, e)) return report_success() func is_empty() -> GdUnitArrayAssert: - var current_ = __current() - if current_ == null or current_.size() > 0: - return report_error(GdAssertMessages.error_is_empty(current_)) + var current_value = current_value() + if current_value == null or current_value.size() > 0: + return report_error(GdAssertMessages.error_is_empty(current_value)) return report_success() func is_not_empty() -> GdUnitArrayAssert: - var current_ = __current() - if current_ != null and current_.size() == 0: + var current_value = current_value() + if current_value != null and current_value.size() == 0: return report_error(GdAssertMessages.error_is_not_empty()) return report_success() @warning_ignore("unused_parameter", "shadowed_global_identifier") func is_same(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current = __current() + var current = current_value() if not is_same(current, expected): report_error(GdAssertMessages.error_is_same(current, expected)) return self func is_not_same(expected) -> GdUnitArrayAssert: - if not __validate_value_type(expected): + if not _validate_value_type(expected): return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected)) - var current = __current() + var current = current_value() if is_same(current, expected): report_error(GdAssertMessages.error_not_same(current, expected)) return self func has_size(expected: int) -> GdUnitArrayAssert: - var current_ = __current() - if current_ == null or current_.size() != expected: - return report_error(GdAssertMessages.error_has_size(current_, expected)) + var current_value = current_value() + if current_value == null or current_value.size() != expected: + return report_error(GdAssertMessages.error_has_size(current_value, expected)) return report_success() @@ -299,8 +297,9 @@ func is_instanceof(expected) -> GdUnitAssert: func extract(func_name :String, args := Array()) -> GdUnitArrayAssert: var extracted_elements := Array() - var extractor :GdUnitValueExtractor = ResourceLoader.load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(func_name, args) - var current = __current() + var extractor :GdUnitValueExtractor = ResourceLoader.load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd", + "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(func_name, args) + var current = current_value() if current == null: _current_value_provider = DefaultValueProvider.new(null) else: @@ -311,8 +310,8 @@ func extract(func_name :String, args := Array()) -> GdUnitArrayAssert: func extractv( - extr0 :GdUnitValueExtractor, - extr1 :GdUnitValueExtractor = null, + extr0 :GdUnitValueExtractor, + extr1 :GdUnitValueExtractor = null, extr2 :GdUnitValueExtractor = null, extr3 :GdUnitValueExtractor = null, extr4 :GdUnitValueExtractor = null, @@ -323,16 +322,26 @@ func extractv( extr9 :GdUnitValueExtractor = null) -> GdUnitArrayAssert: var extractors :Variant = GdArrayTools.filter_value([extr0, extr1, extr2, extr3, extr4, extr5, extr6, extr7, extr8, extr9], null) var extracted_elements := Array() - var current = __current() + var current = current_value() if current == null: _current_value_provider = DefaultValueProvider.new(null) else: - for element in __current(): - var ev :Array[Variant] = [GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG, GdUnitTuple.NO_ARG] + for element in current_value(): + var ev :Array[Variant] = [ + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG, + GdUnitTuple.NO_ARG + ] for index in extractors.size(): var extractor :GdUnitValueExtractor = extractors[index] ev[index] = extractor.extract_value(element) - if extractors.size() > 1: extracted_elements.append(GdUnitTuple.new(ev[0], ev[1], ev[2], ev[3], ev[4], ev[5], ev[6], ev[7], ev[8], ev[9])) else: diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd index 6fc87b18..30694b0c 100644 --- a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd @@ -6,32 +6,28 @@ var _current_error_message :String = "" var _custom_failure_message :String = "" -func _init(current :Variant): +func _init(current :Variant) -> void: _current = current # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) GdAssertReports.reset_last_error_line_number() -func _failure_message() -> String: +func failure_message() -> String: return _current_error_message -func __current() -> Variant: +func current_value() -> Variant: return _current -func __validate_value_type(value, type :Variant.Type) -> bool: - return value == null or typeof(value) == type - - func report_success() -> GdUnitAssert: GdAssertReports.report_success() return self func report_error(error_message :String, failure_line_number: int = -1) -> GdUnitAssert: - var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssert._get_line_number() + var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number() GdAssertReports.set_last_error_line_number(line_number) _current_error_message = error_message if _custom_failure_message.is_empty() else _custom_failure_message GdAssertReports.report_error(_current_error_message, line_number) @@ -48,28 +44,28 @@ func override_failure_message(message :String): func is_equal(expected) -> GdUnitAssert: - var current = __current() + var current = current_value() if not GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_equal(current, expected)) return report_success() func is_not_equal(expected) -> GdUnitAssert: - var current = __current() + var current = current_value() if GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() func is_null() -> GdUnitAssert: - var current = __current() + var current = current_value() if current != null: return report_error(GdAssertMessages.error_is_null(current)) return report_success() func is_not_null() -> GdUnitAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd index 0f7219b3..a4527911 100644 --- a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd +++ b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd @@ -3,8 +3,9 @@ class_name GdUnitAssertions extends RefCounted -func _init(): +func _init() -> void: # preload all gdunit assertions to speedup testsuite loading time + # gdlint:disable=private-method-call GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd") GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd") GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd") @@ -26,5 +27,37 @@ func _init(): ### in order to noticeably reduce the loading time of the test suite. # We go this hard way to increase the loading performance to avoid reparsing all the used scripts # for more detailed info -> https://github.com/godotengine/godot/issues/67400 +# gdlint:disable=function-name static func __lazy_load(script_path :String) -> GDScript: return ResourceLoader.load(script_path, "GDScript", ResourceLoader.CACHE_MODE_REUSE) + + +static func validate_value_type(value, type :Variant.Type) -> bool: + return value == null or typeof(value) == type + +# Scans the current stack trace for the root cause to extract the line number +static func get_line_number() -> int: + var stack_trace := get_stack() + if stack_trace == null or stack_trace.is_empty(): + return -1 + for index in stack_trace.size(): + var stack_info :Dictionary = stack_trace[index] + var function :String = stack_info.get("function") + # we catch helper asserts to skip over to return the correct line number + if function.begins_with("assert_"): + continue + if function.begins_with("test_"): + return stack_info.get("line") + var source :String = stack_info.get("source") + if source.is_empty() \ + or source.begins_with("user://") \ + or source.ends_with("GdUnitAssert.gd") \ + or source.ends_with("GdUnitAssertions.gd") \ + or source.ends_with("AssertImpl.gd") \ + or source.ends_with("GdUnitTestSuite.gd") \ + or source.ends_with("GdUnitSceneRunnerImpl.gd") \ + or source.ends_with("GdUnitObjectInteractions.gd") \ + or source.ends_with("GdUnitAwaiter.gd"): + continue + return stack_info.get("line") + return -1 diff --git a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd index d838c86f..c2cdd34f 100644 --- a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd @@ -4,10 +4,11 @@ var _base: GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_BOOL): + if not GdUnitAssertions.validate_value_type(current, TYPE_BOOL): report_error("GdUnitBoolAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -18,8 +19,8 @@ func _notification(event): _base = null -func __current(): - return _base.__current() +func current_value(): + return _base.current_value() func report_success() -> GdUnitBoolAssert: @@ -32,7 +33,7 @@ func report_error(error :String) -> GdUnitBoolAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -64,12 +65,12 @@ func is_not_equal(expected) -> GdUnitBoolAssert: func is_true() -> GdUnitBoolAssert: - if __current() != true: - return report_error(GdAssertMessages.error_is_true(__current())) + if current_value() != true: + return report_error(GdAssertMessages.error_is_true(current_value())) return report_success() func is_false() -> GdUnitBoolAssert: - if __current() == true || __current() == null: - return report_error(GdAssertMessages.error_is_false(__current())) + if current_value() == true || current_value() == null: + return report_error(GdAssertMessages.error_is_false(current_value())) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd index 514894ba..1d9dab23 100644 --- a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd @@ -4,10 +4,11 @@ var _base :GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_DICTIONARY): + if not GdUnitAssertions.validate_value_type(current, TYPE_DICTIONARY): report_error("GdUnitDictionaryAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -28,7 +29,7 @@ func report_error(error :String) -> GdUnitDictionaryAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -37,8 +38,8 @@ func override_failure_message(message :String) -> GdUnitDictionaryAssert: return self -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func is_null() -> GdUnitDictionaryAssert: @@ -52,20 +53,20 @@ func is_not_null() -> GdUnitDictionaryAssert: func is_equal(expected) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null: - return report_error(GdAssertMessages.error_equal(null, GdAssertMessages._format_dict(expected))) + return report_error(GdAssertMessages.error_equal(null, GdAssertMessages.format_dict(expected))) if not GdObjects.equals(current, expected): - var c := GdAssertMessages._format_dict(current) - var e := GdAssertMessages._format_dict(expected) + var c := GdAssertMessages.format_dict(current) + var e := GdAssertMessages.format_dict(expected) var diff := GdDiffTool.string_diff(c, e) - var curent_ = GdAssertMessages._colored_array_div(diff[1]) - return report_error(GdAssertMessages.error_equal(curent_, e)) + var curent_diff := GdAssertMessages.colored_array_div(diff[1]) + return report_error(GdAssertMessages.error_equal(curent_diff, e)) return report_success() func is_not_equal(expected) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() @@ -73,44 +74,42 @@ func is_not_equal(expected) -> GdUnitDictionaryAssert: @warning_ignore("unused_parameter", "shadowed_global_identifier") func is_same(expected) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null: - return report_error(GdAssertMessages.error_equal(null, GdAssertMessages._format_dict(expected))) + return report_error(GdAssertMessages.error_equal(null, GdAssertMessages.format_dict(expected))) if not is_same(current, expected): - var c := GdAssertMessages._format_dict(current) - var e := GdAssertMessages._format_dict(expected) + var c := GdAssertMessages.format_dict(current) + var e := GdAssertMessages.format_dict(expected) var diff := GdDiffTool.string_diff(c, e) - var curent_ = GdAssertMessages._colored_array_div(diff[1]) - return report_error(GdAssertMessages.error_is_same(curent_, e)) + var curent_diff := GdAssertMessages.colored_array_div(diff[1]) + return report_error(GdAssertMessages.error_is_same(curent_diff, e)) return report_success() - @warning_ignore("unused_parameter", "shadowed_global_identifier") func is_not_same(expected) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if is_same(current, expected): return report_error(GdAssertMessages.error_not_same(current, expected)) return report_success() - func is_empty() -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null or not current.is_empty(): return report_error(GdAssertMessages.error_is_empty(current)) return report_success() func is_not_empty() -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null or current.is_empty(): return report_error(GdAssertMessages.error_is_not_empty()) return report_success() func has_size(expected: int) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) if current.size() != expected: @@ -119,32 +118,22 @@ func has_size(expected: int) -> GdUnitDictionaryAssert: func _contains_keys(expected :Array, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) # find expected keys - var keys_not_found :Array = expected.filter(func(expected_key): - for current_key in current.keys(): - if GdObjects.equals(current_key, expected_key, false, compare_mode): - return false - return true - ) + var keys_not_found :Array = expected.filter(_filter_by_key.bind(current.keys(), compare_mode)) if not keys_not_found.is_empty(): return report_error(GdAssertMessages.error_contains_keys(current.keys(), expected, keys_not_found, compare_mode)) return report_success() func _contains_key_value(key, value, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() var expected := [key] if current == null: return report_error(GdAssertMessages.error_is_not_null()) - var keys_not_found :Array = expected.filter(func(expected_key): - for current_key in current.keys(): - if GdObjects.equals(current_key, expected_key, false, compare_mode): - return false - return true - ) + var keys_not_found :Array = expected.filter(_filter_by_key.bind(current.keys(), compare_mode)) if not keys_not_found.is_empty(): return report_error(GdAssertMessages.error_contains_key_value(key, value, current.keys(), compare_mode)) if not GdObjects.equals(current[key], value, false, compare_mode): @@ -153,15 +142,10 @@ func _contains_key_value(key, value, compare_mode :GdObjects.COMPARE_MODE) -> Gd func _not_contains_keys(expected :Array, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_is_not_null()) - var keys_found :Array = current.keys().filter(func(current_key): - for expected_key in expected: - if GdObjects.equals(current_key, expected_key, false, compare_mode): - return true - return false - ) + var keys_found :Array = current.keys().filter(_filter_by_key.bind(expected, compare_mode, true)) if not keys_found.is_empty(): return report_error(GdAssertMessages.error_not_contains_keys(current.keys(), expected, keys_found, compare_mode)) return report_success() @@ -189,3 +173,10 @@ func contains_same_key_value(key, value) -> GdUnitDictionaryAssert: func not_contains_same_keys(expected :Array) -> GdUnitDictionaryAssert: return _not_contains_keys(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE) + + +func _filter_by_key(element :Variant, values :Array, compare_mode :GdObjects.COMPARE_MODE, is_not :bool = false) -> bool: + for key in values: + if GdObjects.equals(key, element, false, compare_mode): + return is_not + return !is_not diff --git a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd index 8be056e6..6b1cdaa6 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd @@ -24,7 +24,7 @@ func execute_and_await(assertion :Callable, do_await := true) -> GdUnitFailureAs _is_failed = true _failure_message = "Invalid Callable! It must be a callable of 'GdUnitAssert'" return - _failure_message = current_assert._failure_message() + _failure_message = current_assert.failure_message() return self @@ -38,12 +38,12 @@ func _on_test_failed(value :bool) -> void: @warning_ignore("unused_parameter") -func is_equal(expected :GdUnitAssert) -> GdUnitFailureAssert: +func is_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert: return _report_error("Not implemented") @warning_ignore("unused_parameter") -func is_not_equal(expected :GdUnitAssert) -> GdUnitFailureAssert: +func is_not_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert: return _report_error("Not implemented") @@ -79,7 +79,7 @@ func has_message(expected :String) -> GdUnitFailureAssert: var current_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(_failure_message)) if current_error != expected_error: var diffs := GdDiffTool.string_diff(current_error, expected_error) - var current := GdAssertMessages._colored_array_div(diffs[1]) + var current := GdAssertMessages.colored_array_div(diffs[1]) _report_error(GdAssertMessages.error_not_same_error(current, expected_error)) return self @@ -89,13 +89,13 @@ func starts_with_message(expected :String) -> GdUnitFailureAssert: var current_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(_failure_message)) if current_error.find(expected_error) != 0: var diffs := GdDiffTool.string_diff(current_error, expected_error) - var current := GdAssertMessages._colored_array_div(diffs[1]) + var current := GdAssertMessages.colored_array_div(diffs[1]) _report_error(GdAssertMessages.error_not_same_error(current, expected_error)) return self func _report_error(error_message :String, failure_line_number: int = -1) -> GdUnitAssert: - var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssert._get_line_number() + var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number() GdAssertReports.report_error(error_message, line_number) return self diff --git a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd index b4e07008..b23212a7 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd @@ -6,10 +6,11 @@ var _base: GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_STRING): + if not GdUnitAssertions.validate_value_type(current, TYPE_STRING): report_error("GdUnitFileAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -20,8 +21,8 @@ func _notification(event): _base = null -func __current() -> String: - return _base.__current() as String +func current_value() -> String: + return _base.current_value() as String func report_success() -> GdUnitFileAssert: @@ -34,7 +35,7 @@ func report_error(error :String) -> GdUnitFileAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -54,24 +55,24 @@ func is_not_equal(expected) -> GdUnitFileAssert: func is_file() -> GdUnitFileAssert: - var current := __current() + var current := current_value() if FileAccess.open(current, FileAccess.READ) == null: return report_error("Is not a file '%s', error code %s" % [current, FileAccess.get_open_error()]) return report_success() func exists() -> GdUnitFileAssert: - var current := __current() + var current := current_value() if not FileAccess.file_exists(current): return report_error("The file '%s' not exists" %current) return report_success() func is_script() -> GdUnitFileAssert: - var current := __current() + var current := current_value() if FileAccess.open(current, FileAccess.READ) == null: return report_error("Can't acces the file '%s'! Error code %s" % [current, FileAccess.get_open_error()]) - + var script = load(current) if not script is GDScript: return report_error("The file '%s' is not a GdScript" % current) @@ -79,16 +80,16 @@ func is_script() -> GdUnitFileAssert: func contains_exactly(expected_rows :Array) -> GdUnitFileAssert: - var current := __current() + var current := current_value() if FileAccess.open(current, FileAccess.READ) == null: return report_error("Can't acces the file '%s'! Error code %s" % [current, FileAccess.get_open_error()]) - + var script = load(current) if script is GDScript: var instance = script.new() var source_code = GdScriptParser.to_unix_format(instance.get_script().source_code) GdUnitTools.free_instance(instance) var rows := Array(source_code.split("\n")) - ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(rows)\ - .contains_exactly(expected_rows) + ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(rows).contains_exactly(expected_rows) return self diff --git a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd index d457b00a..c0cd4b4d 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd @@ -4,10 +4,11 @@ var _base: GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_FLOAT): + if not GdUnitAssertions.validate_value_type(current, TYPE_FLOAT): report_error("GdUnitFloatAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -18,8 +19,8 @@ func _notification(event): _base = null -func __current(): - return _base.__current() +func current_value(): + return _base.current_value() func report_success() -> GdUnitFloatAssert: @@ -32,7 +33,7 @@ func report_error(error :String) -> GdUnitFloatAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -67,77 +68,77 @@ func is_equal_approx(expected :float, approx :float) -> GdUnitFloatAssert: func is_less(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current >= expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected)) return report_success() func is_less_equal(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current > expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected)) return report_success() func is_greater(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current <= expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected)) return report_success() func is_greater_equal(expected :float) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current < expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected)) return report_success() func is_negative() -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current >= 0.0: return report_error(GdAssertMessages.error_is_negative(current)) return report_success() func is_not_negative() -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current < 0.0: return report_error(GdAssertMessages.error_is_not_negative(current)) return report_success() func is_zero() -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or not is_equal_approx(0.00000000, current): return report_error(GdAssertMessages.error_is_zero(current)) return report_success() func is_not_zero() -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or is_equal_approx(0.00000000, current): return report_error(GdAssertMessages.error_is_not_zero()) return report_success() func is_in(expected :Array) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if not expected.has(current): return report_error(GdAssertMessages.error_is_in(current, expected)) return report_success() func is_not_in(expected :Array) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if expected.has(current): return report_error(GdAssertMessages.error_is_not_in(current, expected)) return report_success() func is_between(from :float, to :float) -> GdUnitFloatAssert: - var current = __current() + var current = current_value() if current == null or current < from or current > to: return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to)) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd index 0c9e4b9f..4902ba05 100644 --- a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd @@ -15,7 +15,7 @@ var _sleep_timer :Timer = null func _init(instance :Object, func_name :String, args := Array()): - _line_number = GdUnitAssert._get_line_number() + _line_number = GdUnitAssertions.get_line_number() GdAssertReports.reset_last_error_line_number() # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) @@ -49,7 +49,7 @@ func report_error(error_message :String) -> GdUnitFuncAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _current_error_message @@ -72,43 +72,43 @@ func wait_until(timeout := 2000) -> GdUnitFuncAssert: func is_null() -> GdUnitFuncAssert: - await _validate_callback(__is_null) + await _validate_callback(cb_is_null) return self func is_not_null() -> GdUnitFuncAssert: - await _validate_callback(__is_not_null) + await _validate_callback(cb_is_not_null) return self func is_false() -> GdUnitFuncAssert: - await _validate_callback(__is_false) + await _validate_callback(cb_is_false) return self func is_true() -> GdUnitFuncAssert: - await _validate_callback(__is_true) + await _validate_callback(cb_is_true) return self func is_equal(expected) -> GdUnitFuncAssert: - await _validate_callback(__is_equal, expected) + await _validate_callback(cb_is_equal, expected) return self func is_not_equal(expected) -> GdUnitFuncAssert: - await _validate_callback(__is_not_equal, expected) + await _validate_callback(cb_is_not_equal, expected) return self # we need actually to define this Callable as functions otherwise we results into leaked scripts here # this is actually a Godot bug and needs this kind of workaround -func __is_null(c, _e): return c == null -func __is_not_null(c, _e): return c != null -func __is_false(c, _e): return c == false -func __is_true(c, _e): return c == true -func __is_equal(c, e): return GdObjects.equals(c,e) -func __is_not_equal(c, e): return not GdObjects.equals(c, e) +func cb_is_null(c, _e): return c == null +func cb_is_not_null(c, _e): return c != null +func cb_is_false(c, _e): return c == false +func cb_is_true(c, _e): return c == true +func cb_is_equal(c, e): return GdObjects.equals(c,e) +func cb_is_not_equal(c, e): return not GdObjects.equals(c, e) func _validate_callback(predicate :Callable, expected = null): @@ -128,7 +128,7 @@ func _validate_callback(predicate :Callable, expected = null): _sleep_timer = Timer.new() _sleep_timer.set_name("gdunit_funcassert_sleep_timer_%d" % _sleep_timer.get_instance_id() ) Engine.get_main_loop().root.add_child(_sleep_timer) - + while true: var current = await next_current_value() # is interupted or predicate success @@ -137,14 +137,14 @@ func _validate_callback(predicate :Callable, expected = null): if is_instance_valid(_sleep_timer): _sleep_timer.start(0.05) await _sleep_timer.timeout - + _sleep_timer.stop() await Engine.get_main_loop().process_frame if _interrupted: # https://github.com/godotengine/godot/issues/73052 #var predicate_name = predicate.get_method() var predicate_name :String = str(predicate).split('::')[1] - report_error(GdAssertMessages.error_interrupted(predicate_name.strip_edges().trim_prefix("__"), expected, LocalTime.elapsed(_timeout))) + report_error(GdAssertMessages.error_interrupted(predicate_name.strip_edges().trim_prefix("cb_"), expected, LocalTime.elapsed(_timeout))) else: report_success() _sleep_timer.free() diff --git a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd index 169994be..dcd6a9b1 100644 --- a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd @@ -6,7 +6,8 @@ var _callable :Callable func _init(callable :Callable): # we only support Godot 4.1.x+ because of await issue https://github.com/godotengine/godot/issues/80292 - assert(Engine.get_version_info().hex >= 0x40100, "This assertion is not supported for Godot 4.0.x. Please upgrade to the minimum version Godot 4.1.0!") + assert(Engine.get_version_info().hex >= 0x40100, + "This assertion is not supported for Godot 4.0.x. Please upgrade to the minimum version Godot 4.1.0!") # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) GdAssertReports.reset_last_error_line_number() @@ -15,17 +16,18 @@ func _init(callable :Callable): func _execute() -> Array[ErrorLogEntry]: # execute the given code and monitor for runtime errors - var monitor := GodotGdErrorMonitor.new(true) - monitor.start() if _callable == null or not _callable.is_valid(): _report_error("Invalid Callable '%s'" % _callable) else: await _callable.call() - monitor.stop() - return await monitor.scan() + return await _error_monitor().scan(true) -func _failure_message() -> String: +func _error_monitor() -> GodotGdErrorMonitor: + return GdUnitThreadManager.get_current_context().get_execution_context().error_monitor + + +func failure_message() -> String: return _current_error_message @@ -35,7 +37,7 @@ func _report_success() -> GdUnitAssert: func _report_error(error_message :String, failure_line_number: int = -1) -> GdUnitAssert: - var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssert._get_line_number() + var line_number := failure_line_number if failure_line_number != -1 else GdUnitAssertions.get_line_number() _current_error_message = error_message GdAssertReports.report_error(error_message, line_number) return self @@ -44,6 +46,8 @@ func _report_error(error_message :String, failure_line_number: int = -1) -> GdUn func _has_log_entry(log_entries :Array[ErrorLogEntry], type :ErrorLogEntry.TYPE, error :String) -> bool: for entry in log_entries: if entry._type == type and entry._message == error: + # Erase the log entry we already handled it by this assertion, otherwise it will report at twice + _error_monitor().erase_log_entry(entry) return true return false diff --git a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd index 7f39c060..6fc16e12 100644 --- a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd @@ -4,10 +4,11 @@ var _base: GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_INT): + if not GdUnitAssertions.validate_value_type(current, TYPE_INT): report_error("GdUnitIntAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -18,8 +19,8 @@ func _notification(event): _base = null -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitIntAssert: @@ -32,7 +33,7 @@ func report_error(error :String) -> GdUnitIntAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -62,91 +63,91 @@ func is_not_equal(expected :int) -> GdUnitIntAssert: func is_less(expected :int) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current >= expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected)) return report_success() func is_less_equal(expected :int) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current > expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected)) return report_success() func is_greater(expected :int) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current <= expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected)) return report_success() func is_greater_equal(expected :int) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current < expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected)) return report_success() func is_even() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current % 2 != 0: return report_error(GdAssertMessages.error_is_even(current)) return report_success() func is_odd() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current % 2 == 0: return report_error(GdAssertMessages.error_is_odd(current)) return report_success() func is_negative() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current >= 0: return report_error(GdAssertMessages.error_is_negative(current)) return report_success() func is_not_negative() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current < 0: return report_error(GdAssertMessages.error_is_not_negative(current)) return report_success() func is_zero() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current != 0: return report_error(GdAssertMessages.error_is_zero(current)) return report_success() func is_not_zero() -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == 0: return report_error(GdAssertMessages.error_is_not_zero()) return report_success() func is_in(expected :Array) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if not expected.has(current): return report_error(GdAssertMessages.error_is_in(current, expected)) return report_success() func is_not_in(expected :Array) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if expected.has(current): return report_error(GdAssertMessages.error_is_not_in(current, expected)) return report_success() func is_between(from :int, to :int) -> GdUnitIntAssert: - var current = __current() + var current = current_value() if current == null or current < from or current > to: return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to)) return report_success() diff --git a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd index 18c67bf7..e3ee8790 100644 --- a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd @@ -4,14 +4,15 @@ var _base :GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) if (current != null - and (_base.__validate_value_type(current, TYPE_BOOL) - or _base.__validate_value_type(current, TYPE_INT) - or _base.__validate_value_type(current, TYPE_FLOAT) - or _base.__validate_value_type(current, TYPE_STRING))): + and (GdUnitAssertions.validate_value_type(current, TYPE_BOOL) + or GdUnitAssertions.validate_value_type(current, TYPE_INT) + or GdUnitAssertions.validate_value_type(current, TYPE_FLOAT) + or GdUnitAssertions.validate_value_type(current, TYPE_STRING))): report_error("GdUnitObjectAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -22,8 +23,8 @@ func _notification(event): _base = null -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitObjectAssert: @@ -36,7 +37,7 @@ func report_error(error :String) -> GdUnitObjectAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -67,7 +68,7 @@ func is_not_null() -> GdUnitObjectAssert: @warning_ignore("shadowed_global_identifier") func is_same(expected) -> GdUnitObjectAssert: - var current :Variant = __current() + var current :Variant = current_value() if not is_same(current, expected): report_error(GdAssertMessages.error_is_same(current, expected)) return self @@ -76,7 +77,7 @@ func is_same(expected) -> GdUnitObjectAssert: func is_not_same(expected) -> GdUnitObjectAssert: - var current = __current() + var current = current_value() if is_same(current, expected): report_error(GdAssertMessages.error_not_same(current, expected)) return self @@ -85,7 +86,7 @@ func is_not_same(expected) -> GdUnitObjectAssert: func is_instanceof(type :Object) -> GdUnitObjectAssert: - var current :Object = __current() + var current :Object = current_value() if not is_instance_of(current, type): var result_expected: = GdObjects.extract_class_name(type) var result_current: = GdObjects.extract_class_name(current) @@ -96,7 +97,7 @@ func is_instanceof(type :Object) -> GdUnitObjectAssert: func is_not_instanceof(type) -> GdUnitObjectAssert: - var current :Variant = __current() + var current :Variant = current_value() if is_instance_of(current, type): var result: = GdObjects.extract_class_name(type) if result.is_success(): diff --git a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd index ccc92e56..9598b3eb 100644 --- a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd @@ -4,10 +4,11 @@ var _base :GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not __validate_value_type(current): + if not validate_value_type(current): report_error("GdUnitResultAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -18,12 +19,12 @@ func _notification(event): _base = null -func __validate_value_type(value) -> bool: +func validate_value_type(value) -> bool: return value == null or value is GdUnitResult -func __current() -> GdUnitResult: - return _base.__current() as GdUnitResult +func current_value() -> GdUnitResult: + return _base.current_value() as GdUnitResult func report_success() -> GdUnitResultAssert: @@ -36,7 +37,7 @@ func report_error(error :String) -> GdUnitResultAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -55,7 +56,7 @@ func is_not_null() -> GdUnitResultAssert: func is_empty() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_empty(): report_error(GdAssertMessages.error_result_is_empty(result)) else: @@ -64,7 +65,7 @@ func is_empty() -> GdUnitResultAssert: func is_success() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_success(): report_error(GdAssertMessages.error_result_is_success(result)) else: @@ -73,7 +74,7 @@ func is_success() -> GdUnitResultAssert: func is_warning() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_warn(): report_error(GdAssertMessages.error_result_is_warning(result)) else: @@ -82,7 +83,7 @@ func is_warning() -> GdUnitResultAssert: func is_error() -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null or not result.is_error(): report_error(GdAssertMessages.error_result_is_error(result)) else: @@ -91,7 +92,7 @@ func is_error() -> GdUnitResultAssert: func contains_message(expected :String) -> GdUnitResultAssert: - var result := __current() + var result := current_value() if result == null: report_error(GdAssertMessages.error_result_has_message("", expected)) return self @@ -107,7 +108,7 @@ func contains_message(expected :String) -> GdUnitResultAssert: func is_value(expected) -> GdUnitResultAssert: - var result := __current() + var result := current_value() var value = null if result == null else result.value() if not GdObjects.equals(value, expected): report_error(GdAssertMessages.error_result_is_value(value, expected)) diff --git a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd index b82ebe46..4ef2eeb0 100644 --- a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd @@ -16,7 +16,7 @@ func _init(emitter :Object): var context := GdUnitThreadManager.get_current_context() context.set_assert(self) _signal_collector = context.get_signal_collector() - _line_number = GdUnitAssert._get_line_number() + _line_number = GdUnitAssertions.get_line_number() _emitter = emitter GdAssertReports.reset_last_error_line_number() @@ -27,7 +27,7 @@ func report_success() -> GdUnitAssert: func report_warning(message :String) -> GdUnitAssert: - GdAssertReports.report_warning(message, GdUnitAssert._get_line_number()) + GdAssertReports.report_warning(message, GdUnitAssertions.get_line_number()) return self @@ -37,7 +37,7 @@ func report_error(error_message :String) -> GdUnitAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _current_error_message @@ -68,13 +68,13 @@ func is_signal_exists(signal_name :String) -> GdUnitSignalAssert: # Verifies that given signal is emitted until waiting time func is_emitted(name :String, args := []) -> GdUnitSignalAssert: - _line_number = GdUnitAssert._get_line_number() + _line_number = GdUnitAssertions.get_line_number() return await _wail_until_signal(name, args, false) # Verifies that given signal is NOT emitted until waiting time func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert: - _line_number = GdUnitAssert._get_line_number() + _line_number = GdUnitAssertions.get_line_number() return await _wail_until_signal(name, args, true) @@ -101,10 +101,10 @@ func _wail_until_signal(signal_name :String, expected_args :Array, expect_not_em is_signal_emitted = _signal_collector.match(_emitter, signal_name, expected_args) if is_signal_emitted and expect_not_emitted: report_error(GdAssertMessages.error_signal_emitted(signal_name, expected_args, LocalTime.elapsed(int(_timeout-timer.time_left*1000)))) - + if _interrupted and not expect_not_emitted: report_error(GdAssertMessages.error_wait_signal(signal_name, expected_args, LocalTime.elapsed(_timeout))) timer.free() if is_instance_valid(_emitter): - _signal_collector.reset_received_signals(_emitter) + _signal_collector.reset_received_signals(_emitter, signal_name, expected_args) return self diff --git a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd index 154b9e5c..8c1814fd 100644 --- a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd @@ -4,10 +4,11 @@ var _base :GdUnitAssert func _init(current): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) - if not _base.__validate_value_type(current, TYPE_STRING): + if current != null and typeof(current) != TYPE_STRING and typeof(current) != TYPE_STRING_NAME: report_error("GdUnitStringAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current)) @@ -18,12 +19,12 @@ func _notification(event): _base = null -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message -func __current(): - var current = _base.__current() +func current_value(): + var current = _base.current_value() if current == null: return null return current as String @@ -55,92 +56,92 @@ func is_not_null() -> GdUnitStringAssert: func is_equal(expected) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_equal(current, expected)) if not GdObjects.equals(current, expected): var diffs := GdDiffTool.string_diff(current, expected) - var formatted_current := GdAssertMessages._colored_array_div(diffs[1]) + var formatted_current := GdAssertMessages.colored_array_div(diffs[1]) return report_error(GdAssertMessages.error_equal(formatted_current, expected)) return report_success() func is_equal_ignoring_case(expected) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_equal_ignoring_case(current, expected)) if not GdObjects.equals(current, expected, true): var diffs := GdDiffTool.string_diff(current, expected) - var formatted_current := GdAssertMessages._colored_array_div(diffs[1]) + var formatted_current := GdAssertMessages.colored_array_div(diffs[1]) return report_error(GdAssertMessages.error_equal_ignoring_case(formatted_current, expected)) return report_success() func is_not_equal(expected) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if GdObjects.equals(current, expected): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() func is_not_equal_ignoring_case(expected) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if GdObjects.equals(current, expected, true): return report_error(GdAssertMessages.error_not_equal(current, expected)) return report_success() func is_empty() -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null or not current.is_empty(): return report_error(GdAssertMessages.error_is_empty(current)) return report_success() func is_not_empty() -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null or current.is_empty(): return report_error(GdAssertMessages.error_is_not_empty()) return report_success() func contains(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null or current.find(expected) == -1: return report_error(GdAssertMessages.error_contains(current, expected)) return report_success() func not_contains(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current != null and current.find(expected) != -1: return report_error(GdAssertMessages.error_not_contains(current, expected)) return report_success() func contains_ignoring_case(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null or current.findn(expected) == -1: return report_error(GdAssertMessages.error_contains_ignoring_case(current, expected)) return report_success() func not_contains_ignoring_case(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current != null and current.findn(expected) != -1: return report_error(GdAssertMessages.error_not_contains_ignoring_case(current, expected)) return report_success() func starts_with(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null or current.find(expected) != 0: return report_error(GdAssertMessages.error_starts_with(current, expected)) return report_success() func ends_with(expected :String) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_ends_with(current, expected)) var find = current.length() - expected.length() @@ -149,8 +150,9 @@ func ends_with(expected :String) -> GdUnitStringAssert: return report_success() +# gdlint:disable=max-returns func has_length(expected :int, comparator :int = Comparator.EQUAL) -> GdUnitStringAssert: - var current = __current() + var current = current_value() if current == null: return report_error(GdAssertMessages.error_has_length(current, expected, comparator)) match comparator: diff --git a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd index 43a63f1d..9fbe25cb 100644 --- a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd +++ b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd @@ -5,7 +5,8 @@ var _current_type :int func _init(current :Variant): - _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new(current) + _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", + ResourceLoader.CACHE_MODE_REUSE).new(current) # save the actual assert instance on the current thread context GdUnitThreadManager.get_current_context().set_assert(self) if not _validate_value_type(current): @@ -21,14 +22,17 @@ func _notification(event): func _validate_value_type(value) -> bool: - return (value == null + return ( + value == null or typeof(value) in [ TYPE_VECTOR2, TYPE_VECTOR2I, TYPE_VECTOR3, TYPE_VECTOR3I, TYPE_VECTOR4, - TYPE_VECTOR4I]) + TYPE_VECTOR4I + ] + ) func _validate_is_vector_type(value :Variant) -> bool: @@ -39,8 +43,8 @@ func _validate_is_vector_type(value :Variant) -> bool: return false -func __current() -> Variant: - return _base.__current() +func current_value() -> Variant: + return _base.current_value() func report_success() -> GdUnitVectorAssert: @@ -53,7 +57,7 @@ func report_error(error :String) -> GdUnitVectorAssert: return self -func _failure_message() -> String: +func failure_message() -> String: return _base._current_error_message @@ -90,7 +94,7 @@ func is_not_equal(expected :Variant) -> GdUnitVectorAssert: func is_equal_approx(expected :Variant, approx :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected) or not _validate_is_vector_type(approx): return self - var current = __current() + var current = current_value() var from = expected - approx var to = expected + approx if current == null or (not _is_equal_approx(current, from, to)): @@ -117,7 +121,7 @@ func _is_equal_approx(current, from, to) -> bool: func is_less(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current = current_value() if current == null or current >= expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_THAN, current, expected)) return report_success() @@ -126,7 +130,7 @@ func is_less(expected :Variant) -> GdUnitVectorAssert: func is_less_equal(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current = current_value() if current == null or current > expected: return report_error(GdAssertMessages.error_is_value(Comparator.LESS_EQUAL, current, expected)) return report_success() @@ -135,7 +139,7 @@ func is_less_equal(expected :Variant) -> GdUnitVectorAssert: func is_greater(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current = current_value() if current == null or current <= expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_THAN, current, expected)) return report_success() @@ -144,7 +148,7 @@ func is_greater(expected :Variant) -> GdUnitVectorAssert: func is_greater_equal(expected :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(expected): return self - var current = __current() + var current = current_value() if current == null or current < expected: return report_error(GdAssertMessages.error_is_value(Comparator.GREATER_EQUAL, current, expected)) return report_success() @@ -153,7 +157,7 @@ func is_greater_equal(expected :Variant) -> GdUnitVectorAssert: func is_between(from :Variant, to :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(from) or not _validate_is_vector_type(to): return self - var current = __current() + var current = current_value() if current == null or not (current >= from and current <= to): return report_error(GdAssertMessages.error_is_value(Comparator.BETWEEN_EQUAL, current, from, to)) return report_success() @@ -162,7 +166,7 @@ func is_between(from :Variant, to :Variant) -> GdUnitVectorAssert: func is_not_between(from :Variant, to :Variant) -> GdUnitVectorAssert: if not _validate_is_vector_type(from) or not _validate_is_vector_type(to): return self - var current = __current() + var current = current_value() if (current != null and current >= from and current <= to): return report_error(GdAssertMessages.error_is_value(Comparator.NOT_BETWEEN_EQUAL, current, from, to)) return report_success() diff --git a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd b/addons/gdUnit4/src/cmd/CmdCommandHandler.gd index e32557ec..cd4eecd5 100644 --- a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd +++ b/addons/gdUnit4/src/cmd/CmdCommandHandler.gd @@ -81,12 +81,20 @@ func execute(commands :Array) -> GdUnitResult: var cmd_name := cmd.name() if _command_cbs.has(cmd_name): var cb_s :Callable = _command_cbs.get(cmd_name)[CB_SINGLE_ARG] - var cb_m :Callable = _command_cbs.get(cmd_name)[CB_MULTI_ARGS] - if cmd.arguments().is_empty(): + var arguments := cmd.arguments() + if cb_s and arguments.size() == 0: cb_s.call() + elif cb_s: + cb_s.call(arguments[0]) else: - if cmd.arguments().size() == 1: - cb_s.call(cmd.arguments()[CB_SINGLE_ARG]) - else: - cb_m.callv(cmd.arguments()) + var cb_m :Callable = _command_cbs.get(cmd_name)[CB_MULTI_ARGS] + # we need to find the method and determin the arguments to call the right function + for m in cb_m.get_object().get_method_list(): + if m["name"] == cb_m.get_method(): + if m["args"].size() > 1: + cb_m.callv(arguments) + break + else: + cb_m.call(arguments) + break return GdUnitResult.success(true) diff --git a/addons/gdUnit4/src/cmd/CmdConsole.gd b/addons/gdUnit4/src/cmd/CmdConsole.gd index 5a64b154..c01be583 100644 --- a/addons/gdUnit4/src/cmd/CmdConsole.gd +++ b/addons/gdUnit4/src/cmd/CmdConsole.gd @@ -35,6 +35,16 @@ func color(p_color :Color) -> CmdConsole: return self +func save_cursor() -> CmdConsole: + printraw("") + return self + + +func restore_cursor() -> CmdConsole: + printraw("") + return self + + func end_color() -> CmdConsole: printraw("") return self diff --git a/addons/gdUnit4/src/core/GdFunctionDoubler.gd b/addons/gdUnit4/src/core/GdFunctionDoubler.gd index d3df8a9b..1df1638c 100644 --- a/addons/gdUnit4/src/core/GdFunctionDoubler.gd +++ b/addons/gdUnit4/src/core/GdFunctionDoubler.gd @@ -110,10 +110,11 @@ func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray: # save original constructor arguments if func_name == "_init": var constructor_args := ",".join(GdFunctionDoubler.extract_constructor_args(args)) - var constructor := "func _init(%s):\n super(%s)\n pass\n" % [constructor_args, ", ".join(arg_names)] + var constructor := "func _init(%s) -> void:\n super(%s)\n pass\n" % [constructor_args, ", ".join(arg_names)] return constructor.split("\n") var double_src := "" + double_src += '@warning_ignore("untyped_declaration")\n' if Engine.get_version_info().hex >= 0x40200 else '\n' if func_descriptor.is_engine(): double_src += '@warning_ignore("native_method_override")\n' double_src += '@warning_ignore("shadowed_variable")\n' @@ -148,7 +149,10 @@ static func extract_constructor_args(args :Array) -> PackedStringArray: var a := arg as GdFunctionArgument var arg_name := a._name var default_value = get_default(a) - constructor_args.append(arg_name + "=" + default_value) + if default_value == "null": + constructor_args.append(arg_name + ":Variant=" + default_value) + else: + constructor_args.append(arg_name + ":=" + default_value) return constructor_args diff --git a/addons/gdUnit4/src/core/GdObjects.gd b/addons/gdUnit4/src/core/GdObjects.gd index ee4bf792..94de509d 100644 --- a/addons/gdUnit4/src/core/GdObjects.gd +++ b/addons/gdUnit4/src/core/GdObjects.gd @@ -178,7 +178,7 @@ static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary: return {"%s" % clazz_name : dict} -static func equals(obj_a, obj_b, case_sensitive :bool = false, compare_mode :COMPARE_MODE = COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool: +static func equals(obj_a :Variant, obj_b :Variant, case_sensitive :bool = false, compare_mode :COMPARE_MODE = COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool: return _equals(obj_a, obj_b, case_sensitive, compare_mode, [], 0) @@ -190,7 +190,7 @@ static func equals_sorted(obj_a :Array, obj_b :Array, case_sensitive :bool = fal return equals(a, b, case_sensitive, compare_mode) -static func _equals(obj_a, obj_b, case_sensitive :bool, compare_mode :COMPARE_MODE, deep_stack, stack_depth :int ) -> bool: +static func _equals(obj_a :Variant, obj_b :Variant, case_sensitive :bool, compare_mode :COMPARE_MODE, deep_stack :Array, stack_depth :int ) -> bool: var type_a := typeof(obj_a) var type_b := typeof(obj_b) if stack_depth > 32: @@ -198,6 +198,12 @@ static func _equals(obj_a, obj_b, case_sensitive :bool, compare_mode :COMPARE_MO push_error("GdUnit equals has max stack deep reached!") return false + # use argument matcher if requested + if is_instance_valid(obj_a) and obj_a is GdUnitArgumentMatcher: + return (obj_a as GdUnitArgumentMatcher).is_match(obj_b) + if is_instance_valid(obj_b) and obj_b is GdUnitArgumentMatcher: + return (obj_b as GdUnitArgumentMatcher).is_match(obj_a) + stack_depth += 1 # fast fail is different types if not _is_type_equivalent(type_a, type_b): @@ -305,7 +311,7 @@ static func type_as_string(type :int) -> String: return TYPE_AS_STRING_MAPPINGS.get(type, "Variant") -static func typeof_as_string(value) -> String: +static func typeof_as_string(value :Variant) -> String: return TYPE_AS_STRING_MAPPINGS.get(typeof(value), "Unknown type") @@ -314,15 +320,15 @@ static func all_types() -> PackedInt32Array: static func string_as_typeof(type_name :String) -> int: - var type = TYPE_AS_STRING_MAPPINGS.find_key(type_name) + var type :Variant = TYPE_AS_STRING_MAPPINGS.find_key(type_name) return type if type != null else TYPE_VARIANT -static func is_primitive_type(value) -> bool: +static func is_primitive_type(value :Variant) -> bool: return typeof(value) in [TYPE_BOOL, TYPE_STRING, TYPE_STRING_NAME, TYPE_INT, TYPE_FLOAT] -static func _is_type_equivalent(type_a, type_b) -> bool: +static func _is_type_equivalent(type_a :int, type_b :int) -> bool: # don't test for TYPE_STRING_NAME equivalenz if type_a == TYPE_STRING_NAME or type_b == TYPE_STRING_NAME: return true @@ -353,8 +359,7 @@ static func is_type(value :Variant) -> bool: return false -@warning_ignore("shadowed_global_identifier") -static func _is_same(left, right) -> bool: +static func _is_same(left :Variant, right :Variant) -> bool: var left_type := -1 if left == null else typeof(left) var right_type := -1 if right == null else typeof(right) @@ -366,27 +371,27 @@ static func _is_same(left, right) -> bool: return equals(left, right) -static func is_object(value) -> bool: +static func is_object(value :Variant) -> bool: return typeof(value) == TYPE_OBJECT -static func is_script(value) -> bool: +static func is_script(value :Variant) -> bool: return is_object(value) and value is Script static func is_test_suite(script :Script) -> bool: - return is_gd_testsuite(script) or GdUnit4MonoApiLoader.is_test_suite(script.resource_path) + return is_gd_testsuite(script) or GdUnit4CSharpApiLoader.is_test_suite(script.resource_path) -static func is_native_class(value) -> bool: +static func is_native_class(value :Variant) -> bool: return is_object(value) and is_engine_type(value) -static func is_scene(value) -> bool: +static func is_scene(value :Variant) -> bool: return is_object(value) and value is PackedScene -static func is_scene_resource_path(value) -> bool: +static func is_scene_resource_path(value :Variant) -> bool: return value is String and value.ends_with(".tscn") @@ -432,7 +437,7 @@ static func is_instance(value :Variant) -> bool: # only object form type Node and attached filename -static func is_instance_scene(instance) -> bool: +static func is_instance_scene(instance :Variant) -> bool: if instance is Node: var node := instance as Node return node.get_scene_file_path() != null and not node.get_scene_file_path().is_empty() @@ -445,7 +450,7 @@ static func can_be_instantiate(obj :Variant) -> bool: return obj.has_method("new") -static func create_instance(clazz) -> GdUnitResult: +static func create_instance(clazz :Variant) -> GdUnitResult: match typeof(clazz): TYPE_OBJECT: # test is given clazz already an instance @@ -463,7 +468,7 @@ static func create_instance(clazz) -> GdUnitResult: var clazz_path :String = extract_class_path(clazz)[0] if not FileAccess.file_exists(clazz_path): return GdUnitResult.error("Class '%s' not found." % clazz) - var script = load(clazz_path) + var script := load(clazz_path) if script != null: return GdUnitResult.success(script.new()) else: @@ -471,7 +476,7 @@ static func create_instance(clazz) -> GdUnitResult: return GdUnitResult.error("Can't create instance for class '%s'." % clazz) -static func extract_class_path(clazz) -> PackedStringArray: +static func extract_class_path(clazz :Variant) -> PackedStringArray: var clazz_path := PackedStringArray() if clazz is String: clazz_path.append(clazz) @@ -513,7 +518,7 @@ static func extract_class_name_from_class_path(clazz_path :PackedStringArray) -> return clazz_name -static func extract_class_name(clazz) -> GdUnitResult: +static func extract_class_name(clazz :Variant) -> GdUnitResult: if clazz == null: return GdUnitResult.error("Can't extract class name form a null value.") @@ -529,7 +534,7 @@ static func extract_class_name(clazz) -> GdUnitResult: if ClassDB.class_exists(clazz): return GdUnitResult.success(clazz) var source_sript :Script = load(clazz) - var clazz_name = load("res://addons/gdUnit4/src/core/parse/GdScriptParser.gd").new().get_class_name(source_sript) + var clazz_name :String = load("res://addons/gdUnit4/src/core/parse/GdScriptParser.gd").new().get_class_name(source_sript) return GdUnitResult.success(to_pascal_case(clazz_name)) if is_primitive_type(clazz): @@ -537,12 +542,12 @@ static func extract_class_name(clazz) -> GdUnitResult: if is_script(clazz): if clazz.resource_path.is_empty(): - var class_path = extract_class_name_from_class_path(extract_class_path(clazz)) + var class_path := extract_class_name_from_class_path(extract_class_path(clazz)) return GdUnitResult.success(class_path); return extract_class_name(clazz.resource_path) # need to create an instance for a class typ the extract the class name - var instance = clazz.new() + var instance :Variant = clazz.new() if instance == null: return GdUnitResult.error("Can't create a instance for class '%s'" % clazz) var result := extract_class_name(instance) @@ -589,7 +594,7 @@ static func extract_class_functions(clazz_name :String, script_path :PackedStrin # scans all registert script classes for given # if the class is public in the global space than return true otherwise false # public class means the script class is defined by 'class_name ' -static func is_public_script_class(clazz_name) -> bool: +static func is_public_script_class(clazz_name :String) -> bool: var script_classes:Array[Dictionary] = ProjectSettings.get_global_class_list() for class_info in script_classes: if class_info.has("class"): diff --git a/addons/gdUnit4/src/core/GdUnitFileAccess.gd b/addons/gdUnit4/src/core/GdUnitFileAccess.gd new file mode 100644 index 00000000..4f524e8a --- /dev/null +++ b/addons/gdUnit4/src/core/GdUnitFileAccess.gd @@ -0,0 +1,211 @@ +class_name GdUnitFileAccess +extends RefCounted + +const GDUNIT_TEMP := "user://tmp" + + +static func current_dir() -> String: + return ProjectSettings.globalize_path("res://") + + +static func clear_tmp() -> void: + delete_directory(GDUNIT_TEMP) + + +# Creates a new file under +static func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess: + var file_path := create_temp_dir(relative_path) + "/" + file_name + var file := FileAccess.open(file_path, mode) + if file == null: + push_error("Error creating temporary file at: %s, %s" % [file_path, error_string(FileAccess.get_open_error())]) + return file + + +static func temp_dir() -> String: + if not DirAccess.dir_exists_absolute(GDUNIT_TEMP): + DirAccess.make_dir_recursive_absolute(GDUNIT_TEMP) + return GDUNIT_TEMP + + +static func create_temp_dir(folder_name :String) -> String: + var new_folder := temp_dir() + "/" + folder_name + if not DirAccess.dir_exists_absolute(new_folder): + DirAccess.make_dir_recursive_absolute(new_folder) + return new_folder + + +static func copy_file(from_file :String, to_dir :String) -> GdUnitResult: + var dir := DirAccess.open(to_dir) + if dir != null: + var to_file := to_dir + "/" + from_file.get_file() + prints("Copy %s to %s" % [from_file, to_file]) + var error := dir.copy(from_file, to_file) + if error != OK: + return GdUnitResult.error("Can't copy file form '%s' to '%s'. Error: '%s'" % [from_file, to_file, error_string(error)]) + return GdUnitResult.success(to_file) + return GdUnitResult.error("Directory not found: " + to_dir) + + +static func copy_directory(from_dir :String, to_dir :String, recursive :bool = false) -> bool: + if not DirAccess.dir_exists_absolute(from_dir): + push_error("Source directory not found '%s'" % from_dir) + return false + + # check if destination exists + if not DirAccess.dir_exists_absolute(to_dir): + # create it + var err := DirAccess.make_dir_recursive_absolute(to_dir) + if err != OK: + push_error("Can't create directory '%s'. Error: %s" % [to_dir, error_string(err)]) + return false + var source_dir := DirAccess.open(from_dir) + var dest_dir := DirAccess.open(to_dir) + if source_dir != null: + source_dir.list_dir_begin() + var next := "." + + while next != "": + next = source_dir.get_next() + if next == "" or next == "." or next == "..": + continue + var source := source_dir.get_current_dir() + "/" + next + var dest := dest_dir.get_current_dir() + "/" + next + if source_dir.current_is_dir(): + if recursive: + copy_directory(source + "/", dest, recursive) + continue + var err := source_dir.copy(source, dest) + if err != OK: + push_error("Error checked copy file '%s' to '%s'" % [source, dest]) + return false + + return true + else: + push_error("Directory not found: " + from_dir) + return false + + +static func delete_directory(path :String, only_content := false) -> void: + var dir := DirAccess.open(path) + if dir != null: + dir.list_dir_begin() + var file_name := "." + while file_name != "": + file_name = dir.get_next() + if file_name.is_empty() or file_name == "." or file_name == "..": + continue + var next := path + "/" +file_name + if dir.current_is_dir(): + delete_directory(next) + else: + # delete file + var err := dir.remove(next) + if err: + push_error("Delete %s failed: %s" % [next, error_string(err)]) + if not only_content: + var err := dir.remove(path) + if err: + push_error("Delete %s failed: %s" % [path, error_string(err)]) + + +static func delete_path_index_lower_equals_than(path :String, prefix :String, index :int) -> int: + var dir := DirAccess.open(path) + if dir == null: + return 0 + var deleted := 0 + dir.list_dir_begin() + var next := "." + while next != "": + next = dir.get_next() + if next.is_empty() or next == "." or next == "..": + continue + if next.begins_with(prefix): + var current_index := next.split("_")[1].to_int() + if current_index <= index: + deleted += 1 + delete_directory(path + "/" + next) + return deleted + + +# scans given path for sub directories by given prefix and returns the highest index numer +# e.g. +static func find_last_path_index(path :String, prefix :String) -> int: + var dir := DirAccess.open(path) + if dir == null: + return 0 + var last_iteration := 0 + dir.list_dir_begin() + var next := "." + while next != "": + next = dir.get_next() + if next.is_empty() or next == "." or next == "..": + continue + if next.begins_with(prefix): + var iteration := next.split("_")[1].to_int() + if iteration > last_iteration: + last_iteration = iteration + return last_iteration + + +static func scan_dir(path :String) -> PackedStringArray: + var dir := DirAccess.open(path) + if dir == null or not dir.dir_exists(path): + return PackedStringArray() + var content := PackedStringArray() + dir.list_dir_begin() + var next := "." + while next != "": + next = dir.get_next() + if next.is_empty() or next == "." or next == "..": + continue + content.append(next) + return content + + +static func resource_as_array(resource_path :String) -> PackedStringArray: + var file := FileAccess.open(resource_path, FileAccess.READ) + if file == null: + push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_string(FileAccess.get_open_error())]) + return PackedStringArray() + var file_content := PackedStringArray() + while not file.eof_reached(): + file_content.append(file.get_line()) + return file_content + + +static func resource_as_string(resource_path :String) -> String: + var file := FileAccess.open(resource_path, FileAccess.READ) + if file == null: + push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_string(FileAccess.get_open_error())]) + return "" + return file.get_as_text(true) + + +static func make_qualified_path(path :String) -> String: + if not path.begins_with("res://"): + if path.begins_with("//"): + return path.replace("//", "res://") + if path.begins_with("/"): + return "res:/" + path + return path + + +static func extract_zip(zip_package :String, dest_path :String) -> GdUnitResult: + var zip: ZIPReader = ZIPReader.new() + var err := zip.open(zip_package) + if err != OK: + return GdUnitResult.error("Extracting `%s` failed! Please collect the error log and report this. Error Code: %s" % [zip_package, err]) + var zip_entries: PackedStringArray = zip.get_files() + # Get base path and step over archive folder + var archive_path := zip_entries[0] + zip_entries.remove_at(0) + + for zip_entry in zip_entries: + var new_file_path: String = dest_path + "/" + zip_entry.replace(archive_path, "") + if zip_entry.ends_with("/"): + DirAccess.make_dir_recursive_absolute(new_file_path) + continue + var file: FileAccess = FileAccess.open(new_file_path, FileAccess.WRITE) + file.store_buffer(zip.read_file(zip_entry)) + zip.close() + return GdUnitResult.success(dest_path) diff --git a/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd b/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd index 5d455abb..212896ef 100644 --- a/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd +++ b/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd @@ -5,9 +5,10 @@ var __saved_interactions := Dictionary() var __verified_interactions := Array() -func __save_function_interaction(args :Array) -> void: +func __save_function_interaction(args :Array[Variant]) -> void: var matcher := GdUnitArgumentMatchers.to_matcher(args, true) - for key in __saved_interactions.keys(): + for index in __saved_interactions.keys().size(): + var key :Variant = __saved_interactions.keys()[index] if matcher.is_match(key): __saved_interactions[key] += 1 return @@ -23,11 +24,12 @@ func __do_verify_interactions(times :int = 1) -> Object: return self -func __verify_interactions(args :Array): +func __verify_interactions(args :Array[Variant]) -> void: var summary := Dictionary() var total_interactions := 0 var matcher := GdUnitArgumentMatchers.to_matcher(args, true) - for key in __saved_interactions.keys(): + for index in __saved_interactions.keys().size(): + var key :Variant = __saved_interactions.keys()[index] if matcher.is_match(key): var interactions :int = __saved_interactions.get(key, 0) total_interactions += interactions @@ -37,11 +39,11 @@ func __verify_interactions(args :Array): var gd_assert := GdUnitAssertImpl.new("") if total_interactions != __expected_interactions: - var expected_summary = {args : __expected_interactions} + var expected_summary := {args : __expected_interactions} var error_message :String # if no interactions macht collect not verified interactions for failure report if summary.is_empty(): - var current_summary = __verify_no_more_interactions() + var current_summary := __verify_no_more_interactions() error_message = GdAssertMessages.error_validate_interactions(current_summary, expected_summary) else: error_message = GdAssertMessages.error_validate_interactions(summary, expected_summary) @@ -54,21 +56,23 @@ func __verify_interactions(args :Array): func __verify_no_interactions() -> Dictionary: var summary := Dictionary() if not __saved_interactions.is_empty(): - for func_call in __saved_interactions.keys(): + for index in __saved_interactions.keys().size(): + var func_call :Variant = __saved_interactions.keys()[index] summary[func_call] = __saved_interactions[func_call] return summary func __verify_no_more_interactions() -> Dictionary: var summary := Dictionary() - var called_functions :Array = __saved_interactions.keys() + var called_functions :Array[Variant] = __saved_interactions.keys() if called_functions != __verified_interactions: # collect the not verified functions var called_but_not_verified := called_functions.duplicate() - for verified_function in __verified_interactions: - called_but_not_verified.erase(verified_function) + for index in __verified_interactions.size(): + called_but_not_verified.erase(__verified_interactions[index]) - for not_verified in called_but_not_verified: + for index in called_but_not_verified.size(): + var not_verified :Variant = called_but_not_verified[index] summary[not_verified] = __saved_interactions[not_verified] return summary @@ -77,9 +81,10 @@ func __reset_interactions() -> void: __saved_interactions.clear() -func __filter_vargs(arg_values :Array) -> Array: - var filtered := Array() - for arg in arg_values: +func __filter_vargs(arg_values :Array[Variant]) -> Array[Variant]: + var filtered :Array[Variant] = [] + for index in arg_values.size(): + var arg :Variant = arg_values[index] if typeof(arg) == TYPE_STRING and arg == GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE: continue filtered.append(arg) diff --git a/addons/gdUnit4/src/core/GdUnitProperty.gd b/addons/gdUnit4/src/core/GdUnitProperty.gd index 9373d9c7..6e338a3f 100644 --- a/addons/gdUnit4/src/core/GdUnitProperty.gd +++ b/addons/gdUnit4/src/core/GdUnitProperty.gd @@ -1,15 +1,16 @@ class_name GdUnitProperty extends RefCounted + var _name :String var _help :String var _type :int -var _value +var _value :Variant var _value_set :PackedStringArray -var _default +var _default :Variant -func _init(p_name :String, p_type :int, p_value, p_default_value, p_help :="", p_value_set := PackedStringArray()): +func _init(p_name :String, p_type :int, p_value :Variant, p_default_value :Variant, p_help :="", p_value_set := PackedStringArray()) -> void: _name = p_name _type = p_type _value = p_value @@ -26,7 +27,7 @@ func type() -> int: return _type -func value(): +func value() -> Variant: return _value @@ -52,7 +53,7 @@ func set_value(p_value :Variant) -> void: _value = p_value -func default(): +func default() -> Variant: return _default diff --git a/addons/gdUnit4/src/core/GdUnitResult.gd b/addons/gdUnit4/src/core/GdUnitResult.gd index cda3d51a..f2d297f9 100644 --- a/addons/gdUnit4/src/core/GdUnitResult.gd +++ b/addons/gdUnit4/src/core/GdUnitResult.gd @@ -8,7 +8,7 @@ enum { EMPTY } -var _state +var _state :Variant var _warn_message := "" var _error_message := "" var _value :Variant = null @@ -66,7 +66,7 @@ func value() -> Variant: return _value -func or_else(p_value): +func or_else(p_value :Variant) -> Variant: if not is_success(): return p_value return value() diff --git a/addons/gdUnit4/src/core/GdUnitRunner.gd b/addons/gdUnit4/src/core/GdUnitRunner.gd index c383fc3f..686e4f89 100644 --- a/addons/gdUnit4/src/core/GdUnitRunner.gd +++ b/addons/gdUnit4/src/core/GdUnitRunner.gd @@ -17,11 +17,11 @@ const GDUNIT_RUNNER = "GdUnitRunner" var _config := GdUnitRunnerConfig.new() var _test_suites_to_process :Array -var _state = INIT -var _cs_executor +var _state :int = INIT +var _cs_executor :RefCounted -func _init(): +func _init() -> void: # minimize scene window checked debug mode if OS.get_cmdline_args().size() == 1: DisplayServer.window_set_title("GdUnit4 Runner (Debug Mode)") @@ -30,10 +30,10 @@ func _init(): DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED) # store current runner instance to engine meta data to can be access in as a singleton Engine.set_meta(GDUNIT_RUNNER, self) - _cs_executor = GdUnit4MonoApiLoader.create_executor(self) + _cs_executor = GdUnit4CSharpApiLoader.create_executor(self) -func _ready(): +func _ready() -> void: var config_result := _config.load_config() if config_result.is_error(): push_error(config_result.error_message()) @@ -48,23 +48,23 @@ func _ready(): _state = INIT -func _on_connection_failed(message :String): +func _on_connection_failed(message :String) -> void: prints("_on_connection_failed", message, _test_suites_to_process) _state = STOP -func _notification(what): +func _notification(what :int) -> void: #prints("GdUnitRunner", self, GdObjects.notification_as_string(what)) if what == NOTIFICATION_PREDELETE: Engine.remove_meta(GDUNIT_RUNNER) -func _process(_delta): +func _process(_delta :float) -> void: match _state: INIT: # wait until client is connected to the GdUnitServer if _client.is_client_connected(): - var time = LocalTime.now() + var time := LocalTime.now() prints("Scan for test suites.") _test_suites_to_process = load_test_suits() prints("Scanning of %d test suites took" % _test_suites_to_process.size(), time.elapsed_since()) @@ -113,7 +113,7 @@ func load_test_suits() -> Array: func gdUnitInit() -> void: #enable_manuall_polling() send_message("Scaned %d test suites" % _test_suites_to_process.size()) - var total_count = _collect_test_case_count(_test_suites_to_process) + var total_count := _collect_test_case_count(_test_suites_to_process) _on_gdunit_event(GdUnitInit.new(_test_suites_to_process.size(), total_count)) for test_suite in _test_suites_to_process: send_test_suite(test_suite) @@ -145,20 +145,20 @@ func _do_filter_test_case(test_suite :Node, test_case :Node, included_tests :Pac func _collect_test_case_count(testSuites :Array) -> int: var total :int = 0 for test_suite in testSuites: - total += (test_suite as Node).get_child_count() + total += test_suite.get_child_count() return total # RPC send functions -func send_message(message :String): +func send_message(message :String) -> void: _client.rpc_send(RPCMessage.of(message)) -func send_test_suite(test_suite): +func send_test_suite(test_suite :Node) -> void: _client.rpc_send(RPCGdUnitTestSuite.of(test_suite)) -func _on_gdunit_event(event :GdUnitEvent): +func _on_gdunit_event(event :GdUnitEvent) -> void: _client.rpc_send(RPCGdUnitEvent.of(event)) diff --git a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd index f9816cc4..080c18ea 100644 --- a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd +++ b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd @@ -72,10 +72,10 @@ func add_test_case(p_resource_path :String, test_name :StringName, test_param_in # '/path/path', res://path/path', 'res://path/path/testsuite.gd' or 'testsuite' # 'res://path/path/testsuite.gd:test_case' or 'testsuite:test_case' func skip_test_suite(value :StringName) -> GdUnitRunnerConfig: - var parts :Array = GdUnitTools.make_qualified_path(value).rsplit(":") + var parts :Array = GdUnitFileAccess.make_qualified_path(value).rsplit(":") if parts[0] == "res": parts.pop_front() - parts[0] = GdUnitTools.make_qualified_path(parts[0]) + parts[0] = GdUnitFileAccess.make_qualified_path(parts[0]) match parts.size(): 1: skipped()[parts[0]] = PackedStringArray() 2: skip_test_case(parts[0], parts[1]) @@ -108,7 +108,7 @@ func save_config(path :String = CONFIG_FILE) -> GdUnitResult: var file := FileAccess.open(path, FileAccess.WRITE) if file == null: var error = FileAccess.get_open_error() - return GdUnitResult.error("Can't write test runner configuration '%s'! %s" % [path, GdUnitTools.error_as_string(error)]) + return GdUnitResult.error("Can't write test runner configuration '%s'! %s" % [path, error_string(error)]) _config[VERSION] = CONFIG_VERSION file.store_string(JSON.stringify(_config)) return GdUnitResult.success(path) @@ -120,7 +120,7 @@ func load_config(path :String = CONFIG_FILE) -> GdUnitResult: var file := FileAccess.open(path, FileAccess.READ) if file == null: var error = FileAccess.get_open_error() - return GdUnitResult.error("Can't load test runner configuration '%s'! ERROR: %s." % [path, GdUnitTools.error_as_string(error)]) + return GdUnitResult.error("Can't load test runner configuration '%s'! ERROR: %s." % [path, error_string(error)]) var content := file.get_as_text() if not content.is_empty() and content[0] == '{': # Parse as json diff --git a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd index ed6dffaa..86f31088 100644 --- a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd +++ b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd @@ -26,10 +26,12 @@ var _simulate_start_time :LocalTime var _last_input_event :InputEvent = null var _mouse_button_on_press := [] var _key_on_press := [] +var _curent_mouse_position :Vector2 # time factor settings var _time_factor := 1.0 var _saved_iterations_per_second :float +var _scene_auto_free := false func _init(p_scene, p_verbose :bool, p_hide_push_errors = false): @@ -46,7 +48,8 @@ func _init(p_scene, p_verbose :bool, p_hide_push_errors = false): if not p_hide_push_errors: push_error("GdUnitSceneRunner: The given resource: '%s'. is not a scene." % p_scene) return - _current_scene = load(p_scene).instantiate() + _current_scene = load(p_scene).instantiate() + _scene_auto_free = true else: # verify we have a node instance if not p_scene is Node: @@ -77,8 +80,8 @@ func _notification(what): _reset_input_to_default() if is_instance_valid(_current_scene): _scene_tree.root.remove_child(_current_scene) - # don't free already memory managed instances - if not GdUnitMemoryObserver.is_marked_auto_free(_current_scene): + # do only free scenes instanciated by this runner + if _scene_auto_free: _current_scene.free() _scene_tree = null _current_scene = null @@ -148,18 +151,27 @@ func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner: return _handle_input_event(event) -func simulate_mouse_move_relative(relative :Vector2, speed :Vector2 = Vector2.ONE) -> GdUnitSceneRunner: - if _last_input_event is InputEventMouse: - var current_pos :Vector2 = _last_input_event.position - var final_pos := current_pos + relative - var delta_milli := speed.x * 0.1 - var t := 0.0 - while not current_pos.is_equal_approx(final_pos): - t += delta_milli * speed.x - simulate_mouse_move(current_pos) - await _scene_tree.create_timer(delta_milli).timeout - current_pos = current_pos.lerp(final_pos, t) - simulate_mouse_move(final_pos) +func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: + var tween := _scene_tree.create_tween() + _curent_mouse_position = get_mouse_position() + var final_position := _curent_mouse_position + relative + tween.tween_property(self, "_curent_mouse_position", final_position, time).set_trans(trans_type) + tween.play() + + while not get_mouse_position().is_equal_approx(final_position): + simulate_mouse_move(_curent_mouse_position) + await _scene_tree.process_frame + return self + + +func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner: + var tween := _scene_tree.create_tween() + _curent_mouse_position = get_mouse_position() + tween.tween_property(self, "_curent_mouse_position", position, time).set_trans(trans_type) + tween.play() + + while not get_mouse_position().is_equal_approx(position): + simulate_mouse_move(_curent_mouse_position) await _scene_tree.process_frame return self @@ -325,6 +337,20 @@ func _apply_input_mouse_position(event :InputEvent) -> void: event.position = _last_input_event.position +## just for testing maunally event to action handling +func _handle_actions(event :InputEvent) -> bool: + var is_action_match := false + for action in InputMap.get_actions(): + if InputMap.event_is_action(event, action, true): + is_action_match = true + prints(action, event, event.is_ctrl_pressed()) + if event.is_pressed(): + Input.action_press(action, InputMap.action_get_deadzone(action)) + else: + Input.action_release(action) + return is_action_match + + # for handling read https://docs.godotengine.org/en/stable/tutorials/inputs/inputevent.html?highlight=inputevent#how-does-it-work func _handle_input_event(event :InputEvent): if event is InputEventMouse: @@ -356,6 +382,7 @@ func _reset_input_to_default() -> void: simulate_key_release(key_scancode) _key_on_press.clear() Input.flush_buffered_events() + _last_input_event = null func __print(message :String) -> void: diff --git a/addons/gdUnit4/src/core/GdUnitSettings.gd b/addons/gdUnit4/src/core/GdUnitSettings.gd index d9466a12..f3e91f54 100644 --- a/addons/gdUnit4/src/core/GdUnitSettings.gd +++ b/addons/gdUnit4/src/core/GdUnitSettings.gd @@ -2,6 +2,7 @@ class_name GdUnitSettings extends RefCounted + const MAIN_CATEGORY = "gdunit4" # Common Settings const COMMON_SETTINGS = MAIN_CATEGORY + "/settings" @@ -86,7 +87,7 @@ enum NAMING_CONVENTIONS { } -static func setup(): +static func setup() -> void: create_property_if_need(UPDATE_NOTIFICATION_ENABLED, true, "Enables/Disables the update notification checked startup.") create_property_if_need(SERVER_TIMEOUT, DEFAULT_SERVER_TIMEOUT, "Sets the server connection timeout in minutes.") create_property_if_need(TEST_TIMEOUT, DEFAULT_TEST_TIMEOUT, "Sets the test case runtime timeout in seconds.") @@ -105,6 +106,7 @@ static func setup(): migrate_properties() + static func migrate_properties() -> void: var TEST_ROOT_FOLDER := "gdunit4/settings/test/test_root_folder" if get_property(TEST_ROOT_FOLDER) != null: @@ -232,7 +234,7 @@ static func list_settings(category :String) -> Array[GdUnitProperty]: for property in ProjectSettings.get_property_list(): var property_name :String = property["name"] if property_name.begins_with(category): - var value = ProjectSettings.get_setting(property_name) + var value :Variant = ProjectSettings.get_setting(property_name) var default :Variant = ProjectSettings.property_get_revert(property_name) var help :String = property["hint_string"] var value_set := extract_value_set_from_help(help) @@ -285,27 +287,27 @@ static func validate_lookup_folder(value :String) -> Variant: return null -static func save_property(name :String, value) -> void: +static func save_property(name :String, value :Variant) -> void: ProjectSettings.set_setting(name, value) _save_settings() static func _save_settings() -> void: - var err = ProjectSettings.save() + var err := ProjectSettings.save() if err != OK: push_error("Save GdUnit4 settings failed : %s" % error_string(err)) return static func has_property(name :String) -> bool: - return ProjectSettings.get_property_list().any( func(property): return property["name"] == name) + return ProjectSettings.get_property_list().any(func(property :Dictionary) -> bool: return property["name"] == name) static func get_property(name :String) -> GdUnitProperty: for property in ProjectSettings.get_property_list(): - var property_name = property["name"] + var property_name :String = property["name"] if property_name == name: - var value = ProjectSettings.get_setting(property_name) + var value :Variant = ProjectSettings.get_setting(property_name) var default :Variant = ProjectSettings.property_get_revert(property_name) var help :String = property["hint_string"] var value_set := extract_value_set_from_help(help) @@ -318,7 +320,7 @@ static func migrate_property(old_property :String, new_property :String, default if property == null: prints("Migration not possible, property '%s' not found" % old_property) return - var value = converter.call(property.value()) if converter.is_valid() else property.value() + var value :Variant = converter.call(property.value()) if converter.is_valid() else property.value() ProjectSettings.set_setting(new_property, value) ProjectSettings.set_initial_value(new_property, default_value) set_help(new_property, value, help) diff --git a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd b/addons/gdUnit4/src/core/GdUnitSignalCollector.gd index cf55adcf..f797e15c 100644 --- a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd +++ b/addons/gdUnit4/src/core/GdUnitSignalCollector.gd @@ -63,17 +63,18 @@ func _on_signal_emmited( arg0=NO_ARG, arg1=NO_ARG, arg2=NO_ARG, arg3=NO_ARG, arg # extract the emitter and signal_name from the last two arguments (see line 61 where is added) var signal_name :String = signal_args.pop_back() var emitter :Object = signal_args.pop_back() - # prints("_on_signal_emmited:", emitter, signal_name, signal_args) + #prints("_on_signal_emmited:", emitter, signal_name, signal_args) if is_signal_collecting(emitter, signal_name): _collected_signals[emitter][signal_name].append(signal_args) -func reset_received_signals(emitter :Object): - # _debug_signal_list("before claer"); +func reset_received_signals(emitter :Object, signal_name: String, signal_args :Array): + #_debug_signal_list("before claer"); if _collected_signals.has(emitter): - for signal_name in _collected_signals[emitter]: - _collected_signals[emitter][signal_name].clear() - # _debug_signal_list("after claer"); + var signals_by_emitter = _collected_signals[emitter] + if signals_by_emitter.has(signal_name): + _collected_signals[emitter][signal_name].erase(signal_args) + #_debug_signal_list("after claer"); func is_signal_collecting(emitter :Object, signal_name :String) -> bool: @@ -85,7 +86,7 @@ func match(emitter :Object, signal_name :String, args :Array) -> bool: if _collected_signals.is_empty() or not _collected_signals.has(emitter): return false for received_args in _collected_signals[emitter][signal_name]: - # prints("testing", signal_name, received_args, "vs", args) + #prints("testing", signal_name, received_args, "vs", args) if GdObjects.equals(received_args, args): return true return false diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd index 7cd5dfdd..5e49c41c 100644 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd +++ b/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd @@ -8,7 +8,7 @@ static func create(source :Script, line_number :int) -> GdUnitResult: ScriptEditorControls.save_an_open_script(source.resource_path) ScriptEditorControls.save_an_open_script(test_suite_path, true) if GdObjects.is_cs_script(source): - return GdUnit4MonoApiLoader.create_test_suite(source.resource_path, line_number+1, test_suite_path) + return GdUnit4CSharpApiLoader.create_test_suite(source.resource_path, line_number+1, test_suite_path) var parser := GdScriptParser.new() var lines := source.source_code.split("\n") var current_line := lines[line_number] diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd index ff7ab6df..75945587 100644 --- a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd +++ b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd @@ -79,8 +79,8 @@ static func _file(dir :DirAccess, file_name :String) -> String: func _parse_is_test_suite(resource_path :String) -> Node: if not GdUnitTestSuiteScanner._is_script_format_supported(resource_path): return null - if GdUnit4MonoApiLoader.is_test_suite(resource_path): - return GdUnit4MonoApiLoader.parse_test_suite(resource_path) + if GdUnit4CSharpApiLoader.is_test_suite(resource_path): + return GdUnit4CSharpApiLoader.parse_test_suite(resource_path) var script :Script = ResourceLoader.load(resource_path) if not GdObjects.is_test_suite(script): return null @@ -93,7 +93,7 @@ static func _is_script_format_supported(resource_path :String) -> bool: var ext := resource_path.get_extension() if ext == "gd": return true - return GdUnit4MonoApiLoader.is_csharp_file(resource_path) + return GdUnit4CSharpApiLoader.is_csharp_file(resource_path) func _parse_test_suite(script :GDScript) -> GdUnitTestSuite: diff --git a/addons/gdUnit4/src/core/GdUnitTools.gd b/addons/gdUnit4/src/core/GdUnitTools.gd index 1e4f660a..d577c2b0 100644 --- a/addons/gdUnit4/src/core/GdUnitTools.gd +++ b/addons/gdUnit4/src/core/GdUnitTools.gd @@ -1,185 +1,5 @@ extends RefCounted -const GDUNIT_TEMP := "user://tmp" - - -static func temp_dir() -> String: - if not DirAccess.dir_exists_absolute(GDUNIT_TEMP): - DirAccess.make_dir_recursive_absolute(GDUNIT_TEMP) - return GDUNIT_TEMP - - -static func create_temp_dir(folder_name :String) -> String: - var new_folder = temp_dir() + "/" + folder_name - if not DirAccess.dir_exists_absolute(new_folder): - DirAccess.make_dir_recursive_absolute(new_folder) - return new_folder - - -static func clear_tmp(): - delete_directory(GDUNIT_TEMP) - - -# Creates a new file under -static func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess: - var file_path := create_temp_dir(relative_path) + "/" + file_name - var file = FileAccess.open(file_path, mode) - if file == null: - push_error("Error creating temporary file at: %s, %s" % [file_path, error_as_string(FileAccess.get_open_error())]) - return file - - -static func current_dir() -> String: - return ProjectSettings.globalize_path("res://") - - -static func delete_directory(path :String, only_content := false) -> void: - var dir := DirAccess.open(path) - if dir != null: - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var file_name := "." - while file_name != "": - file_name = dir.get_next() - if file_name.is_empty() or file_name == "." or file_name == "..": - continue - var next := path + "/" +file_name - if dir.current_is_dir(): - delete_directory(next) - else: - # delete file - var err = dir.remove(next) - if err: - push_error("Delete %s failed: %s" % [next, error_as_string(err)]) - if not only_content: - var err := dir.remove(path) - if err: - push_error("Delete %s failed: %s" % [path, error_as_string(err)]) - - -static func copy_file(from_file :String, to_dir :String) -> GdUnitResult: - var dir := DirAccess.open(to_dir) - if dir != null: - var to_file := to_dir + "/" + from_file.get_file() - prints("Copy %s to %s" % [from_file, to_file]) - var error = dir.copy(from_file, to_file) - if error != OK: - return GdUnitResult.error("Can't copy file form '%s' to '%s'. Error: '%s'" % [from_file, to_file, error_as_string(error)]) - return GdUnitResult.success(to_file) - return GdUnitResult.error("Directory not found: " + to_dir) - - -static func copy_directory(from_dir :String, to_dir :String, recursive :bool = false) -> bool: - if not DirAccess.dir_exists_absolute(from_dir): - push_error("Source directory not found '%s'" % from_dir) - return false - - # check if destination exists - if not DirAccess.dir_exists_absolute(to_dir): - # create it - var err := DirAccess.make_dir_recursive_absolute(to_dir) - if err != OK: - push_error("Can't create directory '%s'. Error: %s" % [to_dir, error_as_string(err)]) - return false - var source_dir := DirAccess.open(from_dir) - var dest_dir := DirAccess.open(to_dir) - if source_dir != null: - source_dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - - while next != "": - next = source_dir.get_next() - if next == "" or next == "." or next == "..": - continue - var source := source_dir.get_current_dir() + "/" + next - var dest := dest_dir.get_current_dir() + "/" + next - if source_dir.current_is_dir(): - if recursive: - copy_directory(source + "/", dest, recursive) - continue - var err = source_dir.copy(source, dest) - if err != OK: - push_error("Error checked copy file '%s' to '%s'" % [source, dest]) - return false - - return true - else: - push_error("Directory not found: " + from_dir) - return false - - -# scans given path for sub directories by given prefix and returns the highest index numer -# e.g. -static func find_last_path_index(path :String, prefix :String) -> int: - var dir := DirAccess.open(path) - if dir == null: - return 0 - var last_iteration := 0 - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - while next != "": - next = dir.get_next() - if next.is_empty() or next == "." or next == "..": - continue - if next.begins_with(prefix): - var iteration := next.split("_")[1].to_int() - if iteration > last_iteration: - last_iteration = iteration - return last_iteration - - -static func delete_path_index_lower_equals_than(path :String, prefix :String, index :int) -> int: - var dir := DirAccess.open(path) - if dir == null: - return 0 - var deleted := 0 - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - while next != "": - next = dir.get_next() - if next.is_empty() or next == "." or next == "..": - continue - if next.begins_with(prefix): - var current_index := next.split("_")[1].to_int() - if current_index <= index: - deleted += 1 - delete_directory(path + "/" + next) - return deleted - - -static func scan_dir(path :String) -> PackedStringArray: - var dir := DirAccess.open(path) - if dir == null or not dir.dir_exists(path): - return PackedStringArray() - var content := PackedStringArray() - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var next := "." - while next != "": - next = dir.get_next() - if next.is_empty() or next == "." or next == "..": - continue - content.append(next) - return content - - -static func resource_as_array(resource_path :String) -> PackedStringArray: - var file := FileAccess.open(resource_path, FileAccess.READ) - if file == null: - push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_as_string(FileAccess.get_open_error())]) - return PackedStringArray() - var file_content := PackedStringArray() - while not file.eof_reached(): - file_content.append(file.get_line()) - return file_content - - -static func resource_as_string(resource_path :String) -> String: - var file := FileAccess.open(resource_path, FileAccess.READ) - if file == null: - push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_as_string(FileAccess.get_open_error())]) - return "" - return file.get_as_text(true) - - static func normalize_text(text :String) -> String: return text.replace("\r", ""); @@ -221,6 +41,7 @@ static func free_instance(instance :Variant, is_stdout_verbose :=false) -> bool: if instance is RefCounted: instance.notification(Object.NOTIFICATION_PREDELETE) await Engine.get_main_loop().process_frame + await Engine.get_main_loop().physics_frame return true else: # is instance already freed? @@ -241,7 +62,7 @@ static func free_instance(instance :Variant, is_stdout_verbose :=false) -> bool: return !is_instance_valid(instance) -static func _release_connections(instance :Object): +static func _release_connections(instance :Object) -> void: if is_instance_valid(instance): # disconnect from all connected signals to force freeing, otherwise it ends up in orphans for connection in instance.get_incoming_connections(): @@ -256,7 +77,7 @@ static func _release_connections(instance :Object): release_timers() -static func release_timers(): +static func release_timers() -> void: # we go the new way to hold all gdunit timers in group 'GdUnitTimers' for node in Engine.get_main_loop().root.get_children(): if is_instance_valid(node) and node.is_in_group("GdUnitTimers"): @@ -267,7 +88,7 @@ static func release_timers(): # the finally cleaup unfreed resources and singletons -static func dispose_all(): +static func dispose_all() -> void: release_timers() GdUnitSignals.dispose() GdUnitSingleton.dispose() @@ -279,46 +100,12 @@ static func release_double(instance :Object) -> void: instance.call("__release_double") -static func make_qualified_path(path :String) -> String: - if not path.begins_with("res://"): - if path.begins_with("//"): - return path.replace("//", "res://") - if path.begins_with("/"): - return "res:/" + path - return path - - -static func error_as_string(error_number :int) -> String: - return error_string(error_number) - - static func clear_push_errors() -> void: - var runner = Engine.get_meta("GdUnitRunner") + var runner :Node = Engine.get_meta("GdUnitRunner") if runner != null: runner.clear_push_errors() static func register_expect_interupted_by_timeout(test_suite :Node, test_case_name :String) -> void: - var test_case = test_suite.find_child(test_case_name, false, false) + var test_case :Node = test_suite.find_child(test_case_name, false, false) test_case.expect_to_interupt() - - -static func extract_zip(zip_package :String, dest_path :String) -> GdUnitResult: - var zip: ZIPReader = ZIPReader.new() - var err := zip.open(zip_package) - if err != OK: - return GdUnitResult.error("Extracting `%s` failed! Please collect the error log and report this. Error Code: %s" % [zip_package, err]) - var zip_entries: PackedStringArray = zip.get_files() - # Get base path and step over archive folder - var archive_path = zip_entries[0] - zip_entries.remove_at(0) - - for zip_entry in zip_entries: - var new_file_path: String = dest_path + "/" + zip_entry.replace(archive_path, "") - if zip_entry.ends_with("/"): - DirAccess.make_dir_recursive_absolute(new_file_path) - continue - var file: FileAccess = FileAccess.open(new_file_path, FileAccess.WRITE) - file.store_buffer(zip.read_file(zip_entry)) - zip.close() - return GdUnitResult.success(dest_path) diff --git a/addons/gdUnit4/src/core/GodotVersionFixures.gd b/addons/gdUnit4/src/core/GodotVersionFixures.gd index d0ce4e14..2c331d5f 100644 --- a/addons/gdUnit4/src/core/GodotVersionFixures.gd +++ b/addons/gdUnit4/src/core/GodotVersionFixures.gd @@ -9,3 +9,13 @@ static func get_icon(control :Control, icon_name :String) -> Texture2D: if Engine.get_version_info().hex >= 040200: return control.get_theme_icon(icon_name, "EditorIcons") return control.theme.get_icon(icon_name, "EditorIcons") + + +@warning_ignore("shadowed_global_identifier") +static func type_convert(value: Variant, type: int): + return convert(value, type) + + +@warning_ignore("shadowed_global_identifier") +static func convert(value: Variant, type: int) -> Variant: + return type_convert(value, type) diff --git a/addons/gdUnit4/src/core/_TestCase.gd b/addons/gdUnit4/src/core/_TestCase.gd index 47ea60ff..042f3077 100644 --- a/addons/gdUnit4/src/core/_TestCase.gd +++ b/addons/gdUnit4/src/core/_TestCase.gd @@ -26,15 +26,6 @@ var _failed := false var _report :GdUnitReport = null -var monitor : GodotGdErrorMonitor = null: - set (value): - monitor = value - get: - if monitor == null: - monitor = GodotGdErrorMonitor.new() - return monitor - - var timeout : int = DEFAULT_TIMEOUT: set (value): timeout = value @@ -62,31 +53,19 @@ func execute(p_test_parameter := Array(), p_iteration := 0): if _current_iteration == -1: _set_failure_handler() set_timeout() - monitor.start() if not p_test_parameter.is_empty(): update_fuzzers(p_test_parameter, p_iteration) _execute_test_case(name, p_test_parameter) else: _execute_test_case(name, []) await completed - monitor.stop() - for report_ in monitor.reports(): - if report_.is_error(): - _report = report_ - _interupted = true func execute_paramaterized(p_test_parameter :Array): _failure_received(false) set_timeout() - monitor.start() _execute_test_case(name, p_test_parameter) await completed - monitor.stop() - for report_ in monitor.reports(): - if report_.is_error(): - _report = report_ - _interupted = true var _is_disposed := false diff --git a/addons/gdUnit4/src/core/event/GdUnitEvent.gd b/addons/gdUnit4/src/core/event/GdUnitEvent.gd index dbfcc326..cd922088 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEvent.gd +++ b/addons/gdUnit4/src/core/event/GdUnitEvent.gd @@ -26,10 +26,10 @@ var _suite_name :String var _test_name :String var _total_count :int = 0 var _statistics := Dictionary() -var _reports := Array() +var _reports :Array[GdUnitReport] = [] -func suite_before(p_resource_path :String, p_suite_name :String, p_total_count) -> GdUnitEvent: +func suite_before(p_resource_path :String, p_suite_name :String, p_total_count :int) -> GdUnitEvent: _event_type = TESTSUITE_BEFORE _resource_path = p_resource_path _suite_name = p_suite_name @@ -38,7 +38,7 @@ func suite_before(p_resource_path :String, p_suite_name :String, p_total_count) return self -func suite_after(p_resource_path :String, p_suite_name :String, p_statistics :Dictionary = {}, p_reports :Array = []) -> GdUnitEvent: +func suite_after(p_resource_path :String, p_suite_name :String, p_statistics :Dictionary = {}, p_reports :Array[GdUnitReport] = []) -> GdUnitEvent: _event_type = TESTSUITE_AFTER _resource_path = p_resource_path _suite_name = p_suite_name @@ -56,7 +56,7 @@ func test_before(p_resource_path :String, p_suite_name :String, p_test_name :Str return self -func test_after(p_resource_path :String, p_suite_name :String, p_test_name :String, p_statistics :Dictionary = {}, p_reports :Array = []) -> GdUnitEvent: +func test_after(p_resource_path :String, p_suite_name :String, p_test_name :String, p_statistics :Dictionary = {}, p_reports :Array[GdUnitReport] = []) -> GdUnitEvent: _event_type = TESTCASE_AFTER _resource_path = p_resource_path _suite_name = p_suite_name @@ -138,7 +138,7 @@ func reports() -> Array: return _reports -func _to_string(): +func _to_string() -> String: return "Event: %s %s:%s, %s, %s" % [_event_type, _suite_name, _test_name, _statistics, _reports] @@ -161,20 +161,24 @@ func deserialize(serialized :Dictionary) -> GdUnitEvent: _suite_name = serialized.get("suite_name", null) _test_name = serialized.get("test_name", "unknown") _total_count = serialized.get("total_count", 0) - _statistics = serialized.get("statistics", Dictionary()) - _reports = _deserialize_reports(serialized.get("reports",[])) + _statistics = serialized.get("statistics", Dictionary()) + if serialized.has("reports"): + # needs this workaround to copy typed values in the array + var reports :Array[Dictionary] = [] + reports.append_array(serialized.get("reports")) + _reports = _deserialize_reports(reports) return self -func _serialize_TestReports() -> Array: - var serialized_reports := Array() +func _serialize_TestReports() -> Array[Dictionary]: + var serialized_reports :Array[Dictionary] = [] for report in _reports: serialized_reports.append(report.serialize()) return serialized_reports -func _deserialize_reports(p_reports :Array) -> Array: - var deserialized_reports := Array() +func _deserialize_reports(p_reports :Array[Dictionary]) -> Array[GdUnitReport]: + var deserialized_reports :Array[GdUnitReport] = [] for report in p_reports: var test_report := GdUnitReport.new().deserialize(report) deserialized_reports.append(test_report) diff --git a/addons/gdUnit4/src/core/event/GdUnitEventInit.gd b/addons/gdUnit4/src/core/event/GdUnitEventInit.gd index eeb88dce..8bb1d496 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEventInit.gd +++ b/addons/gdUnit4/src/core/event/GdUnitEventInit.gd @@ -4,7 +4,8 @@ extends GdUnitEvent var _total_testsuites :int -func _init(p_total_testsuites :int, p_total_count :int): + +func _init(p_total_testsuites :int, p_total_count :int) -> void: _event_type = INIT _total_testsuites = p_total_testsuites _total_count = p_total_count diff --git a/addons/gdUnit4/src/core/event/GdUnitEventStop.gd b/addons/gdUnit4/src/core/event/GdUnitEventStop.gd index faa6a186..d7a3c11c 100644 --- a/addons/gdUnit4/src/core/event/GdUnitEventStop.gd +++ b/addons/gdUnit4/src/core/event/GdUnitEventStop.gd @@ -2,6 +2,5 @@ class_name GdUnitStop extends GdUnitEvent -func _init(): +func _init() -> void: _event_type = STOP - diff --git a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd b/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd index b55c125f..16117615 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd +++ b/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd @@ -12,6 +12,15 @@ var _test_case_name: StringName var _name :String +var error_monitor : GodotGdErrorMonitor = null: + set (value): + error_monitor = value + get: + if _parent_context != null: + return _parent_context.error_monitor + return error_monitor + + var test_suite : GdUnitTestSuite = null: set (value): test_suite = value @@ -35,6 +44,7 @@ func _init(name :String, parent_context :GdUnitExecutionContext = null) -> void: _orphan_monitor = GdUnitOrphanNodesMonitor.new(name) _orphan_monitor.start() _memory_observer = GdUnitMemoryObserver.new() + error_monitor = GodotGdErrorMonitor.new() _report_collector = GdUnitTestReportCollector.new(get_instance_id()) if parent_context != null: parent_context._sub_context.append(self) @@ -84,6 +94,17 @@ func test_failed() -> bool: return has_failures() or has_errors() +func error_monitor_start() -> void: + error_monitor.start() + + +func error_monitor_stop() -> void: + await error_monitor.scan() + for error_report in error_monitor.to_reports(): + if error_report.is_error(): + _report_collector._reports.append(error_report) + + func orphan_monitor_start() -> void: _orphan_monitor.start() diff --git a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd index 47c02fb8..3392bde0 100644 --- a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd +++ b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd @@ -8,12 +8,12 @@ var _assertions := GdUnitAssertions.new() var _executeStage :IGdUnitExecutionStage = GdUnitTestSuiteExecutionStage.new() -func _init(debug_mode :bool = false): +func _init(debug_mode :bool = false) -> void: _executeStage.set_debug_mode(debug_mode) func execute(test_suite :GdUnitTestSuite) -> void: - var orphan_detection_enabled = GdUnitSettings.is_verbose_orphans() + var orphan_detection_enabled := GdUnitSettings.is_verbose_orphans() if not orphan_detection_enabled: prints("!!! Reporting orphan nodes is disabled. Please check GdUnit settings.") diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd index d8db2c44..d0c8de13 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd @@ -14,14 +14,13 @@ func _init(call_stage := true): func _execute(context :GdUnitExecutionContext) -> void: var test_suite := context.test_suite - if _call_stage: @warning_ignore("redundant_await") await test_suite.after_test() # unreference last used assert form the test to prevent memory leaks GdUnitThreadManager.get_current_context().set_assert(null) await context.gc() - + await context.error_monitor_stop() if context.test_case.is_skipped(): fire_test_skipped(context) else: diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd index 0abc581f..6a1a54d8 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd @@ -22,6 +22,7 @@ func _execute(context :GdUnitExecutionContext) -> void: if _call_stage: @warning_ignore("redundant_await") await test_suite.before_test() + context.error_monitor_start() func set_test_name(test_name :StringName): diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd index dab18da1..5606f39f 100644 --- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd +++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd @@ -23,6 +23,6 @@ func _execute(context :GdUnitExecutionContext) -> void: .create(GdUnitReport.WARN, 1, GdAssertMessages.orphan_detected_on_suite_setup(orphans))) fire_event(GdUnitEvent.new().suite_after(test_suite.get_script().resource_path, test_suite.get_name(), context.build_report_statistics(orphans, false), reports)) - GdUnitTools.clear_tmp() + GdUnitFileAccess.clear_tmp() # Guard that checks if all doubled (spy/mock) objects are released GdUnitClassDoubler.check_leaked_instances() diff --git a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd index 08f70079..55de0b69 100644 --- a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd +++ b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd @@ -20,7 +20,7 @@ func name() -> String: func default() -> Variant: - return convert(_default_value, _type) + return GodotVersionFixures.convert(_default_value, _type) func value_as_string() -> String: diff --git a/addons/gdUnit4/src/core/report/GdUnitReport.gd b/addons/gdUnit4/src/core/report/GdUnitReport.gd index 90b260a8..eb7ed2e5 100644 --- a/addons/gdUnit4/src/core/report/GdUnitReport.gd +++ b/addons/gdUnit4/src/core/report/GdUnitReport.gd @@ -18,7 +18,7 @@ var _line_number :int var _message :String -func create(p_type, p_line_number :int, p_message :String) -> GdUnitReport: +func create(p_type :int, p_line_number :int, p_message :String) -> GdUnitReport: _type = p_type _line_number = p_line_number _message = p_message @@ -53,7 +53,7 @@ func is_error() -> bool: return _type == TERMINATED or _type == INTERUPTED or _type == ABORT -func _to_string(): +func _to_string() -> String: if _line_number == -1: return "[color=green]line [/color][color=aqua]:[/color] %s" % [_message] return "[color=green]line [/color][color=aqua]%d:[/color] %s" % [_line_number, _message] @@ -67,7 +67,7 @@ func serialize() -> Dictionary: } -func deserialize(serialized:Dictionary) -> GdUnitReport: +func deserialize(serialized :Dictionary) -> GdUnitReport: _type = serialized["type"] _line_number = serialized["line_number"] _message = serialized["message"] diff --git a/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd b/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd index 6eca57a6..8544c403 100644 --- a/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/AnyArgumentMatcher.gd @@ -5,3 +5,7 @@ extends GdUnitArgumentMatcher @warning_ignore("unused_parameter") func is_match(value) -> bool: return true + + +func _to_string() -> String: + return "any()" diff --git a/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd b/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd index 3c4026a2..04faae92 100644 --- a/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/AnyBuildInTypeArgumentMatcher.gd @@ -3,8 +3,48 @@ extends GdUnitArgumentMatcher var _type : PackedInt32Array = [] + func _init(type :PackedInt32Array): _type = type + func is_match(value) -> bool: return _type.has(typeof(value)) + + +func _to_string() -> String: + match _type[0]: + TYPE_BOOL: return "any_bool()" + TYPE_STRING, TYPE_STRING_NAME: return "any_string()" + TYPE_INT: return "any_int()" + TYPE_FLOAT: return "any_float()" + TYPE_COLOR: return "any_color()" + TYPE_VECTOR2: return "any_vector2()" if _type.size() == 1 else "any_vector()" + TYPE_VECTOR2I: return "any_vector2i()" + TYPE_VECTOR3: return "any_vector3()" + TYPE_VECTOR3I: return "any_vector3i()" + TYPE_VECTOR4: return "any_vector4()" + TYPE_VECTOR4I: return "any_vector4i()" + TYPE_RECT2: return "any_rect2()" + TYPE_RECT2I: return "any_rect2i()" + TYPE_PLANE: return "any_plane()" + TYPE_QUATERNION: return "any_quat()" + TYPE_AABB: return "any_aabb()" + TYPE_BASIS: return "any_basis()" + TYPE_TRANSFORM2D: return "any_transform_2d()" + TYPE_TRANSFORM3D: return "any_transform_3d()" + TYPE_NODE_PATH: return "any_node_path()" + TYPE_RID: return "any_rid()" + TYPE_OBJECT: return "any_object()" + TYPE_DICTIONARY: return "any_dictionary()" + TYPE_ARRAY: return "any_array()" + TYPE_PACKED_BYTE_ARRAY: return "any_packed_byte_array()" + TYPE_PACKED_INT32_ARRAY: return "any_packed_int32_array()" + TYPE_PACKED_INT64_ARRAY: return "any_packed_int64_array()" + TYPE_PACKED_FLOAT32_ARRAY: return "any_packed_float32_array()" + TYPE_PACKED_FLOAT64_ARRAY: return "any_packed_float64_array()" + TYPE_PACKED_STRING_ARRAY: return "any_packed_string_array()" + TYPE_PACKED_VECTOR2_ARRAY: return "any_packed_vector2_array()" + TYPE_PACKED_VECTOR3_ARRAY: return "any_packed_vector3_array()" + TYPE_PACKED_COLOR_ARRAY: return "any_packed_color_array()" + _: return "any()" diff --git a/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd b/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd index f553bfe0..ec148fde 100644 --- a/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd @@ -3,7 +3,7 @@ extends GdUnitArgumentMatcher var _clazz :Object -func _init(clazz :Object): +func _init(clazz :Object) -> void: _clazz = clazz func is_match(value :Variant) -> bool: @@ -12,3 +12,17 @@ func is_match(value :Variant) -> bool: if is_instance_valid(value) and GdObjects.is_script(_clazz): return value.get_script() == _clazz return is_instance_of(value, _clazz) + + +func _to_string() -> String: + if (_clazz as Object).is_class("GDScriptNativeClass"): + var instance :Object = _clazz.new() + var clazz_name := instance.get_class() + if not instance is RefCounted: + instance.free() + return "any_class(<"+clazz_name+">)"; + if _clazz is GDScript: + var result := GdObjects.extract_class_name(_clazz) + if result.is_success(): + return "any_class(<"+ result.value() + ">)" + return "any_class()" diff --git a/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd b/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd index e42de50d..cb0e4323 100644 --- a/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd @@ -3,15 +3,18 @@ extends GdUnitArgumentMatcher var _matchers :Array + func _init(matchers :Array): _matchers = matchers -func is_match(arguments :Array) -> bool: - if arguments.size() != _matchers.size(): + +func is_match(arguments :Variant) -> bool: + var arg_array := arguments as Array + if arg_array.size() != _matchers.size(): return false - for index in arguments.size(): - var arg = arguments[index] + for index in arg_array.size(): + var arg :Variant = arg_array[index] var matcher = _matchers[index] as GdUnitArgumentMatcher if not matcher.is_match(arg): diff --git a/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd b/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd index dff6dac2..aa43b80f 100644 --- a/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd +++ b/addons/gdUnit4/src/matchers/GdUnitArgumentMatcher.gd @@ -4,5 +4,5 @@ extends RefCounted @warning_ignore("unused_parameter") -func is_match(value) -> bool: +func is_match(value :Variant) -> bool: return true diff --git a/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd b/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd index 24e6e855..ddd58f11 100644 --- a/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd +++ b/addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd @@ -4,8 +4,8 @@ extends RefCounted const TYPE_ANY = TYPE_MAX + 100 -static func to_matcher(arguments :Array, auto_deep_check_mode := false) -> ChainedArgumentMatcher: - var matchers := Array() +static func to_matcher(arguments :Array[Variant], auto_deep_check_mode := false) -> ChainedArgumentMatcher: + var matchers :Array[Variant] = [] for arg in arguments: # argument is already a matcher if arg is GdUnitArgumentMatcher: @@ -28,5 +28,5 @@ static func by_types(types :PackedInt32Array) -> GdUnitArgumentMatcher: return AnyBuildInTypeArgumentMatcher.new(types) -static func any_class(clazz) -> GdUnitArgumentMatcher: +static func any_class(clazz :Object) -> GdUnitArgumentMatcher: return AnyClazzArgumentMatcher.new(clazz) diff --git a/addons/gdUnit4/src/mocking/GdUnitMock.gd b/addons/gdUnit4/src/mocking/GdUnitMock.gd index ba993226..07a37bee 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMock.gd +++ b/addons/gdUnit4/src/mocking/GdUnitMock.gd @@ -9,10 +9,10 @@ const RETURN_DEFAULTS = "RETURN_DEFAULTS" ## builds full deep mocked object const RETURN_DEEP_STUB = "RETURN_DEEP_STUB" -var _value +var _value :Variant -func _init(value): +func _init(value :Variant) -> void: _value = value @@ -28,7 +28,7 @@ func on(obj :Object) -> Object: ## [color=yellow]`checked` is obsolete, use `on` instead [/color] -func checked(obj :Object) -> Object: +func checked(obj :Object) -> Object: push_warning("Using a deprecated function 'checked' use `on` instead") return on(obj) diff --git a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd b/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd index df7f02c3..1fc378d3 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd +++ b/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd @@ -96,7 +96,7 @@ static func mock_on_script(instance :Object, clazz :Variant, function_excludes : var mock := GDScript.new() mock.source_code = "\n".join(lines) mock.resource_name = "Mock%s.gd" % clazz_name - mock.resource_path = GdUnitTools.create_temp_dir("mock") + "/Mock%s_%d.gd" % [clazz_name, Time.get_ticks_msec()] + mock.resource_path = GdUnitFileAccess.create_temp_dir("mock") + "/Mock%s_%d.gd" % [clazz_name, Time.get_ticks_msec()] if debug_write: DirAccess.remove_absolute(mock.resource_path) diff --git a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd b/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd index 94110c28..aa0d6646 100644 --- a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd +++ b/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd @@ -7,7 +7,7 @@ const __SOURCE_CLASS = "${source_class}" var __working_mode := GdUnitMock.RETURN_DEFAULTS var __excluded_methods :PackedStringArray = [] -var __do_return_value = null +var __do_return_value :Variant = null var __prepare_return_value := false #{ = { @@ -17,11 +17,11 @@ var __prepare_return_value := false var __mocked_return_values := Dictionary() -static func __instance(): +static func __instance() -> Object: return Engine.get_meta(__INSTANCE_ID) -func _notification(what): +func _notification(what :int) -> void: if what == NOTIFICATION_PREDELETE: if Engine.has_meta(__INSTANCE_ID): Engine.remove_meta(__INSTANCE_ID) @@ -31,12 +31,12 @@ func __instance_id() -> String: return __INSTANCE_ID -func __set_singleton(): +func __set_singleton() -> void: # store self need to mock static functions Engine.set_meta(__INSTANCE_ID, self) -func __release_double(): +func __release_double() -> void: # we need to release the self reference manually to prevent orphan nodes Engine.remove_meta(__INSTANCE_ID) @@ -46,7 +46,8 @@ func __is_prepare_return_value() -> bool: func __sort_by_argument_matcher(left_args :Array, _right_args :Array) -> bool: - for larg in left_args: + for index in left_args.size(): + var larg :Variant = left_args[index] if larg is GdUnitArgumentMatcher: return false return true @@ -60,12 +61,13 @@ func __sort_dictionary(unsorted_args :Dictionary) -> Dictionary: var sorted_args := unsorted_args.keys() sorted_args.sort_custom(__sort_by_argument_matcher) var sorted_result := {} - for key in sorted_args: + for index in sorted_args.size(): + var key :Variant = sorted_args[index] sorted_result[key] = unsorted_args[key] return sorted_result -func __save_function_return_value(args :Array): +func __save_function_return_value(args :Array) -> void: var func_name :String = args[0] var func_args :Array = args.slice(1) var mocked_return_value_by_args :Dictionary = __mocked_return_values.get(func_name, {}) @@ -77,13 +79,14 @@ func __save_function_return_value(args :Array): func __is_mocked_args_match(func_args :Array, mocked_args :Array) -> bool: var is_matching := false - for args in mocked_args: + for index in mocked_args.size(): + var args :Variant = mocked_args[index] if func_args.size() != args.size(): continue is_matching = true for arg_index in func_args.size(): - var func_arg = func_args[arg_index] - var mock_arg = args[arg_index] + var func_arg :Variant = func_args[arg_index] + var mock_arg :Variant = args[arg_index] if mock_arg is GdUnitArgumentMatcher: is_matching = is_matching and mock_arg.is_match(func_arg) else: @@ -101,7 +104,8 @@ func __get_mocked_return_value_or_default(args :Array, default_return_value :Var return default_return_value var func_args :Array = args.slice(1) var mocked_args :Array = __mocked_return_values.get(func_name).keys() - for margs in mocked_args: + for index in mocked_args.size(): + var margs :Variant = mocked_args[index] if __is_mocked_args_match(func_args, [margs]): return __mocked_return_values[func_name][margs] return default_return_value @@ -111,7 +115,7 @@ func __set_script(script :GDScript) -> void: super.set_script(script) -func __set_mode(working_mode :String): +func __set_mode(working_mode :String) -> Object: __working_mode = working_mode return self @@ -130,7 +134,7 @@ func __exclude_method_call(exluded_methods :PackedStringArray) -> void: __excluded_methods.append_array(exluded_methods) -func __do_return(return_value): +func __do_return(return_value :Variant) -> Object: __do_return_value = return_value __prepare_return_value = true return self diff --git a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd index 47250b65..967dc7e2 100644 --- a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd +++ b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd @@ -4,32 +4,30 @@ extends GdUnitMonitor var _godot_log_file :String var _eof :int var _report_enabled := false -var _report_force : bool +var _entries: Array[ErrorLogEntry] = [] -func _init(force := false): +func _init(): super("GodotGdErrorMonitor") - _report_force = force _godot_log_file = GdUnitSettings.get_log_path() + _report_enabled = _is_reporting_enabled() func start(): - _report_enabled = _report_force or is_reporting_enabled() - if _report_enabled: - var file = FileAccess.open(_godot_log_file, FileAccess.READ) - if file: - file.seek_end(0) - _eof = file.get_length() + var file = FileAccess.open(_godot_log_file, FileAccess.READ) + if file: + file.seek_end(0) + _eof = file.get_length() func stop(): pass -func reports() -> Array[GdUnitReport]: +func to_reports() -> Array[GdUnitReport]: var reports_ :Array[GdUnitReport] = [] if _report_enabled: - reports_.assign(_collect_log_entries().map(_to_report)) + reports_.assign(_entries.map(_to_report)) return reports_ @@ -42,35 +40,45 @@ static func _to_report(errorLog :ErrorLogEntry) -> GdUnitReport: return GdUnitReport.new().create(GdUnitReport.ABORT, errorLog._line, failure) -func scan() -> Array[ErrorLogEntry]: +func scan(force_collect_reports := false) -> Array[ErrorLogEntry]: await Engine.get_main_loop().process_frame - return _collect_log_entries() + await Engine.get_main_loop().physics_frame + _entries.append_array(_collect_log_entries(force_collect_reports)) + return _entries + + +func erase_log_entry(entry :ErrorLogEntry) -> void: + _entries.erase(entry) -func _collect_log_entries() -> Array[ErrorLogEntry]: +func _collect_log_entries(force_collect_reports :bool) -> Array[ErrorLogEntry]: var file = FileAccess.open(_godot_log_file, FileAccess.READ) file.seek(_eof) var records := PackedStringArray() while not file.eof_reached(): records.append(file.get_line()) + file.seek_end(0) + _eof = file.get_length() var log_entries :Array[ErrorLogEntry]= [] + var is_report_errors := force_collect_reports or _is_report_push_errors() + var is_report_script_errors := force_collect_reports or _is_report_script_errors() for index in records.size(): - if _report_force: + if force_collect_reports: log_entries.append(ErrorLogEntry.extract_push_warning(records, index)) - if _is_report_push_errors(): + if is_report_errors: log_entries.append(ErrorLogEntry.extract_push_error(records, index)) - if _is_report_script_errors(): + if is_report_script_errors: log_entries.append(ErrorLogEntry.extract_error(records, index)) return log_entries.filter(func(value): return value != null ) -func is_reporting_enabled() -> bool: +func _is_reporting_enabled() -> bool: return _is_report_script_errors() or _is_report_push_errors() func _is_report_push_errors() -> bool: - return _report_force or GdUnitSettings.is_report_push_errors() + return GdUnitSettings.is_report_push_errors() func _is_report_script_errors() -> bool: - return _report_force or GdUnitSettings.is_report_script_errors() + return GdUnitSettings.is_report_script_errors() diff --git a/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs b/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs new file mode 100644 index 00000000..6c27132c --- /dev/null +++ b/addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs @@ -0,0 +1,19 @@ +using Godot; +using Godot.Collections; + +using GdUnit4; + +// GdUnit4 GDScript - C# API wrapper +public partial class GdUnit4CSharpApi : RefCounted +{ + public static string Version() => GdUnit4MonoAPI.Version(); + + public static bool IsTestSuite(string classPath) => GdUnit4MonoAPI.IsTestSuite(classPath); + + public static RefCounted Executor(Node listener) => (RefCounted)GdUnit4MonoAPI.Executor(listener); + + public static GdUnit4.CsNode? ParseTestSuite(string classPath) => GdUnit4MonoAPI.ParseTestSuite(classPath); + + public static Dictionary CreateTestSuite(string sourcePath, int lineNumber, string testSuitePath) => + GdUnit4MonoAPI.CreateTestSuite(sourcePath, lineNumber, testSuitePath); +} diff --git a/addons/gdUnit4/src/mono/GdUnit4MonoApiLoader.gd b/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd similarity index 67% rename from addons/gdUnit4/src/mono/GdUnit4MonoApiLoader.gd rename to addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd index 4ad97a48..2eff8c96 100644 --- a/addons/gdUnit4/src/mono/GdUnit4MonoApiLoader.gd +++ b/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd @@ -1,18 +1,17 @@ extends RefCounted -class_name GdUnit4MonoApiLoader +class_name GdUnit4CSharpApiLoader static func instance() -> Object: - return GdUnitSingleton.instance("GdUnit4MonoAPI", func(): - if not GdUnit4MonoApiLoader.is_mono_supported(): + return GdUnitSingleton.instance("GdUnit4CSharpApi", func() -> Object: + if not GdUnit4CSharpApiLoader.is_mono_supported(): return null - var GdUnit4MonoApi = load("res://addons/gdUnit4/src/mono/GdUnit4MonoApi.cs") - return GdUnit4MonoApi.new() + return load("res://addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs") ) static func is_engine_version_supported(engine_version :int = Engine.get_version_info().hex) -> bool: - return engine_version >= 0x40100 + return engine_version >= 0x40200 # test is Godot mono running @@ -21,14 +20,14 @@ static func is_mono_supported() -> bool: static func version() -> String: - if not GdUnit4MonoApiLoader.is_mono_supported(): + if not GdUnit4CSharpApiLoader.is_mono_supported(): return "unknown" return instance().Version() static func create_test_suite(source_path :String, line_number :int, test_suite_path :String) -> GdUnitResult: - if not GdUnit4MonoApiLoader.is_mono_supported(): - return GdUnitResult.error("Can't create test suite. No c# support found.") + if not GdUnit4CSharpApiLoader.is_mono_supported(): + return GdUnitResult.error("Can't create test suite. No C# support found.") var result := instance().CreateTestSuite(source_path, line_number, test_suite_path) as Dictionary if result.has("error"): return GdUnitResult.error(result.get("error")) @@ -36,8 +35,9 @@ static func create_test_suite(source_path :String, line_number :int, test_suite_ static func is_test_suite(resource_path :String) -> bool: - if not is_csharp_file(resource_path) or not GdUnit4MonoApiLoader.is_mono_supported(): + if not is_csharp_file(resource_path) or not GdUnit4CSharpApiLoader.is_mono_supported(): return false + if resource_path.is_empty(): if GdUnitSettings.is_report_push_errors(): push_error("Can't create test suite. Missing resource path.") @@ -46,7 +46,7 @@ static func is_test_suite(resource_path :String) -> bool: static func parse_test_suite(source_path :String) -> Node: - if not GdUnit4MonoApiLoader.is_mono_supported(): + if not GdUnit4CSharpApiLoader.is_mono_supported(): if GdUnitSettings.is_report_push_errors(): push_error("Can't create test suite. No c# support found.") return null @@ -54,11 +54,11 @@ static func parse_test_suite(source_path :String) -> Node: static func create_executor(listener :Node) -> RefCounted: - if not GdUnit4MonoApiLoader.is_mono_supported(): + if not GdUnit4CSharpApiLoader.is_mono_supported(): return null return instance().Executor(listener) static func is_csharp_file(resource_path :String) -> bool: var ext := resource_path.get_extension() - return ext == "cs" and GdUnit4MonoApiLoader.is_mono_supported() + return ext == "cs" and GdUnit4CSharpApiLoader.is_mono_supported() diff --git a/addons/gdUnit4/src/mono/GdUnit4MonoApi.cs b/addons/gdUnit4/src/mono/GdUnit4MonoApi.cs deleted file mode 100644 index 0563ef92..00000000 --- a/addons/gdUnit4/src/mono/GdUnit4MonoApi.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Godot; -using Godot.Collections; - -// GdUnit4 C# API wrapper -public partial class GdUnit4MonoApi : GdUnit4.GdUnit4MonoAPI -{ - public new string Version() => GdUnit4.GdUnit4MonoAPI.Version(); - - public new bool IsTestSuite(string classPath) => GdUnit4.GdUnit4MonoAPI.IsTestSuite(classPath); - - public new RefCounted Executor(Node listener) => (RefCounted)GdUnit4.GdUnit4MonoAPI.Executor(listener); - - public new GdUnit4.CsNode? ParseTestSuite(string classPath) => GdUnit4.GdUnit4MonoAPI.ParseTestSuite(classPath); - - public new Dictionary CreateTestSuite(string sourcePath, int lineNumber, string testSuitePath) => - GdUnit4.GdUnit4MonoAPI.CreateTestSuite(sourcePath, lineNumber, testSuitePath); -} diff --git a/addons/gdUnit4/src/network/rpc/RPC.gd b/addons/gdUnit4/src/network/rpc/RPC.gd index 93d5462a..89bd8e36 100644 --- a/addons/gdUnit4/src/network/rpc/RPC.gd +++ b/addons/gdUnit4/src/network/rpc/RPC.gd @@ -5,13 +5,13 @@ func serialize() -> String: return JSON.stringify(inst_to_dict(self)) # using untyped version see comments below -static func deserialize(json_value :String): +static func deserialize(json_value :String) -> Object: var json := JSON.new() var err := json.parse(json_value) if err != OK: push_error("Can't deserialize JSON, error at line %d: %s \n json: '%s'" % [json.get_error_line(), json.get_error_message(), json_value]) return null - var result = json.get_data() as Dictionary + var result := json.get_data() as Dictionary if not typeof(result) == TYPE_DICTIONARY: push_error("Can't deserialize JSON, error at line %d: %s \n json: '%s'" % [result.error_line, result.error_string, json_value]) return null diff --git a/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd b/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd index d8d8b00e..ed2fff5e 100644 --- a/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd +++ b/addons/gdUnit4/src/network/rpc/RPCGdUnitTestSuite.gd @@ -3,13 +3,13 @@ extends RPC var _data :Dictionary -static func of(test_suite) -> RPCGdUnitTestSuite: - var rpc = RPCGdUnitTestSuite.new() +static func of(test_suite :Node) -> RPCGdUnitTestSuite: + var rpc := RPCGdUnitTestSuite.new() rpc._data = GdUnitTestSuiteDto.new().serialize(test_suite) return rpc func dto() -> GdUnitResourceDto: return GdUnitTestSuiteDto.new().deserialize(_data) -func _to_string(): +func _to_string() -> String: return "RPCGdUnitTestSuite: " + str(_data) diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd index d2cea382..9152c8d2 100644 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd +++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd @@ -4,19 +4,23 @@ extends Resource var _name :String var _path :String -func serialize(resource) -> Dictionary: + +func serialize(resource :Node) -> Dictionary: var serialized := Dictionary() serialized["name"] = resource.get_name() serialized["resource_path"] = resource.ResourcePath() return serialized + func deserialize(data :Dictionary) -> GdUnitResourceDto: _name = data.get("name", "n.a.") _path = data.get("resource_path", "") return self + func name() -> String: return _name + func path() -> String: return _path diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd index d30a0696..26f5dda5 100644 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd +++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd @@ -5,7 +5,7 @@ var _line_number :int = -1 var _test_case_names :PackedStringArray = [] -func serialize(test_case :Object) -> Dictionary: +func serialize(test_case :Node) -> Dictionary: var serialized := super.serialize(test_case) if test_case.has_method("line_number"): serialized["line_number"] = test_case.line_number() diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd index fb93edc5..9ecc9f68 100644 --- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd +++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd @@ -15,13 +15,13 @@ func serialize(test_suite :Node) -> Dictionary: func deserialize(data :Dictionary) -> GdUnitResourceDto: super.deserialize(data) - var test_cases_ :Array = data.get("test_cases", Array()) + var test_cases_ :Array = data.get("test_cases", []) for test_case in test_cases_: add_test_case(GdUnitTestCaseDto.new().deserialize(test_case)) return self -func add_test_case(test_case :GdUnitTestCaseDto): +func add_test_case(test_case :GdUnitTestCaseDto) -> void: _test_cases_by_name[test_case.name()] = test_case diff --git a/addons/gdUnit4/src/report/GdUnitHtmlReport.gd b/addons/gdUnit4/src/report/GdUnitHtmlReport.gd index 41ea52a7..050a0065 100644 --- a/addons/gdUnit4/src/report/GdUnitHtmlReport.gd +++ b/addons/gdUnit4/src/report/GdUnitHtmlReport.gd @@ -8,7 +8,7 @@ var _iteration :int func _init(path_ :String): - _iteration = GdUnitTools.find_last_path_index(path_, REPORT_DIR_PREFIX) + 1 + _iteration = GdUnitFileAccess.find_last_path_index(path_, REPORT_DIR_PREFIX) + 1 _report_path = "%s/%s%d" % [path_, REPORT_DIR_PREFIX, _iteration] DirAccess.make_dir_recursive_absolute(_report_path) @@ -59,12 +59,12 @@ func write() -> String: # write report var index_file := "%s/index.html" % _report_path FileAccess.open(index_file, FileAccess.WRITE).store_string(to_write) - GdUnitTools.copy_directory("res://addons/gdUnit4/src/report/template/css/", _report_path + "/css") + GdUnitFileAccess.copy_directory("res://addons/gdUnit4/src/report/template/css/", _report_path + "/css") return index_file func delete_history(max_reports :int) -> int: - return GdUnitTools.delete_path_index_lower_equals_than(_report_path.get_base_dir(), REPORT_DIR_PREFIX, _iteration-max_reports) + return GdUnitFileAccess.delete_path_index_lower_equals_than(_report_path.get_base_dir(), REPORT_DIR_PREFIX, _iteration-max_reports) func apply_path_reports(report_dir :String, template :String, reports_ :Array) -> String: diff --git a/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd b/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd index b01350ae..2db29bd7 100644 --- a/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd +++ b/addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd @@ -5,7 +5,7 @@ const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") const SPY_TEMPLATE :GDScript = preload("res://addons/gdUnit4/src/spy/GdUnitSpyImpl.gd") -static func build(to_spy, debug_write = false) -> Object: +static func build(to_spy, debug_write := false) -> Object: if GdObjects.is_singleton(to_spy): push_error("Spy on a Singleton is not allowed! '%s'" % to_spy.get_class()) return null @@ -61,7 +61,7 @@ static func spy_on_script(instance, function_excludes :PackedStringArray, debug_ var spy := GDScript.new() spy.source_code = "\n".join(lines) spy.resource_name = "Spy%s.gd" % clazz_name - spy.resource_path = GdUnitTools.create_temp_dir("spy") + "/Spy%s_%d.gd" % [clazz_name, Time.get_ticks_msec()] + spy.resource_path = GdUnitFileAccess.create_temp_dir("spy") + "/Spy%s_%d.gd" % [clazz_name, Time.get_ticks_msec()] if debug_write: DirAccess.remove_absolute(spy.resource_path) diff --git a/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd b/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd index 8b75a3e0..3b61e869 100644 --- a/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd +++ b/addons/gdUnit4/src/spy/GdUnitSpyImpl.gd @@ -2,7 +2,7 @@ const __INSTANCE_ID = "${instance_id}" const __SOURCE_CLASS = "${source_class}" -var __instance_delegator +var __instance_delegator :Object var __excluded_methods :PackedStringArray = [] @@ -10,7 +10,7 @@ static func __instance() -> Variant: return Engine.get_meta(__INSTANCE_ID) -func _notification(what): +func _notification(what :int) -> void: if what == NOTIFICATION_PREDELETE: if Engine.has_meta(__INSTANCE_ID): Engine.remove_meta(__INSTANCE_ID) @@ -20,13 +20,13 @@ func __instance_id() -> String: return __INSTANCE_ID -func __set_singleton(delegator): +func __set_singleton(delegator :Object) -> void: # store self need to mock static functions Engine.set_meta(__INSTANCE_ID, self) __instance_delegator = delegator -func __release_double(): +func __release_double() -> void: # we need to release the self reference manually to prevent orphan nodes Engine.remove_meta(__INSTANCE_ID) __instance_delegator = null @@ -40,5 +40,5 @@ func __exclude_method_call(exluded_methods :PackedStringArray) -> void: __excluded_methods.append_array(exluded_methods) -func __call_func(func_name :String, arguments :Array): +func __call_func(func_name :String, arguments :Array) -> Variant: return __instance_delegator.callv(func_name, arguments) diff --git a/addons/gdUnit4/src/ui/GdUnitFonts.gd b/addons/gdUnit4/src/ui/GdUnitFonts.gd index 206b8752..46ee80c9 100644 --- a/addons/gdUnit4/src/ui/GdUnitFonts.gd +++ b/addons/gdUnit4/src/ui/GdUnitFonts.gd @@ -16,7 +16,7 @@ static func init_fonts(item: CanvasItem) -> float: var plugin :EditorPlugin = Engine.get_meta("GdUnitEditorPlugin") var settings := plugin.get_editor_interface().get_editor_settings() var scale_factor := plugin.get_editor_interface().get_editor_scale() - var font_size = settings.get_setting("interface/editor/main_font_size") + var font_size :float = settings.get_setting("interface/editor/main_font_size") font_size *= scale_factor var font_mono := load_and_resize_font(FONT_MONO, font_size) item.set("theme_override_fonts/normal_font", font_mono) @@ -39,6 +39,6 @@ static func load_and_resize_font(font_resource: String, size: float) -> Font: if font == null: push_error("Can't load font '%s'" % font_resource) return null - var resized_font = font.duplicate() + var resized_font := font.duplicate() resized_font.fixed_size = int(size) return resized_font diff --git a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd index 8d2f1fad..8ea55fd5 100644 --- a/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd +++ b/addons/gdUnit4/src/ui/settings/GdUnitSettingsDialog.gd @@ -174,7 +174,7 @@ func _install_examples() -> void: _init_progress(5) update_progress("Downloading examples") await get_tree().process_frame - var tmp_path := GdUnitTools.create_temp_dir("download") + var tmp_path := GdUnitFileAccess.create_temp_dir("download") var zip_file := tmp_path + "/examples.zip" var response :GdUnitUpdateClient.HttpResponse = await _update_client.request_zip_package(EAXAMPLE_URL, zip_file) if response.code() != 200: @@ -185,7 +185,7 @@ func _install_examples() -> void: return # extract zip to tmp update_progress("Install examples into project") - var result := GdUnitTools.extract_zip(zip_file, "res://gdUnit4-examples/") + var result := GdUnitFileAccess.extract_zip(zip_file, "res://gdUnit4-examples/") if result.is_error(): update_progress("Install examples failed! %s" % result.error_message()) await get_tree().create_timer(3).timeout diff --git a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd index ffa316f4..b186e7c4 100644 --- a/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd +++ b/addons/gdUnit4/src/ui/templates/TestSuiteTemplate.gd @@ -6,15 +6,15 @@ extends MarginContainer @onready var _title_bar :Panel = $VBoxContainer/sub_category @onready var _save_button :Button = $VBoxContainer/Panel/HBoxContainer/Save @onready var _selected_type :OptionButton = $VBoxContainer/EdiorLayout/Editor/MarginContainer/HBoxContainer/SelectType -@onready var _show_tags = $Tags +@onready var _show_tags :PopupPanel = $Tags -var gd_key_words := ["extends", "class_name", "const", "var", "onready", "func", "void", "pass"] -var gdunit_key_words := ["GdUnitTestSuite", "before", "after", "before_test", "after_test"] +var gd_key_words :PackedStringArray = ["extends", "class_name", "const", "var", "onready", "func", "void", "pass"] +var gdunit_key_words :PackedStringArray = ["GdUnitTestSuite", "before", "after", "before_test", "after_test"] var _selected_template :int -func _ready(): +func _ready() -> void: setup_editor_colors() setup_fonts() setup_supported_types() @@ -22,7 +22,7 @@ func _ready(): setup_tags_help() -func _notification(what): +func _notification(what :int) -> void: if what == EditorSettings.NOTIFICATION_EDITOR_SETTINGS_CHANGED: setup_fonts() @@ -78,7 +78,7 @@ func setup_highlighter(editor :CodeEdit, settings :EditorSettings) -> void: func setup_fonts() -> void: if _template_editor: GdUnitFonts.init_fonts(_template_editor) - var font_size = GdUnitFonts.init_fonts(_tags_editor) + var font_size := GdUnitFonts.init_fonts(_tags_editor) _title_bar.size.y = font_size + 16 _title_bar.custom_minimum_size.y = font_size + 16 @@ -98,25 +98,25 @@ func load_template(template_id :int) -> void: _template_editor.set_text(GdUnitTestSuiteTemplate.load_template(template_id)) -func _on_Restore_pressed(): +func _on_Restore_pressed() -> void: _template_editor.set_text(GdUnitTestSuiteTemplate.default_template(_selected_template)) GdUnitTestSuiteTemplate.reset_to_default(_selected_template) _save_button.disabled = true -func _on_Save_pressed(): +func _on_Save_pressed() -> void: GdUnitTestSuiteTemplate.save_template(_selected_template, _template_editor.get_text()) _save_button.disabled = true -func _on_Tags_pressed(): +func _on_Tags_pressed() -> void: _show_tags.popup_centered_ratio(.5) -func _on_Editor_text_changed(): +func _on_Editor_text_changed() -> void: _save_button.disabled = false -func _on_SelectType_item_selected(index): +func _on_SelectType_item_selected(index :int) -> void: load_template(_selected_type.get_item_id(index)) setup_tags_help() diff --git a/addons/gdUnit4/src/update/GdUnitUpdate.gd b/addons/gdUnit4/src/update/GdUnitUpdate.gd index dc2c0bec..3d78a50d 100644 --- a/addons/gdUnit4/src/update/GdUnitUpdate.gd +++ b/addons/gdUnit4/src/update/GdUnitUpdate.gd @@ -211,7 +211,7 @@ func extract_zip(zip_package :String, dest_path :String) -> Variant: func download_release() -> void: - var zip_file := GdUnitTools.temp_dir() + "/update.zip" + var zip_file := GdUnitFileAccess.temp_dir() + "/update.zip" var response :GdUnitUpdateClient.HttpResponse if _debug_mode: response = GdUnitUpdateClient.HttpResponse.new(200, PackedByteArray()) diff --git a/extension/src/nodes/beehave_tree.cpp b/extension/src/nodes/beehave_tree.cpp index 8c37b83f..2aa92408 100644 --- a/extension/src/nodes/beehave_tree.cpp +++ b/extension/src/nodes/beehave_tree.cpp @@ -34,10 +34,17 @@ using namespace godot; BeehaveTree::BeehaveTree() : - context(Ref(memnew(BeehaveContext))) { + context(Ref(memnew(BeehaveContext))), + tick_status(BeehaveTreeNode::TickStatus::PENDING), + tick_rate(1) { } BeehaveTree::~BeehaveTree() { + context.unref(); + if (_internal_blackboard) { + memfree(_internal_blackboard); + _internal_blackboard = nullptr; + } } void BeehaveTree::_bind_methods() { @@ -52,6 +59,18 @@ void BeehaveTree::_bind_methods() { // methods ClassDB::bind_method(D_METHOD("enable"), &BeehaveTree::enable); ClassDB::bind_method(D_METHOD("disable"), &BeehaveTree::disable); + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &BeehaveTree::set_enabled); + ClassDB::bind_method(D_METHOD("is_enabled"), &BeehaveTree::is_enabled); + ClassDB::bind_method(D_METHOD("get_tick_status"), &BeehaveTree::get_tick_status); + ClassDB::bind_method(D_METHOD("set_tick_rate", "tick_rate"), &BeehaveTree::set_tick_rate); + ClassDB::bind_method(D_METHOD("get_tick_rate"), &BeehaveTree::get_tick_rate); + ClassDB::bind_method(D_METHOD("set_process_thread", "thread"), &BeehaveTree::set_process_thread); + ClassDB::bind_method(D_METHOD("get_process_thread"), &BeehaveTree::get_process_thread); + + // exports + ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread", PROPERTY_HINT_ENUM, "Idle,Physics"), "set_process_thread", "get_process_thread"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tick_rate"), "set_tick_rate", "get_tick_rate"); } void BeehaveTree::_ready() { @@ -96,6 +115,33 @@ void BeehaveTree::disable() { enabled = false; } +void BeehaveTree::set_enabled(bool enabled) { + this->enabled = enabled; +} +bool BeehaveTree::is_enabled() const { + return enabled; +} + +void BeehaveTree::set_tick_rate(int tick_rate) { + this->tick_rate = tick_rate; +} + +int BeehaveTree::get_tick_rate() const { + return tick_rate; +} + +BeehaveTreeNode::TickStatus BeehaveTree::get_tick_status() const { + return tick_status; +} + +void BeehaveTree::set_process_thread(BeehaveTree::ProcessThread thread) { + process_thread = thread; +} + +BeehaveTree::ProcessThread BeehaveTree::get_process_thread() const { + return process_thread; +} + void BeehaveTree::process_internally(double delta) { // ensure that we consider the current tick rate of the tree if (_last_tick < tick_rate - 1) { @@ -103,6 +149,8 @@ void BeehaveTree::process_internally(double delta) { return; } + _last_tick = 0; + context->set_delta(delta); tick(); @@ -112,6 +160,11 @@ BeehaveTreeNode::TickStatus BeehaveTree::tick() { context->set_blackboard(blackboard); context->set_tree(this); + if (get_child_count() == 0 || actor == nullptr) { + tick_status = BeehaveTreeNode::FAILURE; + return tick_status; + } + for (int i = 0; i < get_child_count(); i++) { Node *child = get_child(i); if (!child) { diff --git a/extension/src/nodes/beehave_tree.h b/extension/src/nodes/beehave_tree.h index 07fd90b2..845534c1 100644 --- a/extension/src/nodes/beehave_tree.h +++ b/extension/src/nodes/beehave_tree.h @@ -71,9 +71,17 @@ class BeehaveTree : public Node { void _process(double delta); void _physics_process(double delta); void _exit_tree(); + void enable(); void disable(); + void set_enabled(bool enabled); + bool is_enabled() const; BeehaveTreeNode::TickStatus tick(); + BeehaveTreeNode::TickStatus get_tick_status() const; + void set_tick_rate(int tick_rate); + int get_tick_rate() const; + void set_process_thread(BeehaveTree::ProcessThread thread); + BeehaveTree::ProcessThread get_process_thread() const; }; } //namespace godot diff --git a/extension/src/nodes/beehave_tree_node.cpp b/extension/src/nodes/beehave_tree_node.cpp index e9ecfc37..4b8fd601 100644 --- a/extension/src/nodes/beehave_tree_node.cpp +++ b/extension/src/nodes/beehave_tree_node.cpp @@ -46,6 +46,7 @@ void BeehaveTreeNode::_bind_methods() { BIND_VIRTUAL_METHOD(BeehaveTreeNode, tick); + BIND_ENUM_CONSTANT(PENDING); BIND_ENUM_CONSTANT(SUCCESS); BIND_ENUM_CONSTANT(FAILURE); BIND_ENUM_CONSTANT(RUNNING); diff --git a/extension/src/nodes/beehave_tree_node.h b/extension/src/nodes/beehave_tree_node.h index cd1cb9ae..74774dc6 100644 --- a/extension/src/nodes/beehave_tree_node.h +++ b/extension/src/nodes/beehave_tree_node.h @@ -43,6 +43,7 @@ class BeehaveTreeNode : public Node { public: enum TickStatus { + PENDING = -1, SUCCESS = 0, FAILURE = 1, RUNNING = 2 diff --git a/extension/src/nodes/decorators/beehave_succeeder.cpp b/extension/src/nodes/decorators/beehave_succeeder.cpp index f3fb0ac6..9e8ce7bb 100644 --- a/extension/src/nodes/decorators/beehave_succeeder.cpp +++ b/extension/src/nodes/decorators/beehave_succeeder.cpp @@ -41,4 +41,8 @@ BeehaveSucceeder::~BeehaveSucceeder() { void BeehaveSucceeder::_bind_methods() { +} + +BeehaveTreeNode::TickStatus BeehaveSucceeder::tick(Ref context) { + return BeehaveTreeNode::SUCCESS; } \ No newline at end of file diff --git a/extension/src/nodes/decorators/beehave_succeeder.h b/extension/src/nodes/decorators/beehave_succeeder.h index 236c8b62..e4e0e4c9 100644 --- a/extension/src/nodes/decorators/beehave_succeeder.h +++ b/extension/src/nodes/decorators/beehave_succeeder.h @@ -43,6 +43,7 @@ class BeehaveSucceeder : public BeehaveDecorator { public: BeehaveSucceeder(); ~BeehaveSucceeder(); + virtual TickStatus tick(Ref context); }; } diff --git a/test/beehave_tree_test.gd b/test/beehave_tree_test.gd index bcbc2ce6..32cd6b20 100644 --- a/test/beehave_tree_test.gd +++ b/test/beehave_tree_test.gd @@ -14,54 +14,53 @@ func create_tree() -> BeehaveTree: func test_normal_tick() -> void: var scene = create_scene() - scene_runner(scene) - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(BeehaveTreeNode.SUCCESS) + var runner = scene_runner(scene) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(BeehaveTreeNode.SUCCESS) func test_low_tick_rate() -> void: var scene = create_scene() - scene_runner(scene) - scene.beehave_tree.tick_rate = 3 - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(-1) - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(-1) - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(BeehaveTreeNode.SUCCESS) + var runner = scene_runner(scene) + scene.beehave_tree.set_tick_rate(3) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(-1) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(-1) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(BeehaveTreeNode.SUCCESS) func test_low_tick_rate_last_tick() -> void: var scene = create_scene() - scene_runner(scene) - scene.beehave_tree.tick_rate = 3 - scene.beehave_tree.last_tick = 1 - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(-1) - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.status).is_equal(BeehaveTreeNode.SUCCESS) - -func test_nothing_running_before_first_tick() -> void: - var scene = create_scene() - scene_runner(scene) - assert_that(scene.beehave_tree.get_running_action()).is_null() - assert_that(scene.beehave_tree.get_last_condition()).is_null() - assert_that(scene.beehave_tree.get_last_condition_status()).is_equal("") - -func test_get_last_condition() -> void: - var scene = create_scene() - var runner := scene_runner(scene) - await runner.simulate_frames(100) - assert_that(scene.beehave_tree.get_running_action()).is_null() - assert_that(scene.beehave_tree.get_last_condition()).is_not_null() - assert_that(scene.beehave_tree.get_last_condition_status()).is_equal('SUCCESS') + var runner = scene_runner(scene) + scene.beehave_tree.set_tick_rate(3) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(BeehaveTreeNode.PENDING) + await runner.simulate_frames(1) + assert_that(scene.beehave_tree.get_tick_status()).is_equal(BeehaveTreeNode.SUCCESS) + +#func test_nothing_running_before_first_tick() -> void: +# var scene = create_scene() +# scene_runner(scene) +# assert_that(scene.beehave_tree.get_running_action()).is_null() +# assert_that(scene.beehave_tree.get_last_condition()).is_null() +# assert_that(scene.beehave_tree.get_last_condition_status()).is_equal("") + +#func test_get_last_condition() -> void: +# var scene = create_scene() +# var runner := scene_runner(scene) +# await runner.simulate_frames(100) +# assert_that(scene.beehave_tree.get_running_action()).is_null() +# assert_that(scene.beehave_tree.get_last_condition()).is_not_null() +# assert_that(scene.beehave_tree.get_last_condition_status()).is_equal('SUCCESS') func test_disabled() -> void: var scene = create_scene() var runner := scene_runner(scene) scene.beehave_tree.disable() await runner.simulate_frames(50) - assert_bool(scene.beehave_tree.enabled).is_false() - assert_that(scene.beehave_tree.get_running_action()).is_null() - assert_that(scene.beehave_tree.get_last_condition()).is_null() + assert_bool(scene.beehave_tree.is_enabled()).is_false() + #assert_that(scene.beehave_tree.get_running_action()).is_null() + #assert_that(scene.beehave_tree.get_last_condition()).is_null() func test_reenabled() -> void: @@ -71,19 +70,19 @@ func test_reenabled() -> void: scene.beehave_tree.enable() await runner.simulate_frames(50) assert_bool(scene.beehave_tree.enabled).is_true() - assert_that(scene.beehave_tree.get_last_condition()).is_not_null() - - -func test_interrupt_running_action() -> void: - var scene = create_scene() - scene_runner(scene) - scene.count_up_action.status = BeehaveTreeNode.RUNNING - scene.beehave_tree._physics_process(1.0) - scene.beehave_tree._physics_process(1.0) - assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(2) - scene.beehave_tree.interrupt() - assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(0) - assert_that(scene.count_up_action.status).is_equal(BeehaveTreeNode.FAILURE) + #assert_that(scene.beehave_tree.get_last_condition()).is_not_null() + + +#func test_interrupt_running_action() -> void: +# var scene = create_scene() +# scene_runner(scene) +# scene.count_up_action.status = BeehaveTreeNode.RUNNING +# scene.beehave_tree._physics_process(1.0) +# scene.beehave_tree._physics_process(1.0) +# assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(2) +# scene.beehave_tree.interrupt() +# assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(0) +# assert_that(scene.count_up_action.status).is_equal(BeehaveTreeNode.FAILURE) #func test_blackboard_not_initialized() -> void: diff --git a/test/unit_test_scene.tscn b/test/unit_test_scene.tscn index 1743134e..f7e14a7d 100644 --- a/test/unit_test_scene.tscn +++ b/test/unit_test_scene.tscn @@ -6,8 +6,11 @@ script = ExtResource("1_1kcjy") [node name="BeehaveTree" type="BeehaveTree" parent="."] +process_thread = 0 unique_name_in_owner = true +[node name="BeehaveSucceeder" type="BeehaveSucceeder" parent="BeehaveTree"] + [node name="BeehaveBlackboard" type="BeehaveBlackboard" parent="."] unique_name_in_owner = true