diff --git a/addons/gdUnit4/GdUnitRunner.cfg b/addons/gdUnit4/GdUnitRunner.cfg
index 59dd7687..e3dc0ecd 100644
--- a/addons/gdUnit4/GdUnitRunner.cfg
+++ b/addons/gdUnit4/GdUnitRunner.cfg
@@ -1 +1 @@
-{"included":{"res://test/":[]},"server_port":31002,"skipped":{},"version":"1.0"}
\ No newline at end of file
+{"included":{"res://test/scene/mock_scene_test.gd":["test_instantiate_mock_data_via_scene"]},"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 c4ef66cc..6e5d588c 100644
--- a/addons/gdUnit4/bin/GdUnitBuildTool.gd
+++ b/addons/gdUnit4/bin/GdUnitBuildTool.gd
@@ -68,7 +68,7 @@ func _idle(_delta :float) -> void:
exit(RETURN_ERROR, result.error_message())
return
_console.prints_color("Added testcase: %s" % result.value(), Color.CORNFLOWER_BLUE)
- print_json_result(result.value())
+ print_json_result(result.value() as Dictionary)
exit(RETURN_SUCCESS)
@@ -85,7 +85,7 @@ func exit(code :int, message :String = "") -> void:
func print_json_result(result :Dictionary) -> void:
# convert back to system path
- var path := ProjectSettings.globalize_path(result["path"]);
+ var path := ProjectSettings.globalize_path(result["path"] as String)
var json := 'JSON_RESULT:{"TestCases" : [{"line":%d, "path": "%s"}]}' % [result["line"], path]
prints(json)
diff --git a/addons/gdUnit4/bin/GdUnitCmdTool.gd b/addons/gdUnit4/bin/GdUnitCmdTool.gd
index 7e905afc..07d0926b 100644
--- a/addons/gdUnit4/bin/GdUnitCmdTool.gd
+++ b/addons/gdUnit4/bin/GdUnitCmdTool.gd
@@ -33,6 +33,7 @@ class CLIRunner:
var _headless_mode_ignore := false
var _runner_config := GdUnitRunnerConfig.new()
var _runner_config_file := ""
+ var _debug_cmd_args: = PackedStringArray()
var _console := CmdConsole.new()
var _cmd_options := CmdOptions.new([
CmdOption.new(
@@ -105,9 +106,10 @@ class CLIRunner:
func _ready() -> void:
_state = INIT
_report_dir = GdUnitFileAccess.current_dir() + "reports"
- _executor = load("res://addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd").new()
+ _executor = GdUnitTestSuiteExecutor.new()
# stop checked first test failure to fail fast
- _executor.fail_fast(true)
+ @warning_ignore("unsafe_cast")
+ (_executor as GdUnitTestSuiteExecutor).fail_fast(true)
if GdUnit4CSharpApiLoader.is_mono_supported():
prints("GdUnit4Net version '%s' loaded." % GdUnit4CSharpApiLoader.version())
_cs_executor = GdUnit4CSharpApiLoader.create_executor(self)
@@ -123,6 +125,7 @@ class CLIRunner:
prints("Finallize .. done")
+ @warning_ignore("unsafe_method_access")
func _process(_delta :float) -> void:
match _state:
INIT:
@@ -135,7 +138,8 @@ class CLIRunner:
else:
set_process(false)
# process next test suite
- var test_suite := _test_suites_to_process.pop_front() as Node
+ var test_suite: Node = _test_suites_to_process.pop_front()
+
if _cs_executor != null and _cs_executor.IsExecutable(test_suite):
_cs_executor.Execute(test_suite)
await _cs_executor.ExecutionCompleted
@@ -185,6 +189,7 @@ class CLIRunner:
"Disabled fail fast!",
Color.DEEP_SKY_BLUE
)
+ @warning_ignore("unsafe_method_access")
_executor.fail_fast(false)
@@ -199,13 +204,13 @@ class CLIRunner:
func show_version() -> void:
_console.prints_color(
- "Godot %s" % Engine.get_version_info().get("string"),
+ "Godot %s" % Engine.get_version_info().get("string") as String,
Color.DARK_SALMON
)
var config := ConfigFile.new()
config.load("addons/gdUnit4/plugin.cfg")
_console.prints_color(
- "GdUnit4 %s" % config.get_value("plugin", "version"),
+ "GdUnit4 %s" % config.get_value("plugin", "version") as String,
Color.DARK_SALMON
)
quit(RETURN_SUCCESS)
@@ -274,6 +279,12 @@ class CLIRunner:
quit(RETURN_SUCCESS)
+ func get_cmdline_args() -> PackedStringArray:
+ if _debug_cmd_args.is_empty():
+ return OS.get_cmdline_args()
+ return _debug_cmd_args
+
+
func init_gd_unit() -> void:
_console.prints_color(
"""
@@ -284,7 +295,7 @@ class CLIRunner:
).new_line()
var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitCmdTool.gd")
- var result := cmd_parser.parse(OS.get_cmdline_args())
+ var result := cmd_parser.parse(get_cmdline_args())
if result.is_error():
show_options()
_console.prints_error(result.error_message())
@@ -297,7 +308,8 @@ class CLIRunner:
return
# build runner config by given commands
var commands :Array[CmdCommand] = []
- commands.append_array(result.value())
+ @warning_ignore("unsafe_cast")
+ commands.append_array(result.value() as Array)
result = (
CmdCommandHandler.new(_cmd_options)
.register_cb("-help", Callable(self, "show_help"))
@@ -412,7 +424,9 @@ class CLIRunner:
# if no tests skipped test the complete suite is skipped
if skipped_tests.is_empty():
_console.prints_warning("Mark test suite '%s' as skipped!" % suite_to_skip)
+ @warning_ignore("unsafe_property_access")
test_suite.__is_skipped = true
+ @warning_ignore("unsafe_property_access")
test_suite.__skip_reason = skip_reason
else:
# skip tests
@@ -443,10 +457,8 @@ class CLIRunner:
func _on_gdunit_event(event: GdUnitEvent) -> void:
match event.type():
GdUnitEvent.INIT:
- _report = GdUnitHtmlReport.new(_report_dir)
+ _report = GdUnitHtmlReport.new(_report_dir, _report_max)
GdUnitEvent.STOP:
- if _report == null:
- _report = GdUnitHtmlReport.new(_report_dir)
var report_path := _report.write()
_report.delete_history(_report_max)
JUnitXmlReport.new(_report._report_path, _report.iteration()).write(_report)
@@ -464,45 +476,31 @@ class CLIRunner:
Color.CORNFLOWER_BLUE
)
GdUnitEvent.TESTSUITE_BEFORE:
- _report.add_testsuite_report(
- GdUnitTestSuiteReport.new(event.resource_path(), event.suite_name(), event.total_count())
- )
+ _report.add_testsuite_report(event.resource_path(), event.suite_name(), event.total_count())
GdUnitEvent.TESTSUITE_AFTER:
- _report.update_test_suite_report(
+ _report.add_testsuite_reports(
event.resource_path(),
- event.elapsed_time(),
- event.is_error(),
- event.is_failed(),
- event.is_warning(),
- event.is_skipped(),
- event.skipped_count(),
+ event.error_count(),
event.failed_count(),
event.orphan_nodes(),
+ event.elapsed_time(),
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(event.resource_path(), event.suite_name(), event.test_name())
GdUnitEvent.TESTCASE_AFTER:
- var test_report := GdUnitTestCaseReport.new(
- event.resource_path(),
- event.suite_name(),
+ _report.set_testcase_counters(event.resource_path(),
event.test_name(),
event.is_error(),
- event.is_failed(),
event.failed_count(),
event.orphan_nodes(),
event.is_skipped(),
- event.reports(),
- event.elapsed_time()
- )
- _report.update_testcase_report(event.resource_path(), test_report)
+ event.is_flaky(),
+ event.elapsed_time())
+ _report.add_testcase_reports(event.resource_path(), event.test_name(), event.reports())
+ GdUnitEvent.TESTCASE_STATISTICS:
+ _report.update_testsuite_counters(event.resource_path(), event.is_error(), event.failed_count(), event.orphan_nodes(),\
+ event.is_skipped(), event.is_flaky(), event.elapsed_time())
print_status(event)
@@ -556,11 +554,12 @@ class CLIRunner:
_print_failure_report(event.reports())
_print_status(event)
_console.prints_color(
- "Statistics: | %d tests cases | %d error | %d failed | %d skipped | %d orphans |\n"
+ "Statistics: | %d tests cases | %d error | %d failed | %d flaky | %d skipped | %d orphans |\n"
% [
_report.test_count(),
_report.error_count(),
_report.failure_count(),
+ _report.flaky_count(),
_report.skipped_count(),
_report.orphan_count()
],
@@ -587,14 +586,22 @@ class CLIRunner:
func _print_status(event: GdUnitEvent) -> void:
- if event.is_skipped():
+ if event.is_flaky() and event.is_success():
+ var retries :int = event.statistic(GdUnitEvent.RETRY_COUNT)
+ _console.print_color("FLAKY (%d retries)" % retries, Color.GREEN_YELLOW, CmdConsole.BOLD | CmdConsole.ITALIC)
+ elif event.is_success():
+ _console.print_color("PASSED", Color.FOREST_GREEN, CmdConsole.BOLD)
+ elif event.is_skipped():
_console.print_color("SKIPPED", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.ITALIC)
elif event.is_failed() or event.is_error():
- _console.print_color("FAILED", Color.FIREBRICK, CmdConsole.BOLD)
- elif event.orphan_nodes() > 0:
- _console.print_color("PASSED", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.UNDERLINE)
- else:
- _console.print_color("PASSED", Color.FOREST_GREEN, CmdConsole.BOLD)
+ var retries :int = event.statistic(GdUnitEvent.RETRY_COUNT)
+ if retries > 1:
+ _console.print_color("FAILED (retry %d)" % retries, Color.FIREBRICK, CmdConsole.BOLD)
+ else:
+ _console.print_color("FAILED", Color.FIREBRICK, CmdConsole.BOLD)
+ elif event.is_warning():
+ _console.print_color("WARNING", Color.GOLDENROD, CmdConsole.BOLD | CmdConsole.UNDERLINE)
+
_console.prints_color(
" %s" % LocalTime.elapsed(event.elapsed_time()), Color.CORNFLOWER_BLUE
)
diff --git a/addons/gdUnit4/bin/GdUnitCopyLog.gd b/addons/gdUnit4/bin/GdUnitCopyLog.gd
index 2e037a69..084ac72d 100644
--- a/addons/gdUnit4/bin/GdUnitCopyLog.gd
+++ b/addons/gdUnit4/bin/GdUnitCopyLog.gd
@@ -4,23 +4,30 @@ extends MainLoop
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
# gdlint: disable=max-line-length
-const NO_LOG_TEMPLATE = """
+const LOG_FRAME_TEMPLATE = """
-
+
-
-
- Logging
-
+
+
+ Godot Logging
+
-
-
-
No logging available!
-
-
For logging to occur, you must check Enable File Logging in Project Settings.
-
You can enable Logging Project Settings > Logging > File Logging > Enable File Logging in the Project Settings.
+
+
+
+${content}
+
+"""
+
+const NO_LOG_MESSAGE = """
+
No logging available!
+
+
In order for logging to take place, you must activate the Activate file logging option in the project settings.
+
You can enable the logging under:
+Project Settings > Debug > File Logging > Enable File Logging in the project settings.
"""
#warning-ignore-all:return_value_discarded
@@ -34,48 +41,65 @@ var _cmd_options := CmdOptions.new([
)
])
+
var _report_root_path: String
+var _current_report_path: String
+var _debug_cmd_args := PackedStringArray()
func _init() -> void:
- _report_root_path = GdUnitFileAccess.current_dir() + "reports"
+ set_report_directory(GdUnitFileAccess.current_dir() + "reports")
+ set_current_report_path()
-func _process(_delta :float) -> bool:
+func _process(_delta: float) -> bool:
# check if reports exists
if not reports_available():
prints("no reports found")
return true
- # scan for latest report path
- 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, "")
+ write_report(NO_LOG_MESSAGE, "")
return true
+
# 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():
+ if cmd_parser.parse(get_cmdline_args(), true).is_error():
return true
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)
+
+ var godot_log_file := scan_latest_godot_log()
+ var result := read_log_file_content(godot_log_file)
if result.is_error():
- push_error(result.error_message())
+ write_report(result.error_message(), godot_log_file)
return true
- _patch_report(report_path, godot_log)
+ write_report(result.value_as_string(), godot_log_file)
return true
+func set_current_report_path() -> void:
+ # scan for latest report directory
+ var iteration := GdUnitFileAccess.find_last_path_index(
+ _report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX
+ )
+ _current_report_path = "%s/%s%d" % [_report_root_path, GdUnitHtmlReport.REPORT_DIR_PREFIX, iteration]
+
+
func set_report_directory(path: String) -> void:
_report_root_path = path
-func _scan_latest_godot_log() -> String:
+func get_log_report_html() -> String:
+ return _current_report_path + "/godot_report_log.html"
+
+
+func reports_available() -> bool:
+ return DirAccess.dir_exists_absolute(_report_root_path)
+
+
+func scan_latest_godot_log() -> String:
var path := GdUnitSettings.get_log_path().get_base_dir()
var files_sorted := Array()
for file in GdUnitFileAccess.scan_dir(path):
@@ -83,59 +107,60 @@ func _scan_latest_godot_log() -> String:
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:
- 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"
- % 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)
- 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)
+ return files_sorted.back()
-func _copy_and_pach(from_file: String, to_dir: String) -> GdUnitResult:
- var result := GdUnitFileAccess.copy_file(from_file, to_dir)
- if result.is_error():
- return result
- var file := FileAccess.open(from_file, FileAccess.READ)
+func read_log_file_content(log_file: String) -> GdUnitResult:
+ var file := FileAccess.open(log_file, FileAccess.READ)
if file == null:
return GdUnitResult.error(
- "Can't find file '%s'. Error: %s"
- % [from_file, error_string(FileAccess.get_open_error())]
+ "Can't find log file '%s'. Error: %s"
+ % [log_file, error_string(FileAccess.get_open_error())]
)
- var content := file.get_as_text()
+ 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 = content\
.replace("[0m", "")\
.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)
+ return GdUnitResult.success(content)
+
+
+func write_report(content: String, godot_log_file: String) -> GdUnitResult:
+ var file := FileAccess.open(get_log_report_html(), FileAccess.WRITE)
if file == null:
return GdUnitResult.error(
"Can't open to write '%s'. Error: %s"
- % [to_file, error_string(FileAccess.get_open_error())]
+ % [get_log_report_html(), error_string(FileAccess.get_open_error())]
)
- file.store_string(content)
- return GdUnitResult.empty()
+ var report_html := LOG_FRAME_TEMPLATE.replace("${content}", content)
+ file.store_string(report_html)
+ _update_index_html(godot_log_file)
+ return GdUnitResult.success(file)
-func reports_available() -> bool:
- return DirAccess.dir_exists_absolute(_report_root_path)
+func _update_index_html(godot_log_file: String) -> void:
+ var index_file := FileAccess.open("%s/index.html" % _current_report_path, FileAccess.READ_WRITE)
+ if index_file == null:
+ push_error(
+ "Can't add log path to index.html. Error: %s"
+ % error_string(FileAccess.get_open_error())
+ )
+ return
+ var content := index_file.get_as_text()\
+ .replace("${log_report}", get_log_report_html())\
+ .replace("${godot_log_file}", godot_log_file)
+ # overide it
+ index_file.seek(0)
+ index_file.store_string(content)
+
+
+func get_cmdline_args() -> PackedStringArray:
+ if _debug_cmd_args.is_empty():
+ return OS.get_cmdline_args()
+ return _debug_cmd_args
diff --git a/addons/gdUnit4/plugin.cfg b/addons/gdUnit4/plugin.cfg
index cf7c92f1..6fdf5032 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.3.3"
+version="4.4.1"
script="plugin.gd"
diff --git a/addons/gdUnit4/plugin.gd b/addons/gdUnit4/plugin.gd
index dd6d87da..b7887c8a 100644
--- a/addons/gdUnit4/plugin.gd
+++ b/addons/gdUnit4/plugin.gd
@@ -5,13 +5,14 @@ const GdUnitTools := preload ("res://addons/gdUnit4/src/core/GdUnitTools.gd")
const GdUnitTestDiscoverGuard := preload ("res://addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd")
-var _gd_inspector :Node
-var _gd_console :Node
+var _gd_inspector: Control
+var _gd_console: Control
var _guard: GdUnitTestDiscoverGuard
func _enter_tree() -> void:
if check_running_in_test_env():
+ @warning_ignore("return_value_discarded")
CmdConsole.new().prints_warning("It was recognized that GdUnit4 is running in a test environment, therefore the GdUnit4 plugin will not be executed!")
return
if Engine.get_version_info().hex < 0x40200:
@@ -23,6 +24,7 @@ func _enter_tree() -> void:
add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_UR, _gd_inspector)
# install the GdUnit Console
_gd_console = load("res://addons/gdUnit4/src/ui/GdUnitConsole.tscn").instantiate()
+ @warning_ignore("return_value_discarded")
add_control_to_bottom_panel(_gd_console, "gdUnitConsole")
prints("Loading GdUnit4 Plugin success")
if GdUnitSettings.is_update_notification_enabled():
@@ -32,6 +34,7 @@ func _enter_tree() -> void:
prints("GdUnit4Net version '%s' loaded." % GdUnit4CSharpApiLoader.version())
# connect to be notified for script changes to be able to discover new tests
_guard = GdUnitTestDiscoverGuard.new()
+ @warning_ignore("return_value_discarded")
resource_saved.connect(_on_resource_saved)
@@ -56,4 +59,4 @@ func check_running_in_test_env() -> bool:
func _on_resource_saved(resource: Resource) -> void:
if resource is Script:
- await _guard.discover(resource)
+ await _guard.discover(resource as Script)
diff --git a/addons/gdUnit4/src/GdUnitAssert.gd b/addons/gdUnit4/src/GdUnitAssert.gd
index fe056d8b..6b354750 100644
--- a/addons/gdUnit4/src/GdUnitAssert.gd
+++ b/addons/gdUnit4/src/GdUnitAssert.gd
@@ -18,19 +18,19 @@ func is_not_null():
## Verifies that the current value is equal to expected one.
@warning_ignore("unused_parameter")
@warning_ignore("untyped_declaration")
-func is_equal(expected):
+func is_equal(expected: Variant):
return self
## Verifies that the current value is not equal to expected one.
@warning_ignore("unused_parameter")
@warning_ignore("untyped_declaration")
-func is_not_equal(expected):
+func is_not_equal(expected: Variant):
return self
@warning_ignore("untyped_declaration")
-func test_fail():
+func do_fail():
return self
diff --git a/addons/gdUnit4/src/GdUnitAwaiter.gd b/addons/gdUnit4/src/GdUnitAwaiter.gd
index fc2e487b..51385e88 100644
--- a/addons/gdUnit4/src/GdUnitAwaiter.gd
+++ b/addons/gdUnit4/src/GdUnitAwaiter.gd
@@ -1,8 +1,6 @@
class_name GdUnitAwaiter
extends RefCounted
-const GdUnitAssertImpl = preload("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd")
-
# Waits for a specified signal in an interval of 50ms sent from the
, and terminates with an error after the specified timeout has elapsed.
# source: the object from which the signal is emitted
@@ -14,16 +12,19 @@ func await_signal_on(source :Object, signal_name :String, args :Array = [], time
var assert_that := GdUnitAssertImpl.new(signal_name)
var line_number := GdUnitAssertions.get_line_number()
if not is_instance_valid(source):
+ @warning_ignore("return_value_discarded")
assert_that.report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number)
- return await Engine.get_main_loop().process_frame
+ return await (Engine.get_main_loop() as SceneTree).process_frame
# fail fast if the given source instance invalid
if not is_instance_valid(source):
+ @warning_ignore("return_value_discarded")
assert_that.report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number)
return await await_idle_frame()
var awaiter := GdUnitSignalAwaiter.new(timeout_millis)
var value :Variant = await awaiter.on_signal(source, signal_name, args)
if awaiter.is_interrupted():
var failure := "await_signal_on(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis]
+ @warning_ignore("return_value_discarded")
assert_that.report_error(failure, line_number)
return value
@@ -37,6 +38,7 @@ func await_signal_idle_frames(source :Object, signal_name :String, args :Array =
var line_number := GdUnitAssertions.get_line_number()
# fail fast if the given source instance invalid
if not is_instance_valid(source):
+ @warning_ignore("return_value_discarded")
GdUnitAssertImpl.new(signal_name)\
.report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number)
return await await_idle_frame()
@@ -44,6 +46,7 @@ func await_signal_idle_frames(source :Object, signal_name :String, args :Array =
var value :Variant = await awaiter.on_signal(source, signal_name, args)
if awaiter.is_interrupted():
var failure := "await_signal_idle_frames(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis]
+ @warning_ignore("return_value_discarded")
GdUnitAssertImpl.new(signal_name).report_error(failure, line_number)
return value
@@ -56,7 +59,7 @@ func await_signal_idle_frames(source :Object, signal_name :String, args :Array =
func await_millis(milliSec :int) -> void:
var timer :Timer = Timer.new()
timer.set_name("gdunit_await_millis_timer_%d" % timer.get_instance_id())
- Engine.get_main_loop().root.add_child(timer)
+ (Engine.get_main_loop() as SceneTree).root.add_child(timer)
timer.add_to_group("GdUnitTimers")
timer.set_one_shot(true)
timer.start(milliSec / 1000.0)
@@ -66,4 +69,4 @@ func await_millis(milliSec :int) -> void:
# Waits until the next idle frame
func await_idle_frame() -> void:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
diff --git a/addons/gdUnit4/src/GdUnitFloatAssert.gd b/addons/gdUnit4/src/GdUnitFloatAssert.gd
index 8f24f561..6ce5f1e8 100644
--- a/addons/gdUnit4/src/GdUnitFloatAssert.gd
+++ b/addons/gdUnit4/src/GdUnitFloatAssert.gd
@@ -3,15 +3,15 @@ class_name GdUnitFloatAssert
extends GdUnitAssert
-## Verifies that the current value is equal to expected one.
+## Verifies that the current String is equal to the given one.
@warning_ignore("unused_parameter")
-func is_equal(expected :float) -> GdUnitFloatAssert:
+func is_equal(expected :Variant) -> GdUnitFloatAssert:
return self
-## Verifies that the current value is not equal to expected one.
+## Verifies that the current String is not equal to the given one.
@warning_ignore("unused_parameter")
-func is_not_equal(expected :float) -> GdUnitFloatAssert:
+func is_not_equal(expected :Variant) -> GdUnitFloatAssert:
return self
diff --git a/addons/gdUnit4/src/GdUnitFuncAssert.gd b/addons/gdUnit4/src/GdUnitFuncAssert.gd
index c5e0e5fc..75e8ebd6 100644
--- a/addons/gdUnit4/src/GdUnitFuncAssert.gd
+++ b/addons/gdUnit4/src/GdUnitFuncAssert.gd
@@ -5,39 +5,39 @@ extends GdUnitAssert
## Verifies that the current value is null.
func is_null() -> GdUnitFuncAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
## Verifies that the current value is not null.
func is_not_null() -> GdUnitFuncAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
## Verifies that the current value is equal to the given one.
@warning_ignore("unused_parameter")
func is_equal(expected :Variant) -> GdUnitFuncAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
## Verifies that the current value is not equal to the given one.
@warning_ignore("unused_parameter")
func is_not_equal(expected :Variant) -> GdUnitFuncAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
## Verifies that the current value is true.
func is_true() -> GdUnitFuncAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
## Verifies that the current value is false.
func is_false() -> GdUnitFuncAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
diff --git a/addons/gdUnit4/src/GdUnitGodotErrorAssert.gd b/addons/gdUnit4/src/GdUnitGodotErrorAssert.gd
index d689625a..c5e9f863 100644
--- a/addons/gdUnit4/src/GdUnitGodotErrorAssert.gd
+++ b/addons/gdUnit4/src/GdUnitGodotErrorAssert.gd
@@ -9,7 +9,7 @@ extends GdUnitAssert
## await assert_error().is_success()
## [/codeblock]
func is_success() -> GdUnitGodotErrorAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
@@ -20,7 +20,7 @@ func is_success() -> GdUnitGodotErrorAssert:
## [/codeblock]
@warning_ignore("unused_parameter")
func is_runtime_error(expected_error :String) -> GdUnitGodotErrorAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
@@ -31,7 +31,7 @@ func is_runtime_error(expected_error :String) -> GdUnitGodotErrorAssert:
## [/codeblock]
@warning_ignore("unused_parameter")
func is_push_warning(expected_warning :String) -> GdUnitGodotErrorAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
@@ -42,5 +42,5 @@ func is_push_warning(expected_warning :String) -> GdUnitGodotErrorAssert:
## [/codeblock]
@warning_ignore("unused_parameter")
func is_push_error(expected_error :String) -> GdUnitGodotErrorAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
diff --git a/addons/gdUnit4/src/GdUnitIntAssert.gd b/addons/gdUnit4/src/GdUnitIntAssert.gd
index 584788fc..d593edf9 100644
--- a/addons/gdUnit4/src/GdUnitIntAssert.gd
+++ b/addons/gdUnit4/src/GdUnitIntAssert.gd
@@ -2,16 +2,15 @@
class_name GdUnitIntAssert
extends GdUnitAssert
-
-## Verifies that the current value is equal to expected one.
+## Verifies that the current String is equal to the given one.
@warning_ignore("unused_parameter")
-func is_equal(expected :int) -> GdUnitIntAssert:
+func is_equal(expected :Variant) -> GdUnitIntAssert:
return self
-## Verifies that the current value is not equal to expected one.
+## Verifies that the current String is not equal to the given one.
@warning_ignore("unused_parameter")
-func is_not_equal(expected :int) -> GdUnitIntAssert:
+func is_not_equal(expected :Variant) -> GdUnitIntAssert:
return self
diff --git a/addons/gdUnit4/src/GdUnitSceneRunner.gd b/addons/gdUnit4/src/GdUnitSceneRunner.gd
index 5707a6ae..184be50a 100644
--- a/addons/gdUnit4/src/GdUnitSceneRunner.gd
+++ b/addons/gdUnit4/src/GdUnitSceneRunner.gd
@@ -1,44 +1,30 @@
-## The scene runner for GdUnit to simmulate scene interactions
+## The Scene Runner is a tool used for simulating interactions on a scene.
+## With this tool, you can simulate input events such as keyboard or mouse input and/or simulate scene processing over a certain number of frames.
+## This tool is typically used for integration testing a scene.
class_name GdUnitSceneRunner
extends RefCounted
const NO_ARG = GdUnitConstants.NO_ARG
-## Sets the mouse cursor to given position relative to the viewport.
-@warning_ignore("unused_parameter")
-func set_mouse_pos(pos :Vector2) -> GdUnitSceneRunner:
- return self
-
-
-## Gets the current mouse position of the current viewport
-func get_mouse_position() -> Vector2:
- return Vector2.ZERO
-
-
-## Gets the current global mouse position of the current window
-func get_global_mouse_position() -> Vector2:
- return Vector2.ZERO
-
-
## Simulates that an action has been pressed.[br]
## [member action] : the action e.g. [code]"ui_up"[/code][br]
@warning_ignore("unused_parameter")
-func simulate_action_pressed(action :String) -> GdUnitSceneRunner:
+func simulate_action_pressed(action: String) -> GdUnitSceneRunner:
return self
## Simulates that an action is pressed.[br]
## [member action] : the action e.g. [code]"ui_up"[/code][br]
@warning_ignore("unused_parameter")
-func simulate_action_press(action :String) -> GdUnitSceneRunner:
+func simulate_action_press(action: String) -> GdUnitSceneRunner:
return self
## Simulates that an action has been released.[br]
## [member action] : the action e.g. [code]"ui_up"[/code][br]
@warning_ignore("unused_parameter")
-func simulate_action_release(action :String) -> GdUnitSceneRunner:
+func simulate_action_release(action: String) -> GdUnitSceneRunner:
return self
@@ -46,8 +32,14 @@ func simulate_action_release(action :String) -> GdUnitSceneRunner:
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
+## [codeblock]
+## func test_key_presssed():
+## var runner = scene_runner("res://scenes/simple_scene.tscn")
+## await runner.simulate_key_pressed(KEY_SPACE)
+## [/codeblock]
@warning_ignore("unused_parameter")
-func simulate_key_pressed(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
+func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
@@ -56,7 +48,7 @@ func simulate_key_pressed(key_code :int, shift_pressed := false, ctrl_pressed :=
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
@warning_ignore("unused_parameter")
-func simulate_key_press(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
+func simulate_key_press(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
return self
@@ -65,14 +57,38 @@ func simulate_key_press(key_code :int, shift_pressed := false, ctrl_pressed := f
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
@warning_ignore("unused_parameter")
-func simulate_key_release(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
+func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
+ return self
+
+
+## Sets the mouse cursor to given position relative to the viewport.
+## @deprecated: Use [set_mouse_position] instead.
+@warning_ignore("unused_parameter")
+func set_mouse_pos(position: Vector2) -> GdUnitSceneRunner:
return self
+## Sets the mouse position to the specified vector, provided in pixels and relative to an origin at the upper left corner of the currently focused Window Manager game window.[br]
+## [member position] : The absolute position in pixels as Vector2
+@warning_ignore("unused_parameter")
+func set_mouse_position(position: Vector2) -> GdUnitSceneRunner:
+ return self
+
+
+## Returns the mouse's position in this Viewport using the coordinate system of this Viewport.
+func get_mouse_position() -> Vector2:
+ return Vector2.ZERO
+
+
+## Gets the current global mouse position of the current window
+func get_global_mouse_position() -> Vector2:
+ return Vector2.ZERO
+
+
## Simulates a mouse moved to final position.[br]
-## [member pos] : The final mouse position
+## [member position] : The final mouse position
@warning_ignore("unused_parameter")
-func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner:
+func simulate_mouse_move(position: Vector2) -> GdUnitSceneRunner:
return self
@@ -89,7 +105,7 @@ func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner:
## [/codeblock]
@warning_ignore("unused_parameter")
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
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
@@ -106,36 +122,149 @@ func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_ty
## [/codeblock]
@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
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
## Simulates a mouse button pressed.[br]
-## [member buttonIndex] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
+## [member button_index] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
+## [member double_click] : Set to true to simulate a double-click
@warning_ignore("unused_parameter")
-func simulate_mouse_button_pressed(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner:
+func simulate_mouse_button_pressed(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner:
return self
## Simulates a mouse button press (holding)[br]
-## [member buttonIndex] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
+## [member button_index] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
+## [member double_click] : Set to true to simulate a double-click
@warning_ignore("unused_parameter")
-func simulate_mouse_button_press(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner:
+func simulate_mouse_button_press(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner:
return self
## Simulates a mouse button released.[br]
-## [member buttonIndex] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
+## [member button_index] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
+@warning_ignore("unused_parameter")
+func simulate_mouse_button_release(button_index: MouseButton) -> GdUnitSceneRunner:
+ return self
+
+
+## Simulates a screen touch is pressed.[br]
+## [member index] : The touch index in the case of a multi-touch event.[br]
+## [member position] : The position to touch the screen.[br]
+## [member double_tap] : If true, the touch's state is a double tab.
+@warning_ignore("unused_parameter")
+func simulate_screen_touch_pressed(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner:
+ return self
+
+
+## Simulates a screen touch press without releasing it immediately, effectively simulating a "hold" action.[br]
+## [member index] : The touch index in the case of a multi-touch event.[br]
+## [member position] : The position to touch the screen.[br]
+## [member double_tap] : If true, the touch's state is a double tab.
+@warning_ignore("unused_parameter")
+func simulate_screen_touch_press(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner:
+ return self
+
+
+## Simulates a screen touch is released.[br]
+## [member index] : The touch index in the case of a multi-touch event.[br]
+## [member double_tap] : If true, the touch's state is a double tab.
+@warning_ignore("unused_parameter")
+func simulate_screen_touch_release(index: int, double_tap := false) -> GdUnitSceneRunner:
+ return self
+
+
+## Simulates a touch drag and drop event to a relative position.[br]
+## [color=yellow]You must use [b]await[/b] to wait until the simulated drag&drop is complete.[/color][br]
+## [br]
+## [member index] : The touch index in the case of a multi-touch event.[br]
+## [member relative] : The relative position, indicating the drag&drop position offset.[br]
+## [member time] : The time to move to the relative position in seconds (default is 1 second).[br]
+## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br]
+## [codeblock]
+## func test_touch_drag_drop():
+## var runner = scene_runner("res://scenes/simple_scene.tscn")
+## # start drag at position 50,50
+## runner.simulate_screen_touch_drag_begin(1, Vector2(50, 50))
+## # and drop it at final at 150,50 relative (50,50 + 100,0)
+## await runner.simulate_screen_touch_drag_relative(1, Vector2(100,0))
+## [/codeblock]
+@warning_ignore("unused_parameter")
+func simulate_screen_touch_drag_relative(index: int, relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
+ await (Engine.get_main_loop() as SceneTree).process_frame
+ return self
+
+
+## Simulates a touch screen drop to the absolute coordinates (offset).[br]
+## [color=yellow]You must use [b]await[/b] to wait until the simulated drop is complete.[/color][br]
+## [br]
+## [member index] : The touch index in the case of a multi-touch event.[br]
+## [member position] : The final position, indicating the drop position.[br]
+## [member time] : The time to move 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]
+## [codeblock]
+## func test_touch_drag_drop():
+## var runner = scene_runner("res://scenes/simple_scene.tscn")
+## # start drag at position 50,50
+## runner.simulate_screen_touch_drag_begin(1, Vector2(50, 50))
+## # and drop it at 100,50
+## await runner.simulate_screen_touch_drag_absolute(1, Vector2(100,50))
+## [/codeblock]
+@warning_ignore("unused_parameter")
+func simulate_screen_touch_drag_absolute(index: int, position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
+ await (Engine.get_main_loop() as SceneTree).process_frame
+ return self
+
+
+## Simulates a complete drag and drop event from one position to another.[br]
+## This is ideal for testing complex drag-and-drop scenarios that require a specific start and end position.[br]
+## [color=yellow]You must use [b]await[/b] to wait until the simulated drop is complete.[/color][br]
+## [br]
+## [member index] : The touch index in the case of a multi-touch event.[br]
+## [member position] : The drag start position, indicating the drag position.[br]
+## [member drop_position] : The drop position, indicating the drop position.[br]
+## [member time] : The time to move 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]
+## [codeblock]
+## func test_touch_drag_drop():
+## var runner = scene_runner("res://scenes/simple_scene.tscn")
+## # start drag at position 50,50 and drop it at 100,50
+## await runner.simulate_screen_touch_drag_drop(1, Vector2(50, 50), Vector2(100,50))
+## [/codeblock]
@warning_ignore("unused_parameter")
-func simulate_mouse_button_release(buttonIndex :MouseButton) -> GdUnitSceneRunner:
+func simulate_screen_touch_drag_drop(index: int, position: Vector2, drop_position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
+## Simulates a touch screen drag event to given position.[br]
+## [member index] : The touch index in the case of a multi-touch event.[br]
+## [member position] : The drag start position, indicating the drag position.[br]
+@warning_ignore("unused_parameter")
+func simulate_screen_touch_drag(index: int, position: Vector2) -> GdUnitSceneRunner:
+ return self
+
+
+## Returns the actual position of the touchscreen drag position by given index.
+## [member index] : The touch index in the case of a multi-touch event.[br]
+@warning_ignore("unused_parameter")
+func get_screen_touch_drag_position(index: int) -> Vector2:
+ return Vector2.ZERO
+
+
## Sets how fast or slow the scene simulation is processed (clock ticks versus the real).[br]
## It defaults to 1.0. A value of 2.0 means the game moves twice as fast as real life,
## whilst a value of 0.5 means the game moves at half the regular speed.
+
+
+## Sets the time factor for the scene simulation.
+## [member time_factor] : A float representing the simulation speed.[br]
+## - Default is 1.0, meaning the simulation runs at normal speed.[br]
+## - A value of 2.0 means the simulation runs twice as fast as real time.[br]
+## - A value of 0.5 means the simulation runs at half the regular speed.[br]
@warning_ignore("unused_parameter")
-func set_time_factor(time_factor := 1.0) -> GdUnitSceneRunner:
+func set_time_factor(time_factor: float = 1.0) -> GdUnitSceneRunner:
return self
@@ -143,8 +272,8 @@ func set_time_factor(time_factor := 1.0) -> GdUnitSceneRunner:
## [member frames] : amount of frames to process[br]
## [member delta_milli] : the time delta between a frame in milliseconds
@warning_ignore("unused_parameter")
-func simulate_frames(frames: int, delta_milli :int = -1) -> GdUnitSceneRunner:
- await Engine.get_main_loop().process_frame
+func simulate_frames(frames: int, delta_milli: int = -1) -> GdUnitSceneRunner:
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
@@ -153,18 +282,18 @@ func simulate_frames(frames: int, delta_milli :int = -1) -> GdUnitSceneRunner:
## [member args] : optional signal arguments to be matched for stop[br]
@warning_ignore("unused_parameter")
func simulate_until_signal(
- signal_name :String,
- arg0 :Variant = NO_ARG,
- arg1 :Variant = NO_ARG,
- arg2 :Variant = NO_ARG,
- arg3 :Variant = NO_ARG,
- arg4 :Variant = NO_ARG,
- arg5 :Variant = NO_ARG,
- arg6 :Variant = NO_ARG,
- arg7 :Variant = NO_ARG,
- arg8 :Variant = NO_ARG,
- arg9 :Variant = NO_ARG) -> GdUnitSceneRunner:
- await Engine.get_main_loop().process_frame
+ signal_name: String,
+ arg0: Variant = NO_ARG,
+ arg1: Variant = NO_ARG,
+ arg2: Variant = NO_ARG,
+ arg3: Variant = NO_ARG,
+ arg4: Variant = NO_ARG,
+ arg5: Variant = NO_ARG,
+ arg6: Variant = NO_ARG,
+ arg7: Variant = NO_ARG,
+ arg8: Variant = NO_ARG,
+ arg9: Variant = NO_ARG) -> GdUnitSceneRunner:
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
@@ -174,64 +303,103 @@ func simulate_until_signal(
## [member args] : optional signal arguments to be matched for stop
@warning_ignore("unused_parameter")
func simulate_until_object_signal(
- source :Object,
- signal_name :String,
- arg0 :Variant = NO_ARG,
- arg1 :Variant = NO_ARG,
- arg2 :Variant = NO_ARG,
- arg3 :Variant = NO_ARG,
- arg4 :Variant = NO_ARG,
- arg5 :Variant = NO_ARG,
- arg6 :Variant = NO_ARG,
- arg7 :Variant = NO_ARG,
- arg8 :Variant = NO_ARG,
- arg9 :Variant = NO_ARG) -> GdUnitSceneRunner:
- await Engine.get_main_loop().process_frame
+ source: Object,
+ signal_name: String,
+ arg0: Variant = NO_ARG,
+ arg1: Variant = NO_ARG,
+ arg2: Variant = NO_ARG,
+ arg3: Variant = NO_ARG,
+ arg4: Variant = NO_ARG,
+ arg5: Variant = NO_ARG,
+ arg6: Variant = NO_ARG,
+ arg7: Variant = NO_ARG,
+ arg8: Variant = NO_ARG,
+ arg9: Variant = NO_ARG) -> GdUnitSceneRunner:
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
-### Waits for all input events are processed
+## Waits for all input events to be processed by flushing any buffered input events
+## and then awaiting a full cycle of both the process and physics frames.[br]
+## [br]
+## This is typically used to ensure that any simulated or queued inputs are fully
+## processed before proceeding with the next steps in the scene.[br]
+## It's essential for reliable input simulation or when synchronizing logic based
+## on inputs.[br]
+##
+## Usage Example:
+## [codeblock]
+## await await_input_processed() # Ensure all inputs are processed before continuing
+## [/codeblock]
func await_input_processed() -> void:
- await Engine.get_main_loop().process_frame
- await Engine.get_main_loop().physics_frame
+ if scene() != null and scene().process_mode != Node.PROCESS_MODE_DISABLED:
+ Input.flush_buffered_events()
+ await (Engine.get_main_loop() as SceneTree).process_frame
+ await (Engine.get_main_loop() as SceneTree).physics_frame
-## Waits for the function return value until specified timeout or fails.[br]
-## [member args] : optional function arguments
+## The await_func function pauses execution until a specified function in the scene returns a value.[br]
+## It returns a [GdUnitFuncAssert], which provides a suite of assertion methods to verify the returned value.[br]
+## [member func_name] : The name of the function to wait for.[br]
+## [member args] : Optional function arguments
+## [br]
+## Usage Example:
+## [codeblock]
+## # Waits for 'calculate_score' function and verifies the result is equal to 100.
+## await_func("calculate_score").is_equal(100)
+## [/codeblock]
@warning_ignore("unused_parameter")
-func await_func(func_name :String, args := []) -> GdUnitFuncAssert:
+func await_func(func_name: String, args := []) -> GdUnitFuncAssert:
return null
-## Waits for the function return value of specified source until specified timeout or fails.[br]
-## [member source : the object where implements the function[br]
+
+## The await_func_on function extends the functionality of await_func by allowing you to specify a source node within the scene.[br]
+## It waits for a specified function on that node to return a value and returns a [GdUnitFuncAssert] object for assertions.[br]
+## [member source] : The object where implements the function.[br]
+## [member func_name] : The name of the function to wait for.[br]
## [member args] : optional function arguments
+## [br]
+## Usage Example:
+## [codeblock]
+## # Waits for 'calculate_score' function and verifies the result is equal to 100.
+## var my_instance := ScoreCalculator.new()
+## await_func(my_instance, "calculate_score").is_equal(100)
+## [/codeblock]
@warning_ignore("unused_parameter")
-func await_func_on(source :Object, func_name :String, args := []) -> GdUnitFuncAssert:
+func await_func_on(source: Object, func_name: String, args := []) -> GdUnitFuncAssert:
return null
-## Waits for given signal is emited by the scene until a specified timeout to fail.[br]
-## [member signal_name] : signal name[br]
-## [member args] : the expected signal arguments as an array[br]
-## [member timeout] : the timeout in ms, default is set to 2000ms
+## Waits for the specified signal to be emitted by the scene. If the signal is not emitted within the given timeout, the operation fails.[br]
+## [member signal_name] : The name of the signal to wait for[br]
+## [member args] : The signal arguments as an array[br]
+## [member timeout] : The maximum duration (in milliseconds) to wait for the signal to be emitted before failing
@warning_ignore("unused_parameter")
-func await_signal(signal_name :String, args := [], timeout := 2000 ) -> void:
- await Engine.get_main_loop().process_frame
+func await_signal(signal_name: String, args := [], timeout := 2000 ) -> void:
+ await (Engine.get_main_loop() as SceneTree).process_frame
pass
-## Waits for given signal is emited by the until a specified timeout to fail.[br]
+## Waits for the specified signal to be emitted by a particular source node. If the signal is not emitted within the given timeout, the operation fails.[br]
## [member source] : the object from which the signal is emitted[br]
-## [member signal_name] : signal name[br]
-## [member args] : the expected signal arguments as an array[br]
-## [member timeout] : the timeout in ms, default is set to 2000ms
+## [member signal_name] : The name of the signal to wait for[br]
+## [member args] : The signal arguments as an array[br]
+## [member timeout] : tThe maximum duration (in milliseconds) to wait for the signal to be emitted before failing
@warning_ignore("unused_parameter")
-func await_signal_on(source :Object, signal_name :String, args := [], timeout := 2000 ) -> void:
+func await_signal_on(source: Object, signal_name: String, args := [], timeout := 2000 ) -> void:
pass
-## maximizes the window to bring the scene visible
+## Restores the scene window to a windowed mode and brings it to the foreground.[br]
+## This ensures that the scene is visible and active during testing, making it easier to observe and interact with.
+func move_window_to_foreground() -> GdUnitSceneRunner:
+ return self
+
+
+## Restores the scene window to a windowed mode and brings it to the foreground.[br]
+## This ensures that the scene is visible and active during testing, making it easier to observe and interact with.
+## @deprecated: Use [move_window_to_foreground] instead.
func maximize_view() -> GdUnitSceneRunner:
return self
@@ -240,7 +408,7 @@ func maximize_view() -> GdUnitSceneRunner:
## [member name] : name of property[br]
## [member return] : the value of the property
@warning_ignore("unused_parameter")
-func get_property(name :String) -> Variant:
+func get_property(name: String) -> Variant:
return null
## Set the value of the property with the name .[br]
@@ -248,7 +416,7 @@ func get_property(name :String) -> Variant:
## [member value] : value of property[br]
## [member return] : true|false depending on valid property name.
@warning_ignore("unused_parameter")
-func set_property(name :String, value :Variant) -> bool:
+func set_property(name: String, value: Variant) -> bool:
return false
@@ -258,17 +426,17 @@ func set_property(name :String, value :Variant) -> bool:
## [member return] : the function result
@warning_ignore("unused_parameter")
func invoke(
- name :String,
- arg0 :Variant = NO_ARG,
- arg1 :Variant = NO_ARG,
- arg2 :Variant = NO_ARG,
- arg3 :Variant = NO_ARG,
- arg4 :Variant = NO_ARG,
- arg5 :Variant = NO_ARG,
- arg6 :Variant = NO_ARG,
- arg7 :Variant = NO_ARG,
- arg8 :Variant = NO_ARG,
- arg9 :Variant = NO_ARG) -> Variant:
+ name: String,
+ arg0: Variant = NO_ARG,
+ arg1: Variant = NO_ARG,
+ arg2: Variant = NO_ARG,
+ arg3: Variant = NO_ARG,
+ arg4: Variant = NO_ARG,
+ arg5: Variant = NO_ARG,
+ arg6: Variant = NO_ARG,
+ arg7: Variant = NO_ARG,
+ arg8: Variant = NO_ARG,
+ arg9: Variant = NO_ARG) -> Variant:
return null
@@ -277,7 +445,7 @@ func invoke(
## [member recursive] : enables/disables seraching recursive[br]
## [member return] : the node if find otherwise null
@warning_ignore("unused_parameter")
-func find_child(name :String, recursive :bool = true, owned :bool = false) -> Node:
+func find_child(name: String, recursive: bool = true, owned: bool = false) -> Node:
return null
diff --git a/addons/gdUnit4/src/GdUnitSignalAssert.gd b/addons/gdUnit4/src/GdUnitSignalAssert.gd
index 8150df23..9dbc76d3 100644
--- a/addons/gdUnit4/src/GdUnitSignalAssert.gd
+++ b/addons/gdUnit4/src/GdUnitSignalAssert.gd
@@ -6,14 +6,14 @@ extends GdUnitAssert
## Verifies that given signal is emitted until waiting time
@warning_ignore("unused_parameter")
func is_emitted(name :String, args := []) -> GdUnitSignalAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
## Verifies that given signal is NOT emitted until waiting time
@warning_ignore("unused_parameter")
func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return self
diff --git a/addons/gdUnit4/src/GdUnitTestSuite.gd b/addons/gdUnit4/src/GdUnitTestSuite.gd
index f23cbd74..13241f4a 100644
--- a/addons/gdUnit4/src/GdUnitTestSuite.gd
+++ b/addons/gdUnit4/src/GdUnitTestSuite.gd
@@ -23,8 +23,6 @@ var __is_skipped := false
var __skip_reason :String = "Unknow."
var __active_test_case :String
var __awaiter := __gdunit_awaiter()
-# holds the actual 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"
@@ -100,25 +98,25 @@ func error_as_string(error_number :int) -> String:
## A litle helper to auto freeing your created objects after test execution
func auto_free(obj :Variant) -> Variant:
- if __execution_context != null:
- return __execution_context.register_auto_free(obj)
- else:
- if is_instance_valid(obj):
- obj.queue_free()
- return obj
+ var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
+
+ assert(execution_context != null, "INTERNAL ERROR: The current execution_context is null! Please report this as bug.")
+ return execution_context.register_auto_free(obj)
@warning_ignore("native_method_override")
func add_child(node :Node, force_readable_name := false, internal := Node.INTERNAL_MODE_DISABLED) -> void:
super.add_child(node, force_readable_name, internal)
- if __execution_context != null:
- __execution_context.orphan_monitor_start()
+ var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
+ if execution_context != null:
+ execution_context.orphan_monitor_start()
## Discard the error message triggered by a timeout (interruption).[br]
## By default, an interrupted test is reported as an error.[br]
## This function allows you to change the message to Success when an interrupted error is reported.
func discard_error_interupted_by_timeout() -> void:
+ @warning_ignore("unsafe_method_access")
__gdunit_tools().register_expect_interupted_by_timeout(self, __active_test_case)
@@ -126,12 +124,14 @@ 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:
+ @warning_ignore("unsafe_method_access")
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() -> void:
+ @warning_ignore("unsafe_method_access")
__gdunit_file_access().clear_tmp()
@@ -139,28 +139,26 @@ func clean_temp_dir() -> void:
## 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:
+ @warning_ignore("unsafe_method_access")
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:
+ @warning_ignore("unsafe_method_access")
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:
+ @warning_ignore("unsafe_method_access")
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) -> Variant:
- return str_to_var(__gdunit_file_access().resource_as_string(resource_path))
-
-
-## clears the debuger error list[br]
-## PROTOTYPE!!!! Don't use it for now
-func clear_push_errors() -> void:
- __gdunit_tools().clear_push_errors()
+ @warning_ignore("unsafe_method_access", "unsafe_cast")
+ return str_to_var(__gdunit_file_access().resource_as_string(resource_path) as String)
## Waits for given signal is emited by the until a specified timeout to fail[br]
@@ -169,11 +167,13 @@ func clear_push_errors() -> void:
## args: the expected signal arguments as an array[br]
## timeout: the timeout in ms, default is set to 2000ms
func await_signal_on(source :Object, signal_name :String, args :Array = [], timeout :int = 2000) -> Variant:
+ @warning_ignore("unsafe_method_access")
return await __awaiter.await_signal_on(source, signal_name, args, timeout)
## Waits until the next idle frame
func await_idle_frame() -> void:
+ @warning_ignore("unsafe_method_access")
await __awaiter.await_idle_frame()
@@ -185,6 +185,7 @@ func await_idle_frame() -> void:
## [/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) -> void:
+ @warning_ignore("unsafe_method_access")
await __awaiter.await_millis(timeout)
@@ -216,11 +217,13 @@ const RETURN_DEEP_STUB = GdUnitMock.RETURN_DEEP_STUB
## Creates a mock for given class name
func mock(clazz :Variant, mock_mode := RETURN_DEFAULTS) -> Variant:
+ @warning_ignore("unsafe_method_access")
return __lazy_load("res://addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd").build(clazz, mock_mode)
## Creates a spy checked given object instance
func spy(instance :Variant) -> Variant:
+ @warning_ignore("unsafe_method_access")
return __lazy_load("res://addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd").build(instance)
@@ -236,21 +239,25 @@ func do_return(value :Variant) -> GdUnitMock:
## Verifies certain behavior happened at least once or exact number of times
func verify(obj :Variant, times := 1) -> Variant:
+ @warning_ignore("unsafe_method_access")
return __gdunit_object_interactions().verify(obj, times)
## Verifies no interactions is happen checked this mock or spy
func verify_no_interactions(obj :Variant) -> GdUnitAssert:
+ @warning_ignore("unsafe_method_access")
return __gdunit_object_interactions().verify_no_interactions(obj)
## Verifies the given mock or spy has any unverified interaction.
func verify_no_more_interactions(obj :Variant) -> GdUnitAssert:
+ @warning_ignore("unsafe_method_access")
return __gdunit_object_interactions().verify_no_more_interactions(obj)
## Resets the saved function call counters checked a mock or spy
func reset(obj :Variant) -> void:
+ @warning_ignore("unsafe_method_access")
__gdunit_object_interactions().reset(obj)
@@ -267,6 +274,7 @@ func reset(obj :Variant) -> void:
## await assert_signal(emitter).is_emitted('my_signal')
## [/codeblock]
func monitor_signals(source :Object, _auto_free := true) -> Object:
+ @warning_ignore("unsafe_method_access")
__lazy_load("res://addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd")\
.get_current_context()\
.get_signal_collector()\
@@ -277,36 +285,43 @@ func monitor_signals(source :Object, _auto_free := true) -> Object:
# === Argument matchers ========================================================
## Argument matcher to match any argument
func any() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().any()
## Argument matcher to match any boolean value
func any_bool() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_BOOL)
## Argument matcher to match any integer value
func any_int() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_INT)
## Argument matcher to match any float value
func any_float() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_FLOAT)
## Argument matcher to match any string value
func any_string() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_STRING)
## Argument matcher to match any Color value
func any_color() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_COLOR)
## Argument matcher to match any Vector typed value
func any_vector() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_types([
TYPE_VECTOR2,
TYPE_VECTOR2I,
@@ -319,141 +334,169 @@ func any_vector() -> GdUnitArgumentMatcher:
## Argument matcher to match any Vector2 value
func any_vector2() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR2)
## Argument matcher to match any Vector2i value
func any_vector2i() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR2I)
## Argument matcher to match any Vector3 value
func any_vector3() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR3)
## Argument matcher to match any Vector3i value
func any_vector3i() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR3I)
## Argument matcher to match any Vector4 value
func any_vector4() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR4)
## Argument matcher to match any Vector3i value
func any_vector4i() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR4I)
## Argument matcher to match any Rect2 value
func any_rect2() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_RECT2)
## Argument matcher to match any Plane value
func any_plane() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PLANE)
## Argument matcher to match any Quaternion value
func any_quat() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_QUATERNION)
## Argument matcher to match any AABB value
func any_aabb() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_AABB)
## Argument matcher to match any Basis value
func any_basis() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_BASIS)
## Argument matcher to match any Transform2D value
func any_transform_2d() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM2D)
## Argument matcher to match any Transform3D value
func any_transform_3d() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM3D)
## Argument matcher to match any NodePath value
func any_node_path() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_NODE_PATH)
## Argument matcher to match any RID value
func any_rid() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_RID)
## Argument matcher to match any Object value
func any_object() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_OBJECT)
## Argument matcher to match any Dictionary value
func any_dictionary() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_DICTIONARY)
## Argument matcher to match any Array value
func any_array() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_ARRAY)
## Argument matcher to match any PackedByteArray value
func any_packed_byte_array() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_BYTE_ARRAY)
## Argument matcher to match any PackedInt32Array value
func any_packed_int32_array() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT32_ARRAY)
## Argument matcher to match any PackedInt64Array value
func any_packed_int64_array() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT64_ARRAY)
## Argument matcher to match any PackedFloat32Array value
func any_packed_float32_array() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT32_ARRAY)
## Argument matcher to match any PackedFloat64Array value
func any_packed_float64_array() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT64_ARRAY)
## Argument matcher to match any PackedStringArray value
func any_packed_string_array() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_STRING_ARRAY)
## Argument matcher to match any PackedVector2Array value
func any_packed_vector2_array() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR2_ARRAY)
## Argument matcher to match any PackedVector3Array value
func any_packed_vector3_array() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR3_ARRAY)
## Argument matcher to match any PackedColorArray value
func any_packed_color_array() -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_COLOR_ARRAY)
## Argument matcher to match any instance of given class
func any_class(clazz :Object) -> GdUnitArgumentMatcher:
+ @warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().any_class(clazz)
@@ -492,13 +535,13 @@ func assert_that(current :Variant) -> GdUnitAssert:
TYPE_STRING:
return assert_str(current)
TYPE_VECTOR2, TYPE_VECTOR2I, TYPE_VECTOR3, TYPE_VECTOR3I, TYPE_VECTOR4, TYPE_VECTOR4I:
- return assert_vector(current)
+ return assert_vector(current, false)
TYPE_DICTIONARY:
return assert_dict(current)
TYPE_ARRAY, TYPE_PACKED_BYTE_ARRAY, TYPE_PACKED_INT32_ARRAY, TYPE_PACKED_INT64_ARRAY,\
TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_FLOAT64_ARRAY, TYPE_PACKED_STRING_ARRAY,\
TYPE_PACKED_VECTOR2_ARRAY, TYPE_PACKED_VECTOR3_ARRAY, TYPE_PACKED_COLOR_ARRAY:
- return assert_array(current)
+ return assert_array(current, false)
TYPE_OBJECT, TYPE_NIL:
return assert_object(current)
_:
@@ -531,13 +574,13 @@ func assert_float(current :Variant) -> GdUnitFloatAssert:
## [codeblock]
## assert_vector(Vector2(1.2, 1.000001)).is_equal(Vector2(1.2, 1.000001))
## [/codeblock]
-func assert_vector(current :Variant) -> GdUnitVectorAssert:
- return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd").new(current)
+func assert_vector(current :Variant, type_check := true) -> GdUnitVectorAssert:
+ return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd").new(current, type_check)
## An assertion tool to verify arrays.
-func assert_array(current :Variant) -> GdUnitArrayAssert:
- return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd").new(current)
+func assert_array(current :Variant, type_check := true) -> GdUnitArrayAssert:
+ return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd").new(current, type_check)
## An assertion tool to verify dictionaries.
@@ -577,6 +620,7 @@ func assert_signal(instance :Object) -> GdUnitSignalAssert:
## .has_message("Expecting:\n 'true'\n not equal to\n 'true'")
## [/codeblock]
func assert_failure(assertion :Callable) -> GdUnitFailureAssert:
+ @warning_ignore("unsafe_method_access")
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute(assertion)
@@ -588,6 +632,7 @@ func assert_failure(assertion :Callable) -> GdUnitFailureAssert:
## .has_message("Expecting:\n 'true'\n not equal to\n 'true'")
## [/codeblock]
func assert_failure_await(assertion :Callable) -> GdUnitFailureAssert:
+ @warning_ignore("unsafe_method_access")
return await __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute_and_await(assertion)
@@ -608,10 +653,12 @@ func assert_error(current :Callable) -> GdUnitGodotErrorAssert:
func assert_not_yet_implemented() -> void:
- __gdunit_assert().new(null).test_fail()
+ @warning_ignore("unsafe_method_access")
+ __gdunit_assert().new(null).do_fail()
func fail(message :String) -> void:
+ @warning_ignore("unsafe_method_access")
__gdunit_assert().new(null).report_error(message)
diff --git a/addons/gdUnit4/src/asserts/GdAssertMessages.gd b/addons/gdUnit4/src/asserts/GdAssertMessages.gd
index 315a0a52..83c2892c 100644
--- a/addons/gdUnit4/src/asserts/GdAssertMessages.gd
+++ b/addons/gdUnit4/src/asserts/GdAssertMessages.gd
@@ -8,8 +8,12 @@ const SUB_COLOR := Color(1, 0, 0, .3)
const ADD_COLOR := Color(0, 1, 0, .3)
-static func format_dict(value :Dictionary) -> String:
- if value.is_empty():
+static func format_dict(value :Variant) -> String:
+ if not value is Dictionary:
+ return str(value)
+
+ var dict_value: Dictionary = value
+ if dict_value.is_empty():
return "{ }"
var as_rows := var_to_str(value).split("\n")
for index in range( 1, as_rows.size()-1):
@@ -22,15 +26,22 @@ static func format_dict(value :Dictionary) -> String:
static func input_event_as_text(event :InputEvent) -> String:
var text := ""
if event is InputEventKey:
+ var key_event := event as InputEventKey
text += "InputEventKey : key='%s', pressed=%s, keycode=%d, physical_keycode=%s" % [
- event.as_text(), event.pressed, event.keycode, event.physical_keycode]
+ event.as_text(), key_event.pressed, key_event.keycode, key_event.physical_keycode]
else:
text += event.as_text()
if event is InputEventMouse:
- text += ", global_position %s" % event.global_position
+ var mouse_event := event as InputEventMouse
+ text += ", global_position %s" % mouse_event.global_position
if event is InputEventWithModifiers:
+ var mouse_event := event as 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]
+ mouse_event.shift_pressed,
+ mouse_event.alt_pressed,
+ mouse_event.ctrl_pressed,
+ mouse_event.meta_pressed,
+ mouse_event.command_or_control_autoremap]
return text
@@ -51,9 +62,11 @@ static func colored_array_div(characters :PackedByteArray) -> String:
match character:
GdDiffTool.DIV_ADD:
index += 1
+ @warning_ignore("return_value_discarded")
additional_chars.append(characters[index])
GdDiffTool.DIV_SUB:
index += 1
+ @warning_ignore("return_value_discarded")
missing_chars.append(characters[index])
_:
if not missing_chars.is_empty():
@@ -62,6 +75,7 @@ static func colored_array_div(characters :PackedByteArray) -> String:
if not additional_chars.is_empty():
result.append_array(format_chars(additional_chars, ADD_COLOR))
additional_chars = PackedByteArray()
+ @warning_ignore("return_value_discarded")
result.append(character)
index += 1
@@ -95,7 +109,7 @@ static func _nerror(number :Variant) -> String:
static func _colored_value(value :Variant) -> String:
match typeof(value):
TYPE_STRING, TYPE_STRING_NAME:
- return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _colored_string_div(value)]
+ return "'[color=%s]%s[/color]'" % [VALUE_COLOR, _colored_string_div(str(value))]
TYPE_INT:
return "'[color=%s]%d[/color]'" % [VALUE_COLOR, value]
TYPE_FLOAT:
@@ -106,10 +120,12 @@ static func _colored_value(value :Variant) -> String:
if value == null:
return "'[color=%s][/color]'" % [VALUE_COLOR]
if value is InputEvent:
- return "[color=%s]<%s>[/color]" % [VALUE_COLOR, input_event_as_text(value)]
- if value.has_method("_to_string"):
+ var ie: InputEvent = value
+ return "[color=%s]<%s>[/color]" % [VALUE_COLOR, input_event_as_text(ie)]
+ var obj_value: Object = value
+ if obj_value.has_method("_to_string"):
return "[color=%s]<%s>[/color]" % [VALUE_COLOR, str(value)]
- return "[color=%s]<%s>[/color]" % [VALUE_COLOR, value.get_class()]
+ return "[color=%s]<%s>[/color]" % [VALUE_COLOR, obj_value.get_class()]
TYPE_DICTIONARY:
return "'[color=%s]%s[/color]'" % [VALUE_COLOR, format_dict(value)]
_:
@@ -336,6 +352,7 @@ static func error_ends_with(current :Variant, expected :Variant) -> String:
static func error_has_length(current :Variant, expected: int, compare_operator :int) -> String:
+ @warning_ignore("unsafe_method_access")
var current_length :Variant = current.length() if current != null else null
match compare_operator:
Comparator.EQUAL:
@@ -361,48 +378,50 @@ static func error_has_length(current :Variant, expected: int, compare_operator :
# - ArrayAssert specific messgaes ---------------------------------------------------
-static func error_arr_contains(current :Variant, expected :Array, not_expect :Array, not_found :Array, by_reference :bool) -> String:
+static func error_arr_contains(current: Variant, expected: Variant, not_expect: Variant, not_found: Variant, 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)]
- if not not_expect.is_empty():
+ if not is_empty(not_expect):
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
- if not not_found.is_empty():
- var prefix := "but" if not_expect.is_empty() else "and"
+ if not is_empty(not_found):
+ var prefix := "but" if is_empty(not_expect) else "and"
error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)]
return error
static func error_arr_contains_exactly(
- current :Variant,
- expected :Variant,
- not_expect :Variant,
- not_found :Variant, compare_mode :GdObjects.COMPARE_MODE) -> String:
+ current: Variant,
+ expected: Variant,
+ not_expect: Variant,
+ not_found: Variant, 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:"
)
- if not_expect.is_empty() and not_found.is_empty():
- var diff := _find_first_diff(current, expected)
+ if is_empty(not_expect) and is_empty(not_found):
+ var arr_current: Array = current
+ var arr_expected: Array = expected
+ var diff := _find_first_diff(arr_current, arr_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():
+ if not is_empty(not_expect):
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
- if not not_found.is_empty():
- var prefix := "but" if not_expect.is_empty() else "and"
+ if not is_empty(not_found):
+ var prefix := "but" if is_empty(not_expect) else "and"
error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)]
return error
static func error_arr_contains_exactly_in_any_order(
- current :Variant,
- expected :Array,
- not_expect :Array,
- not_found :Array,
- compare_mode :GdObjects.COMPARE_MODE) -> String:
+ current: Variant,
+ expected: Variant,
+ not_expect: Variant,
+ not_found: Variant,
+ compare_mode: GdObjects.COMPARE_MODE) -> String:
var failure_message := (
"Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
@@ -410,19 +429,19 @@ static func error_arr_contains_exactly_in_any_order(
)
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():
+ if not is_empty(not_expect):
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
- if not not_found.is_empty():
- var prefix := "but" if not_expect.is_empty() else "and"
+ if not is_empty(not_found):
+ var prefix := "but" if is_empty(not_expect) else "and"
error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)]
return error
-static func error_arr_not_contains(current :Array, expected :Array, found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String:
+static func error_arr_not_contains(current: Variant, expected: Variant, found: Variant, 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)]
- if not found.is_empty():
+ if not is_empty(found):
error += "\n but found elements:\n %s" % _colored_value(found)
return error
@@ -542,20 +561,25 @@ static func result_message(result :GdUnitResult) -> String:
# - Spy|Mock specific errors ----------------------------------------------------
static func error_no_more_interactions(summary :Dictionary) -> String:
var interactions := PackedStringArray()
- for args :Variant in summary.keys():
+ for args :Array in summary.keys():
var times :int = summary[args]
+ @warning_ignore("return_value_discarded")
interactions.append(_format_arguments(args, times))
return "%s\n%s\n%s" % [_error("Expecting no more interactions!"), _error("But found interactions on:"), "\n".join(interactions)]
-static func error_validate_interactions(current_interactions :Dictionary, expected_interactions :Dictionary) -> String:
- var interactions := PackedStringArray()
- for args :Variant in current_interactions.keys():
- 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])
+static func error_validate_interactions(current_interactions: Dictionary, expected_interactions: Dictionary) -> String:
+ var collected_interactions := PackedStringArray()
+ for args: Array in current_interactions.keys():
+ var times: int = current_interactions[args]
+ @warning_ignore("return_value_discarded")
+ collected_interactions.append(_format_arguments(args, times))
+
+ var arguments: Array = expected_interactions.keys()[0]
+ var interactions: int = expected_interactions.values()[0]
+ var expected_interaction := _format_arguments(arguments, interactions)
return "%s\n%s\n%s\n%s" % [
- _error("Expecting interaction on:"), expected_interaction, _error("But found interactions on:"), "\n".join(interactions)]
+ _error("Expecting interaction on:"), expected_interaction, _error("But found interactions on:"), "\n".join(collected_interactions)]
static func _format_arguments(args :Array, times :int) -> String:
@@ -569,13 +593,15 @@ static func _format_arguments(args :Array, times :int) -> String:
static func _to_typed_args(args :Array) -> PackedStringArray:
var typed := PackedStringArray()
for arg :Variant in args:
+ @warning_ignore("return_value_discarded")
typed.append(_format_arg(arg) + " :" + GdObjects.type_as_string(typeof(arg)))
return typed
static func _format_arg(arg :Variant) -> String:
if arg is InputEvent:
- return input_event_as_text(arg)
+ var ie: InputEvent = arg
+ return input_event_as_text(ie)
return str(arg)
@@ -589,6 +615,7 @@ static func _find_first_diff(left :Array, right :Array) -> String:
static func error_has_size(current :Variant, expected: int) -> String:
+ @warning_ignore("unsafe_method_access")
var current_size :Variant = null if current == null else current.size()
return "%s\n %s\n but was\n %s" % [_error("Expecting size:"), _colored_value(expected), _colored_value(current_size)]
@@ -623,3 +650,8 @@ static func build_failure_message(failure :String, additional_failure_message: S
%s
[color=LIME_GREEN][b]Additional info:[/b][/color]
%s""".dedent().trim_prefix("\n") % [message, additional_failure_message]
+
+
+static func is_empty(value: Variant) -> bool:
+ var arry_value: Array = value
+ return arry_value != null and arry_value.is_empty()
diff --git a/addons/gdUnit4/src/asserts/GdAssertReports.gd b/addons/gdUnit4/src/asserts/GdAssertReports.gd
index 1ac9e04b..06b72edb 100644
--- a/addons/gdUnit4/src/asserts/GdAssertReports.gd
+++ b/addons/gdUnit4/src/asserts/GdAssertReports.gd
@@ -51,5 +51,4 @@ static func current_failure() -> String:
static func send_report(report :GdUnitReport) -> void:
- var execution_context_id := GdUnitThreadManager.get_current_context().get_execution_context_id()
- GdUnitSignals.instance().gdunit_report.emit(execution_context_id, report)
+ GdUnitThreadManager.get_current_context().get_execution_context().add_report(report)
diff --git a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd
index c2c3cefa..748e20e6 100644
--- a/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd
@@ -1,21 +1,24 @@
+class_name GdUnitArrayAssertImpl
extends GdUnitArrayAssert
-var _base :GdUnitAssert
-var _current_value_provider :ValueProvider
+var _base: GdUnitAssertImpl
+var _current_value_provider: ValueProvider
+var _type_check: bool
-func _init(current :Variant) -> void:
+func _init(current: Variant, type_check := true) -> void:
+ _type_check = type_check
_current_value_provider = DefaultValueProvider.new(current)
- _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
- ResourceLoader.CACHE_MODE_REUSE).new(current)
+ _base = GdUnitAssertImpl.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):
+ @warning_ignore("return_value_discarded")
report_error("GdUnitArrayAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
-func _notification(event :int) -> void:
+func _notification(event: int) -> void:
if event == NOTIFICATION_PREDELETE:
if _base != null:
_base.notification(event)
@@ -27,7 +30,7 @@ func report_success() -> GdUnitArrayAssert:
return self
-func report_error(error :String) -> GdUnitArrayAssert:
+func report_error(error: String) -> GdUnitArrayAssert:
_base.report_error(error)
return self
@@ -36,17 +39,17 @@ func failure_message() -> String:
return _base.failure_message()
-func override_failure_message(message :String) -> GdUnitArrayAssert:
+func override_failure_message(message: String) -> GdUnitArrayAssert:
_base.override_failure_message(message)
return self
-func append_failure_message(message :String) -> GdUnitArrayAssert:
+func append_failure_message(message: String) -> GdUnitArrayAssert:
_base.append_failure_message(message)
return self
-func _validate_value_type(value :Variant) -> bool:
+func _validate_value_type(value: Variant) -> bool:
return value == null or GdArrayTools.is_array_type(value)
@@ -54,15 +57,23 @@ func get_current_value() -> Variant:
return _current_value_provider.get_value()
-func max_length(left :Variant, right :Variant) -> int:
+func max_length(left: Variant, right: Variant) -> int:
var ls := str(left).length()
var rs := str(right).length()
return rs if ls < rs else ls
-func _array_equals_div(current :Array, expected :Array, case_sensitive :bool = false) -> Array:
- var current_value := PackedStringArray(current)
- var expected_value := PackedStringArray(expected)
+# gdlint: disable=function-name
+func _toPackedStringArray(value: Variant) -> PackedStringArray:
+ if GdArrayTools.is_array_type(value):
+ @warning_ignore("unsafe_cast")
+ return PackedStringArray(value as Array)
+ return PackedStringArray([str(value)])
+
+
+func _array_equals_div(current: Variant, expected: Variant, case_sensitive: bool = false) -> Array[Array]:
+ var current_value := _toPackedStringArray(current)
+ var expected_value := _toPackedStringArray(expected)
var index_report := Array()
for index in current_value.size():
var c := current_value[index]
@@ -72,25 +83,25 @@ func _array_equals_div(current :Array, expected :Array, case_sensitive :bool = f
var length := max_length(c, 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})
+ index_report.push_back({"index": index, "current": c, "expected": e})
else:
current_value[index] = GdAssertMessages.format_invalid(c)
- index_report.push_back({"index" : index, "current" :c, "expected": ""})
+ index_report.push_back({"index": index, "current": c, "expected": ""})
- for index in range(current.size(), expected_value.size()):
+ for index in range(current_value.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})
+ 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]:
+func _array_div(compare_mode: GdObjects.COMPARE_MODE, left: Array[Variant], right: Array[Variant], _same_order := false) -> Array[Variant]:
var not_expect := left.duplicate(true)
var not_found := right.duplicate(true)
for index_c in left.size():
- var c :Variant = left[index_c]
+ var c: Variant = left[index_c]
for index_e in right.size():
- var e :Variant = right[index_e]
+ var e: Variant = right[index_e]
if GdObjects.equals(c, e, false, compare_mode):
GdArrayTools.erase_value(not_expect, e)
GdArrayTools.erase_value(not_found, c)
@@ -98,241 +109,262 @@ func _array_div(compare_mode :GdObjects.COMPARE_MODE, left :Array[Variant], righ
return [not_expect, not_found]
-func _contains(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
+func _contains(expected: Variant, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
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_value :Variant = get_current_value()
+ var current_value: Variant = get_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)
+ @warning_ignore("unsafe_cast")
+ var diffs := _array_div(compare_mode, current_value as Array[Variant], expected as Array[Variant])
#var not_expect := diffs[0] as Array
- var not_found := diffs[1] as Array
+ var not_found: Array = diffs[1]
if not not_found.is_empty():
return report_error(GdAssertMessages.error_arr_contains(current_value, expected, [], not_found, by_reference))
return report_success()
-func _contains_exactly(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
+func _contains_exactly(expected: Variant, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
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_value :Variant = get_current_value()
+ var current_value: Variant = get_current_value()
if current_value == null:
- return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, [], expected, compare_mode))
+ return report_error(GdAssertMessages.error_arr_contains_exactly(null, expected, [], expected, compare_mode))
# has same content in same order
- if GdObjects.equals(Array(current_value), Array(expected), false, compare_mode):
+ if _is_equal(current_value, expected, false, compare_mode):
return report_success()
# check has same elements but in different order
- if GdObjects.equals_sorted(Array(current_value), Array(expected), false, compare_mode):
+ if _is_equals_sorted(current_value, 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_value, expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
- var not_expect := diffs[0] as Array[Variant]
- var not_found := diffs[1] as Array[Variant]
+ @warning_ignore("unsafe_cast")
+ var diffs := _array_div(compare_mode,
+ current_value as Array[Variant],
+ expected as Array[Variant],
+ GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
+ var not_expect: Array[Variant] = diffs[0]
+ var not_found: Array[Variant] = diffs[1]
return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected, not_expect, not_found, compare_mode))
-func _contains_exactly_in_any_order(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
+func _contains_exactly_in_any_order(expected: Variant, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
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_value :Variant = get_current_value()
+ var current_value: Variant = get_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_value, expected, false)
- var not_expect := diffs[0] as Array
- var not_found := diffs[1] as Array
+ @warning_ignore("unsafe_cast")
+ var diffs := _array_div(compare_mode, current_value as Array[Variant], expected as Array[Variant], false)
+ var not_expect: Array[Variant] = diffs[0]
+ var not_found: Array[Variant] = diffs[1]
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_value, expected, not_expect, not_found, compare_mode))
-func _not_contains(expected :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
+func _not_contains(expected: Variant, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
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_value :Variant = get_current_value()
+ var current_value: Variant = get_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_value.size():
+ @warning_ignore("unsafe_cast")
+ var diffs := _array_div(compare_mode, current_value as Array[Variant], expected as Array[Variant])
+ var found: Array[Variant] = diffs[0]
+ @warning_ignore("unsafe_cast")
+ if found.size() == (current_value as Array).size():
return report_success()
- var diffs2 := _array_div(compare_mode, expected, diffs[1])
+ @warning_ignore("unsafe_cast")
+ var diffs2 := _array_div(compare_mode, expected as Array[Variant], diffs[1] as Array[Variant])
return report_error(GdAssertMessages.error_arr_not_contains(current_value, expected, diffs2[0], compare_mode))
func is_null() -> GdUnitArrayAssert:
+ @warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitArrayAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_null()
return self
# Verifies that the current String is equal to the given one.
-func is_equal(expected :Variant) -> GdUnitArrayAssert:
- if not _validate_value_type(expected):
+func is_equal(expected: Variant) -> GdUnitArrayAssert:
+ if _type_check and 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_value :Variant = get_current_value()
+ var current_value: Variant = get_current_value()
if current_value == null and expected != null:
return report_error(GdAssertMessages.error_equal(null, expected))
- if not GdObjects.equals(current_value, expected):
+ if not _is_equal(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 :Variant = diff[2]
+ var index_report: Array = diff[2]
return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report))
return report_success()
# Verifies that the current Array is equal to the given one, ignoring case considerations.
-func is_equal_ignoring_case(expected :Variant) -> GdUnitArrayAssert:
- if not _validate_value_type(expected):
+func is_equal_ignoring_case(expected: Variant) -> GdUnitArrayAssert:
+ if _type_check and 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_value :Variant = get_current_value()
+ var current_value: Variant = get_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_value, expected, true):
- var diff := _array_equals_div(current_value, expected, true)
+ @warning_ignore("unsafe_cast")
+ return report_error(GdAssertMessages.error_equal(null, GdArrayTools.as_string(expected as Array)))
+ if not _is_equal(current_value, expected, true):
+ @warning_ignore("unsafe_cast")
+ var diff := _array_equals_div(current_value as Array[Variant], expected as Array[Variant], true)
var expected_as_list := GdArrayTools.as_string(diff[0])
var current_as_list := GdArrayTools.as_string(diff[1])
- var index_report :Variant = diff[2]
+ var index_report: Array = diff[2]
return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report))
return report_success()
-func is_not_equal(expected :Variant) -> GdUnitArrayAssert:
+func is_not_equal(expected: Variant) -> GdUnitArrayAssert:
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_value :Variant = get_current_value()
- if GdObjects.equals(current_value, expected):
+ var current_value: Variant = get_current_value()
+ if _is_equal(current_value, expected):
return report_error(GdAssertMessages.error_not_equal(current_value, expected))
return report_success()
-func is_not_equal_ignoring_case(expected :Variant) -> GdUnitArrayAssert:
+func is_not_equal_ignoring_case(expected: Variant) -> GdUnitArrayAssert:
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_value :Variant = get_current_value()
- if GdObjects.equals(current_value, expected, true):
- var c := GdArrayTools.as_string(current_value)
- var e := GdArrayTools.as_string(expected)
+ var current_value: Variant = get_current_value()
+ if _is_equal(current_value, expected, true):
+ @warning_ignore("unsafe_cast")
+ var c := GdArrayTools.as_string(current_value as Array)
+ @warning_ignore("unsafe_cast")
+ var e := GdArrayTools.as_string(expected as Array)
return report_error(GdAssertMessages.error_not_equal_case_insensetiv(c, e))
return report_success()
func is_empty() -> GdUnitArrayAssert:
- var current_value :Variant = get_current_value()
- if current_value == null or current_value.size() > 0:
+ var current_value: Variant = get_current_value()
+ @warning_ignore("unsafe_cast")
+ if current_value == null or (current_value as Array).size() > 0:
return report_error(GdAssertMessages.error_is_empty(current_value))
return report_success()
func is_not_empty() -> GdUnitArrayAssert:
- var current_value :Variant = get_current_value()
- if current_value != null and current_value.size() == 0:
+ var current_value: Variant = get_current_value()
+ @warning_ignore("unsafe_cast")
+ if current_value != null and (current_value as Array).size() == 0:
return report_error(GdAssertMessages.error_is_not_empty())
return report_success()
@warning_ignore("unused_parameter", "shadowed_global_identifier")
-func is_same(expected :Variant) -> GdUnitArrayAssert:
+func is_same(expected: Variant) -> GdUnitArrayAssert:
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 :Variant = get_current_value()
+ var current: Variant = get_current_value()
if not is_same(current, expected):
+ @warning_ignore("return_value_discarded")
report_error(GdAssertMessages.error_is_same(current, expected))
return self
-func is_not_same(expected :Variant) -> GdUnitArrayAssert:
+func is_not_same(expected: Variant) -> GdUnitArrayAssert:
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 :Variant = get_current_value()
+ var current: Variant = get_current_value()
if is_same(current, expected):
+ @warning_ignore("return_value_discarded")
report_error(GdAssertMessages.error_not_same(current, expected))
return self
func has_size(expected: int) -> GdUnitArrayAssert:
- var current_value :Variant= get_current_value()
- if current_value == null or current_value.size() != expected:
+ var current_value: Variant = get_current_value()
+ @warning_ignore("unsafe_cast")
+ if current_value == null or (current_value as Array).size() != expected:
return report_error(GdAssertMessages.error_has_size(current_value, expected))
return report_success()
-func contains(expected :Variant) -> GdUnitArrayAssert:
+func contains(expected: Variant) -> GdUnitArrayAssert:
return _contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
-func contains_exactly(expected :Variant) -> GdUnitArrayAssert:
+func contains_exactly(expected: Variant) -> GdUnitArrayAssert:
return _contains_exactly(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
-func contains_exactly_in_any_order(expected :Variant) -> GdUnitArrayAssert:
+func contains_exactly_in_any_order(expected: Variant) -> GdUnitArrayAssert:
return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
-func contains_same(expected :Variant) -> GdUnitArrayAssert:
+func contains_same(expected: Variant) -> GdUnitArrayAssert:
return _contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
-func contains_same_exactly(expected :Variant) -> GdUnitArrayAssert:
+func contains_same_exactly(expected: Variant) -> GdUnitArrayAssert:
return _contains_exactly(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
-func contains_same_exactly_in_any_order(expected :Variant) -> GdUnitArrayAssert:
+func contains_same_exactly_in_any_order(expected: Variant) -> GdUnitArrayAssert:
return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
-func not_contains(expected :Variant) -> GdUnitArrayAssert:
+func not_contains(expected: Variant) -> GdUnitArrayAssert:
return _not_contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
-func not_contains_same(expected :Variant) -> GdUnitArrayAssert:
+func not_contains_same(expected: Variant) -> GdUnitArrayAssert:
return _not_contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
-func is_instanceof(expected :Variant) -> GdUnitAssert:
+func is_instanceof(expected: Variant) -> GdUnitAssert:
+ @warning_ignore("unsafe_method_access")
_base.is_instanceof(expected)
return self
-func extract(func_name :String, args := Array()) -> GdUnitArrayAssert:
+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 :Variant = get_current_value()
+
+ var extractor := GdUnitFuncValueExtractor.new(func_name, args)
+ var current: Variant = get_current_value()
if current == null:
_current_value_provider = DefaultValueProvider.new(null)
else:
- for element :Variant in current:
+ for element: Variant in current:
extracted_elements.append(extractor.extract_value(element))
_current_value_provider = DefaultValueProvider.new(extracted_elements)
return self
func extractv(
- extr0 :GdUnitValueExtractor,
- extr1 :GdUnitValueExtractor = null,
- extr2 :GdUnitValueExtractor = null,
- extr3 :GdUnitValueExtractor = null,
- extr4 :GdUnitValueExtractor = null,
- extr5 :GdUnitValueExtractor = null,
- extr6 :GdUnitValueExtractor = null,
- extr7 :GdUnitValueExtractor = null,
- extr8 :GdUnitValueExtractor = null,
- extr9 :GdUnitValueExtractor = null) -> GdUnitArrayAssert:
- var extractors :Variant = GdArrayTools.filter_value([extr0, extr1, extr2, extr3, extr4, extr5, extr6, extr7, extr8, extr9], null)
+ extr0: GdUnitValueExtractor,
+ extr1: GdUnitValueExtractor = null,
+ extr2: GdUnitValueExtractor = null,
+ extr3: GdUnitValueExtractor = null,
+ extr4: GdUnitValueExtractor = null,
+ extr5: GdUnitValueExtractor = null,
+ extr6: GdUnitValueExtractor = null,
+ extr7: GdUnitValueExtractor = null,
+ extr8: GdUnitValueExtractor = null,
+ 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 :Variant = get_current_value()
+ var current: Variant = get_current_value()
if current == null:
_current_value_provider = DefaultValueProvider.new(null)
else:
for element: Variant in current:
- var ev :Array[Variant] = [
+ var ev: Array[Variant] = [
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
@@ -344,12 +376,44 @@ func extractv(
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG
]
- for index :int in extractors.size():
- var extractor :GdUnitValueExtractor = extractors[index]
+ @warning_ignore("unsafe_cast")
+ for index: int in (extractors as Array).size():
+ var extractor: GdUnitValueExtractor = extractors[index]
ev[index] = extractor.extract_value(element)
- if extractors.size() > 1:
+ @warning_ignore("unsafe_cast")
+ if (extractors as Array).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:
extracted_elements.append(ev[0])
_current_value_provider = DefaultValueProvider.new(extracted_elements)
return self
+
+
+@warning_ignore("incompatible_ternary")
+func _is_equal(
+ left: Variant,
+ right: Variant,
+ case_sensitive := false,
+ compare_mode := GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
+
+ @warning_ignore("unsafe_cast")
+ return GdObjects.equals(
+ (left as Array) if GdArrayTools.is_array_type(left) else left,
+ (right as Array) if GdArrayTools.is_array_type(right) else right,
+ case_sensitive,
+ compare_mode
+ )
+
+
+func _is_equals_sorted(
+ left: Variant,
+ right: Variant,
+ case_sensitive := false,
+ compare_mode := GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
+
+ @warning_ignore("unsafe_cast")
+ return GdObjects.equals_sorted(
+ left as Array,
+ right as Array,
+ case_sensitive,
+ compare_mode)
diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd
index 583461db..c08bc132 100644
--- a/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd
@@ -1,3 +1,4 @@
+class_name GdUnitAssertImpl
extends GdUnitAssert
@@ -15,8 +16,6 @@ func _init(current :Variant) -> void:
-
-
func failure_message() -> String:
return _current_failure_message
@@ -39,7 +38,7 @@ func report_error(failure :String, failure_line_number: int = -1) -> GdUnitAsser
return self
-func test_fail() -> GdUnitAssert:
+func do_fail() -> GdUnitAssert:
return report_error(GdAssertMessages.error_not_implemented())
diff --git a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd
index a1b12caf..897d63f4 100644
--- a/addons/gdUnit4/src/asserts/GdUnitAssertions.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitAssertions.gd
@@ -3,6 +3,7 @@ class_name GdUnitAssertions
extends RefCounted
+@warning_ignore("return_value_discarded")
func _init() -> void:
# preload all gdunit assertions to speedup testsuite loading time
# gdlint:disable=private-method-call
diff --git a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd
index b781e28d..5daebcec 100644
--- a/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd
@@ -1,14 +1,14 @@
extends GdUnitBoolAssert
-var _base: GdUnitAssert
+var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
- _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
- ResourceLoader.CACHE_MODE_REUSE).new(current)
+ _base = GdUnitAssertImpl.new(current)
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
if not GdUnitAssertions.validate_value_type(current, TYPE_BOOL):
+ @warning_ignore("return_value_discarded")
report_error("GdUnitBoolAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
@@ -38,33 +38,39 @@ func failure_message() -> String:
func override_failure_message(message :String) -> GdUnitBoolAssert:
+ @warning_ignore("return_value_discarded")
_base.override_failure_message(message)
return self
func append_failure_message(message :String) -> GdUnitBoolAssert:
+ @warning_ignore("return_value_discarded")
_base.append_failure_message(message)
return self
# Verifies that the current value is null.
func is_null() -> GdUnitBoolAssert:
+ @warning_ignore("return_value_discarded")
_base.is_null()
return self
# Verifies that the current value is not null.
func is_not_null() -> GdUnitBoolAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_null()
return self
-func is_equal(expected :Variant) -> GdUnitBoolAssert:
+func is_equal(expected: Variant) -> GdUnitBoolAssert:
+ @warning_ignore("return_value_discarded")
_base.is_equal(expected)
return self
-func is_not_equal(expected :Variant) -> GdUnitBoolAssert:
+func is_not_equal(expected: Variant) -> GdUnitBoolAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_equal(expected)
return self
diff --git a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd
index a4dae18a..17eba6ab 100644
--- a/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd
@@ -1,14 +1,14 @@
extends GdUnitDictionaryAssert
-var _base :GdUnitAssert
+var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
- _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
- ResourceLoader.CACHE_MODE_REUSE).new(current)
+ _base = GdUnitAssertImpl.new(current)
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
if not GdUnitAssertions.validate_value_type(current, TYPE_DICTIONARY):
+ @warning_ignore("return_value_discarded")
report_error("GdUnitDictionaryAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
@@ -34,11 +34,13 @@ func failure_message() -> String:
func override_failure_message(message :String) -> GdUnitDictionaryAssert:
+ @warning_ignore("return_value_discarded")
_base.override_failure_message(message)
return self
func append_failure_message(message :String) -> GdUnitDictionaryAssert:
+ @warning_ignore("return_value_discarded")
_base.append_failure_message(message)
return self
@@ -48,11 +50,13 @@ func current_value() -> Variant:
func is_null() -> GdUnitDictionaryAssert:
+ @warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitDictionaryAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_null()
return self
@@ -101,14 +105,16 @@ func is_not_same(expected :Variant) -> GdUnitDictionaryAssert:
func is_empty() -> GdUnitDictionaryAssert:
var current :Variant = current_value()
- if current == null or not current.is_empty():
+ @warning_ignore("unsafe_cast")
+ if current == null or not (current as Dictionary).is_empty():
return report_error(GdAssertMessages.error_is_empty(current))
return report_success()
func is_not_empty() -> GdUnitDictionaryAssert:
var current :Variant = current_value()
- if current == null or current.is_empty():
+ @warning_ignore("unsafe_cast")
+ if current == null or (current as Dictionary).is_empty():
return report_error(GdAssertMessages.error_is_not_empty())
return report_success()
@@ -117,7 +123,8 @@ func has_size(expected: int) -> GdUnitDictionaryAssert:
var current :Variant = current_value()
if current == null:
return report_error(GdAssertMessages.error_is_not_null())
- if current.size() != expected:
+ @warning_ignore("unsafe_cast")
+ if (current as Dictionary).size() != expected:
return report_error(GdAssertMessages.error_has_size(current, expected))
return report_success()
@@ -127,9 +134,11 @@ func _contains_keys(expected :Array, compare_mode :GdObjects.COMPARE_MODE) -> Gd
if current == null:
return report_error(GdAssertMessages.error_is_not_null())
# find expected keys
- var keys_not_found :Array = expected.filter(_filter_by_key.bind(current.keys(), compare_mode))
+ @warning_ignore("unsafe_cast")
+ var keys_not_found :Array = expected.filter(_filter_by_key.bind((current as Dictionary).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))
+ @warning_ignore("unsafe_cast")
+ return report_error(GdAssertMessages.error_contains_keys((current as Dictionary).keys() as Array, expected, keys_not_found, compare_mode))
return report_success()
@@ -138,11 +147,12 @@ func _contains_key_value(key :Variant, value :Variant, compare_mode :GdObjects.C
var expected := [key]
if current == null:
return report_error(GdAssertMessages.error_is_not_null())
- var keys_not_found :Array = expected.filter(_filter_by_key.bind(current.keys(), compare_mode))
+ var dict_current: Dictionary = current
+ var keys_not_found :Array = expected.filter(_filter_by_key.bind(dict_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))
- if not GdObjects.equals(current[key], value, false, compare_mode):
- return report_error(GdAssertMessages.error_contains_key_value(key, value, current[key], compare_mode))
+ return report_error(GdAssertMessages.error_contains_keys(dict_current.keys() as Array, expected, keys_not_found, compare_mode))
+ if not GdObjects.equals(dict_current[key], value, false, compare_mode):
+ return report_error(GdAssertMessages.error_contains_key_value(key, value, dict_current[key], compare_mode))
return report_success()
@@ -150,9 +160,10 @@ func _not_contains_keys(expected :Array, compare_mode :GdObjects.COMPARE_MODE) -
var current :Variant = current_value()
if current == null:
return report_error(GdAssertMessages.error_is_not_null())
- var keys_found :Array = current.keys().filter(_filter_by_key.bind(expected, compare_mode, true))
+ var dict_current: Dictionary = current
+ var keys_found :Array = dict_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_error(GdAssertMessages.error_not_contains_keys(dict_current.keys() as Array, expected, keys_found, compare_mode))
return report_success()
diff --git a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd
index b25a675e..845d5fa9 100644
--- a/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd
@@ -15,6 +15,7 @@ func execute_and_await(assertion :Callable, do_await := true) -> GdUnitFailureAs
_set_do_expect_fail(true)
var thread_context := GdUnitThreadManager.get_current_context()
thread_context.set_assert(null)
+ @warning_ignore("return_value_discarded")
GdUnitSignals.instance().gdunit_set_test_failed.connect(_on_test_failed)
# execute the given assertion as callable
if do_await:
@@ -28,11 +29,13 @@ 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 self
+ @warning_ignore("unsafe_method_access")
_failure_message = current_assert.failure_message()
return self
func execute(assertion :Callable) -> GdUnitFailureAssert:
+ @warning_ignore("return_value_discarded")
execute_and_await(assertion, false)
return self
@@ -42,12 +45,12 @@ func _on_test_failed(value :bool) -> void:
@warning_ignore("unused_parameter")
-func is_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert:
+func is_equal(_expected: Variant) -> GdUnitFailureAssert:
return _report_error("Not implemented")
@warning_ignore("unused_parameter")
-func is_not_equal(_expected :GdUnitAssert) -> GdUnitFailureAssert:
+func is_not_equal(_expected: Variant) -> GdUnitFailureAssert:
return _report_error("Not implemented")
@@ -79,13 +82,14 @@ func has_line(expected :int) -> GdUnitFailureAssert:
func has_message(expected :String) -> GdUnitFailureAssert:
+ @warning_ignore("return_value_discarded")
is_failed()
var expected_error := GdUnitTools.normalize_text(GdUnitTools.richtext_normalize(expected))
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])
- _report_error(GdAssertMessages.error_not_same_error(current, expected_error))
+ return _report_error(GdAssertMessages.error_not_same_error(current, expected_error))
return self
@@ -95,7 +99,7 @@ func contains_message(expected :String) -> GdUnitFailureAssert:
if not current_error.contains(expected_error):
var diffs := GdDiffTool.string_diff(current_error, expected_error)
var current := GdAssertMessages.colored_array_div(diffs[1])
- _report_error(GdAssertMessages.error_not_same_error(current, expected_error))
+ return _report_error(GdAssertMessages.error_not_same_error(current, expected_error))
return self
@@ -105,7 +109,7 @@ func starts_with_message(expected :String) -> GdUnitFailureAssert:
if current_error.find(expected_error) != 0:
var diffs := GdDiffTool.string_diff(current_error, expected_error)
var current := GdAssertMessages.colored_array_div(diffs[1])
- _report_error(GdAssertMessages.error_not_same_error(current, expected_error))
+ return _report_error(GdAssertMessages.error_not_same_error(current, expected_error))
return self
diff --git a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd
index 63683fce..f98bc933 100644
--- a/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd
@@ -2,15 +2,15 @@ extends GdUnitFileAssert
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
-var _base: GdUnitAssert
+var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
- _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
- ResourceLoader.CACHE_MODE_REUSE).new(current)
+ _base = GdUnitAssertImpl.new(current)
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
if not GdUnitAssertions.validate_value_type(current, TYPE_STRING):
+ @warning_ignore("return_value_discarded")
report_error("GdUnitFileAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
@@ -22,7 +22,7 @@ func _notification(event :int) -> void:
func current_value() -> String:
- return _base.current_value() as String
+ return _base.current_value()
func report_success() -> GdUnitFileAssert:
@@ -40,21 +40,25 @@ func failure_message() -> String:
func override_failure_message(message :String) -> GdUnitFileAssert:
+ @warning_ignore("return_value_discarded")
_base.override_failure_message(message)
return self
func append_failure_message(message :String) -> GdUnitFileAssert:
+ @warning_ignore("return_value_discarded")
_base.append_failure_message(message)
return self
func is_equal(expected :Variant) -> GdUnitFileAssert:
+ @warning_ignore("return_value_discarded")
_base.is_equal(expected)
return self
func is_not_equal(expected :Variant) -> GdUnitFileAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_equal(expected)
return self
@@ -84,17 +88,14 @@ func is_script() -> GdUnitFileAssert:
return report_success()
-func contains_exactly(expected_rows :Array) -> GdUnitFileAssert:
+func contains_exactly(expected_rows: Array) -> GdUnitFileAssert:
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)
+ var script: GDScript = load(current)
if script is GDScript:
- var instance :Variant = script.new()
- var source_code := GdScriptParser.to_unix_format(instance.get_script().source_code)
- GdUnitTools.free_instance(instance)
+ var source_code := GdScriptParser.to_unix_format(script.source_code)
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)
+ GdUnitArrayAssertImpl.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 27463fdb..05d05b84 100644
--- a/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd
@@ -1,14 +1,14 @@
extends GdUnitFloatAssert
-var _base: GdUnitAssert
+var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
- _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
- ResourceLoader.CACHE_MODE_REUSE).new(current)
+ _base = GdUnitAssertImpl.new(current)
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
if not GdUnitAssertions.validate_value_type(current, TYPE_FLOAT):
+ @warning_ignore("return_value_discarded")
report_error("GdUnitFloatAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
@@ -38,31 +38,37 @@ func failure_message() -> String:
func override_failure_message(message :String) -> GdUnitFloatAssert:
+ @warning_ignore("return_value_discarded")
_base.override_failure_message(message)
return self
func append_failure_message(message :String) -> GdUnitFloatAssert:
+ @warning_ignore("return_value_discarded")
_base.append_failure_message(message)
return self
func is_null() -> GdUnitFloatAssert:
+ @warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitFloatAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_null()
return self
-func is_equal(expected :float) -> GdUnitFloatAssert:
+func is_equal(expected :Variant) -> GdUnitFloatAssert:
+ @warning_ignore("return_value_discarded")
_base.is_equal(expected)
return self
-func is_not_equal(expected :float) -> GdUnitFloatAssert:
+func is_not_equal(expected :Variant) -> GdUnitFloatAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_equal(expected)
return self
@@ -116,14 +122,16 @@ func is_not_negative() -> GdUnitFloatAssert:
func is_zero() -> GdUnitFloatAssert:
var current :Variant = current_value()
- if current == null or not is_equal_approx(0.00000000, current):
+ @warning_ignore("unsafe_cast")
+ if current == null or not is_equal_approx(0.00000000, current as float):
return report_error(GdAssertMessages.error_is_zero(current))
return report_success()
func is_not_zero() -> GdUnitFloatAssert:
var current :Variant = current_value()
- if current == null or is_equal_approx(0.00000000, current):
+ @warning_ignore("unsafe_cast")
+ if current == null or is_equal_approx(0.00000000, current as float):
return report_error(GdAssertMessages.error_is_not_zero())
return report_success()
diff --git a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd
index c3992c76..b4a8038e 100644
--- a/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd
@@ -22,6 +22,7 @@ func _init(instance :Object, func_name :String, args := Array()) -> void:
GdUnitThreadManager.get_current_context().set_assert(self)
# verify at first the function name exists
if not instance.has_method(func_name):
+ @warning_ignore("return_value_discarded")
report_error("The function '%s' do not exists checked instance '%s'." % [func_name, instance])
_interrupted = true
else:
@@ -33,7 +34,7 @@ func _notification(_what :int) -> void:
_current_value_provider.dispose()
_current_value_provider = null
if is_instance_valid(_sleep_timer):
- Engine.get_main_loop().root.remove_child(_sleep_timer)
+ (Engine.get_main_loop() as SceneTree).root.remove_child(_sleep_timer)
_sleep_timer.stop()
_sleep_timer.free()
_sleep_timer = null
@@ -54,10 +55,6 @@ func failure_message() -> String:
return _current_failure_message
-func send_report(report :GdUnitReport)-> void:
- GdUnitSignals.instance().gdunit_report.emit(report)
-
-
func override_failure_message(message :String) -> GdUnitFuncAssert:
_custom_failure_message = message
return self
@@ -124,8 +121,10 @@ func _validate_callback(predicate :Callable, expected :Variant = null) -> void:
var time_scale := Engine.get_time_scale()
var timer := Timer.new()
timer.set_name("gdunit_funcassert_interrupt_timer_%d" % timer.get_instance_id())
- Engine.get_main_loop().root.add_child(timer)
+ var scene_tree := Engine.get_main_loop() as SceneTree
+ scene_tree.root.add_child(timer)
timer.add_to_group("GdUnitTimers")
+ @warning_ignore("return_value_discarded")
timer.timeout.connect(func do_interrupt() -> void:
_interrupted = true
, CONNECT_DEFERRED)
@@ -133,7 +132,7 @@ func _validate_callback(predicate :Callable, expected :Variant = null) -> void:
timer.start((_timeout/1000.0)*time_scale)
_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)
+ scene_tree.root.add_child(_sleep_timer)
while true:
var current :Variant = await next_current_value()
@@ -145,13 +144,20 @@ func _validate_callback(predicate :Callable, expected :Variant = null) -> void:
await _sleep_timer.timeout
_sleep_timer.stop()
- await Engine.get_main_loop().process_frame
+ await scene_tree.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("cb_"), expected, LocalTime.elapsed(_timeout)))
+ @warning_ignore("return_value_discarded")
+ report_error(GdAssertMessages.error_interrupted(
+ predicate_name.strip_edges().trim_prefix("cb_"),
+ expected,
+ LocalTime.elapsed(_timeout)
+ )
+ )
else:
+ @warning_ignore("return_value_discarded")
report_success()
_sleep_timer.free()
timer.free()
diff --git a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd
index f08da5bd..adf2a2eb 100644
--- a/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd
@@ -17,6 +17,7 @@ func _init(callable :Callable) -> void:
func _execute() -> Array[ErrorLogEntry]:
# execute the given code and monitor for runtime errors
if _callable == null or not _callable.is_valid():
+ @warning_ignore("return_value_discarded")
_report_error("Invalid Callable '%s'" % _callable)
else:
await _callable.call()
diff --git a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd
index bfcc0317..1527cac5 100644
--- a/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd
@@ -1,14 +1,14 @@
extends GdUnitIntAssert
-var _base: GdUnitAssert
+var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
- _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
- ResourceLoader.CACHE_MODE_REUSE).new(current)
+ _base = GdUnitAssertImpl.new(current)
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
if not GdUnitAssertions.validate_value_type(current, TYPE_INT):
+ @warning_ignore("return_value_discarded")
report_error("GdUnitIntAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
@@ -38,31 +38,37 @@ func failure_message() -> String:
func override_failure_message(message :String) -> GdUnitIntAssert:
+ @warning_ignore("return_value_discarded")
_base.override_failure_message(message)
return self
func append_failure_message(message :String) -> GdUnitIntAssert:
+ @warning_ignore("return_value_discarded")
_base.append_failure_message(message)
return self
func is_null() -> GdUnitIntAssert:
+ @warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitIntAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_null()
return self
-func is_equal(expected :int) -> GdUnitIntAssert:
+func is_equal(expected :Variant) -> GdUnitIntAssert:
+ @warning_ignore("return_value_discarded")
_base.is_equal(expected)
return self
-func is_not_equal(expected :int) -> GdUnitIntAssert:
+func is_not_equal(expected :Variant) -> GdUnitIntAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_equal(expected)
return self
diff --git a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd
index 4ea12d91..ce78a186 100644
--- a/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd
@@ -1,11 +1,10 @@
extends GdUnitObjectAssert
-var _base :GdUnitAssert
+var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
- _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
- ResourceLoader.CACHE_MODE_REUSE).new(current)
+ _base = GdUnitAssertImpl.new(current)
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
if (current != null
@@ -13,6 +12,7 @@ func _init(current :Variant) -> void:
or GdUnitAssertions.validate_value_type(current, TYPE_INT)
or GdUnitAssertions.validate_value_type(current, TYPE_FLOAT)
or GdUnitAssertions.validate_value_type(current, TYPE_STRING))):
+ @warning_ignore("return_value_discarded")
report_error("GdUnitObjectAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
@@ -42,31 +42,37 @@ func failure_message() -> String:
func override_failure_message(message :String) -> GdUnitObjectAssert:
+ @warning_ignore("return_value_discarded")
_base.override_failure_message(message)
return self
func append_failure_message(message :String) -> GdUnitObjectAssert:
+ @warning_ignore("return_value_discarded")
_base.append_failure_message(message)
return self
func is_equal(expected :Variant) -> GdUnitObjectAssert:
+ @warning_ignore("return_value_discarded")
_base.is_equal(expected)
return self
func is_not_equal(expected :Variant) -> GdUnitObjectAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_equal(expected)
return self
func is_null() -> GdUnitObjectAssert:
+ @warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitObjectAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_null()
return self
@@ -75,19 +81,15 @@ func is_not_null() -> GdUnitObjectAssert:
func is_same(expected :Variant) -> GdUnitObjectAssert:
var current :Variant = current_value()
if not is_same(current, expected):
- report_error(GdAssertMessages.error_is_same(current, expected))
- return self
- report_success()
- return self
+ return report_error(GdAssertMessages.error_is_same(current, expected))
+ return report_success()
func is_not_same(expected :Variant) -> GdUnitObjectAssert:
var current :Variant = current_value()
if is_same(current, expected):
- report_error(GdAssertMessages.error_not_same(current, expected))
- return self
- report_success()
- return self
+ return report_error(GdAssertMessages.error_not_same(current, expected))
+ return report_success()
func is_instanceof(type :Object) -> GdUnitObjectAssert:
@@ -95,10 +97,8 @@ func is_instanceof(type :Object) -> GdUnitObjectAssert:
if current == null or not is_instance_of(current, type):
var result_expected: = GdObjects.extract_class_name(type)
var result_current: = GdObjects.extract_class_name(current)
- report_error(GdAssertMessages.error_is_instanceof(result_current, result_expected))
- return self
- report_success()
- return self
+ return report_error(GdAssertMessages.error_is_instanceof(result_current, result_expected))
+ return report_success()
func is_not_instanceof(type :Variant) -> GdUnitObjectAssert:
@@ -106,9 +106,8 @@ func is_not_instanceof(type :Variant) -> GdUnitObjectAssert:
if is_instance_of(current, type):
var result: = GdObjects.extract_class_name(type)
if result.is_success():
- report_error("Expected not be a instance of <%s>" % result.value())
- else:
- push_error("Internal ERROR: %s" % result.error_message())
+ return report_error("Expected not be a instance of <%s>" % str(result.value()))
+
+ push_error("Internal ERROR: %s" % result.error_message())
return self
- report_success()
- return self
+ return report_success()
diff --git a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd
index 6e734e0b..8b6c7f26 100644
--- a/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd
@@ -1,14 +1,14 @@
extends GdUnitResultAssert
-var _base :GdUnitAssert
+var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
- _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
- ResourceLoader.CACHE_MODE_REUSE).new(current)
+ _base = GdUnitAssertImpl.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):
+ @warning_ignore("return_value_discarded")
report_error("GdUnitResultAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
@@ -24,7 +24,7 @@ func validate_value_type(value :Variant) -> bool:
func current_value() -> GdUnitResult:
- return _base.current_value() as GdUnitResult
+ return _base.current_value()
func report_success() -> GdUnitResultAssert:
@@ -42,21 +42,25 @@ func failure_message() -> String:
func override_failure_message(message :String) -> GdUnitResultAssert:
+ @warning_ignore("return_value_discarded")
_base.override_failure_message(message)
return self
func append_failure_message(message :String) -> GdUnitResultAssert:
+ @warning_ignore("return_value_discarded")
_base.append_failure_message(message)
return self
func is_null() -> GdUnitResultAssert:
+ @warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitResultAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_null()
return self
@@ -64,63 +68,50 @@ func is_not_null() -> GdUnitResultAssert:
func is_empty() -> GdUnitResultAssert:
var result := current_value()
if result == null or not result.is_empty():
- report_error(GdAssertMessages.error_result_is_empty(result))
- else:
- report_success()
- return self
+ return report_error(GdAssertMessages.error_result_is_empty(result))
+ return report_success()
func is_success() -> GdUnitResultAssert:
var result := current_value()
if result == null or not result.is_success():
- report_error(GdAssertMessages.error_result_is_success(result))
- else:
- report_success()
- return self
+ return report_error(GdAssertMessages.error_result_is_success(result))
+ return report_success()
func is_warning() -> GdUnitResultAssert:
var result := current_value()
if result == null or not result.is_warn():
- report_error(GdAssertMessages.error_result_is_warning(result))
- else:
- report_success()
- return self
+ return report_error(GdAssertMessages.error_result_is_warning(result))
+ return report_success()
func is_error() -> GdUnitResultAssert:
var result := current_value()
if result == null or not result.is_error():
- report_error(GdAssertMessages.error_result_is_error(result))
- else:
- report_success()
- return self
+ return report_error(GdAssertMessages.error_result_is_error(result))
+ return report_success()
func contains_message(expected :String) -> GdUnitResultAssert:
var result := current_value()
if result == null:
- report_error(GdAssertMessages.error_result_has_message("", expected))
- return self
+ return report_error(GdAssertMessages.error_result_has_message("", expected))
if result.is_success():
- report_error(GdAssertMessages.error_result_has_message_on_success(expected))
- elif result.is_error() and result.error_message() != expected:
- report_error(GdAssertMessages.error_result_has_message(result.error_message(), expected))
- elif result.is_warn() and result.warn_message() != expected:
- report_error(GdAssertMessages.error_result_has_message(result.warn_message(), expected))
- else:
- report_success()
- return self
+ return report_error(GdAssertMessages.error_result_has_message_on_success(expected))
+ if result.is_error() and result.error_message() != expected:
+ return report_error(GdAssertMessages.error_result_has_message(result.error_message(), expected))
+ if result.is_warn() and result.warn_message() != expected:
+ return report_error(GdAssertMessages.error_result_has_message(result.warn_message(), expected))
+ return report_success()
func is_value(expected :Variant) -> GdUnitResultAssert:
var result := current_value()
var value :Variant = null if result == null else result.value()
if not GdObjects.equals(value, expected):
- report_error(GdAssertMessages.error_result_is_value(value, expected))
- else:
- report_success()
- return self
+ return report_error(GdAssertMessages.error_result_is_value(value, expected))
+ return report_success()
func is_equal(expected :Variant) -> GdUnitResultAssert:
diff --git a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd
index 0a1302dd..c3d5d5d6 100644
--- a/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd
@@ -42,10 +42,6 @@ func failure_message() -> String:
return _current_failure_message
-func send_report(report :GdUnitReport)-> void:
- GdUnitSignals.instance().gdunit_report.emit(report)
-
-
func override_failure_message(message :String) -> GdUnitSignalAssert:
_custom_failure_message = message
return self
@@ -58,6 +54,7 @@ func append_failure_message(message :String) -> GdUnitSignalAssert:
func wait_until(timeout := 2000) -> GdUnitSignalAssert:
if timeout <= 0:
+ @warning_ignore("return_value_discarded")
report_warning("Invalid timeout parameter, allowed timeouts must be greater than 0, use default timeout instead!")
_timeout = DEFAULT_TIMEOUT
else:
@@ -68,6 +65,7 @@ func wait_until(timeout := 2000) -> GdUnitSignalAssert:
# Verifies the signal exists checked the emitter
func is_signal_exists(signal_name :String) -> GdUnitSignalAssert:
if not _emitter.has_signal(signal_name):
+ @warning_ignore("return_value_discarded")
report_error("The signal '%s' not exists checked object '%s'." % [signal_name, _emitter.get_class()])
return self
@@ -86,29 +84,30 @@ func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert:
func _wail_until_signal(signal_name :String, expected_args :Array, expect_not_emitted: bool) -> GdUnitSignalAssert:
if _emitter == null:
- report_error("Can't wait for signal checked a NULL object.")
- return self
+ return report_error("Can't wait for signal checked a NULL object.")
# first verify the signal is defined
if not _emitter.has_signal(signal_name):
- report_error("Can't wait for non-existion signal '%s' checked object '%s'." % [signal_name,_emitter.get_class()])
- return self
+ return report_error("Can't wait for non-existion signal '%s' checked object '%s'." % [signal_name,_emitter.get_class()])
_signal_collector.register_emitter(_emitter)
var time_scale := Engine.get_time_scale()
var timer := Timer.new()
- Engine.get_main_loop().root.add_child(timer)
+ (Engine.get_main_loop() as SceneTree).root.add_child(timer)
timer.add_to_group("GdUnitTimers")
timer.set_one_shot(true)
+ @warning_ignore("return_value_discarded")
timer.timeout.connect(func on_timeout() -> void: _interrupted = true)
timer.start((_timeout/1000.0)*time_scale)
var is_signal_emitted := false
while not _interrupted and not is_signal_emitted:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
if is_instance_valid(_emitter):
is_signal_emitted = _signal_collector.match(_emitter, signal_name, expected_args)
if is_signal_emitted and expect_not_emitted:
+ @warning_ignore("return_value_discarded")
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:
+ @warning_ignore("return_value_discarded")
report_error(GdAssertMessages.error_wait_signal(signal_name, expected_args, LocalTime.elapsed(_timeout)))
timer.free()
if is_instance_valid(_emitter):
diff --git a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd
index cf2c44be..0f15956c 100644
--- a/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd
@@ -1,14 +1,14 @@
extends GdUnitStringAssert
-var _base :GdUnitAssert
+var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
- _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
- ResourceLoader.CACHE_MODE_REUSE).new(current)
+ _base = GdUnitAssertImpl.new(current)
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
if current != null and typeof(current) != TYPE_STRING and typeof(current) != TYPE_STRING_NAME:
+ @warning_ignore("return_value_discarded")
report_error("GdUnitStringAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
@@ -38,21 +38,25 @@ func report_error(error :String) -> GdUnitStringAssert:
func override_failure_message(message :String) -> GdUnitStringAssert:
+ @warning_ignore("return_value_discarded")
_base.override_failure_message(message)
return self
func append_failure_message(message :String) -> GdUnitStringAssert:
+ @warning_ignore("return_value_discarded")
_base.append_failure_message(message)
return self
func is_null() -> GdUnitStringAssert:
+ @warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitStringAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_null()
return self
@@ -95,49 +99,56 @@ func is_not_equal_ignoring_case(expected :Variant) -> GdUnitStringAssert:
func is_empty() -> GdUnitStringAssert:
var current :Variant = current_value()
- if current == null or not current.is_empty():
+ @warning_ignore("unsafe_cast")
+ if current == null or not (current as String).is_empty():
return report_error(GdAssertMessages.error_is_empty(current))
return report_success()
func is_not_empty() -> GdUnitStringAssert:
var current :Variant = current_value()
- if current == null or current.is_empty():
+ @warning_ignore("unsafe_cast")
+ if current == null or (current as String).is_empty():
return report_error(GdAssertMessages.error_is_not_empty())
return report_success()
func contains(expected :String) -> GdUnitStringAssert:
var current :Variant = current_value()
- if current == null or current.find(expected) == -1:
+ @warning_ignore("unsafe_cast")
+ if current == null or (current as String).find(expected) == -1:
return report_error(GdAssertMessages.error_contains(current, expected))
return report_success()
func not_contains(expected :String) -> GdUnitStringAssert:
var current :Variant = current_value()
- if current != null and current.find(expected) != -1:
+ @warning_ignore("unsafe_cast")
+ if current != null and (current as String).find(expected) != -1:
return report_error(GdAssertMessages.error_not_contains(current, expected))
return report_success()
func contains_ignoring_case(expected :String) -> GdUnitStringAssert:
var current :Variant = current_value()
- if current == null or current.findn(expected) == -1:
+ @warning_ignore("unsafe_cast")
+ if current == null or (current as String).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 :Variant = current_value()
- if current != null and current.findn(expected) != -1:
+ @warning_ignore("unsafe_cast")
+ if current != null and (current as String).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 :Variant = current_value()
- if current == null or current.find(expected) != 0:
+ @warning_ignore("unsafe_cast")
+ if current == null or (current as String).find(expected) != 0:
return report_error(GdAssertMessages.error_starts_with(current, expected))
return report_success()
@@ -146,8 +157,10 @@ func ends_with(expected :String) -> GdUnitStringAssert:
var current :Variant = current_value()
if current == null:
return report_error(GdAssertMessages.error_ends_with(current, expected))
- var find :int = current.length() - expected.length()
- if current.rfind(expected) != find:
+ @warning_ignore("unsafe_cast")
+ var find :int = (current as String).length() - expected.length()
+ @warning_ignore("unsafe_cast")
+ if (current as String).rfind(expected) != find:
return report_error(GdAssertMessages.error_ends_with(current, expected))
return report_success()
@@ -157,22 +170,23 @@ func has_length(expected :int, comparator := Comparator.EQUAL) -> GdUnitStringAs
var current :Variant = current_value()
if current == null:
return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
+ var str_current: String = current
match comparator:
Comparator.EQUAL:
- if current.length() != expected:
- return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
+ if str_current.length() != expected:
+ return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
Comparator.LESS_THAN:
- if current.length() >= expected:
- return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
+ if str_current.length() >= expected:
+ return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
Comparator.LESS_EQUAL:
- if current.length() > expected:
- return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
+ if str_current.length() > expected:
+ return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
Comparator.GREATER_THAN:
- if current.length() <= expected:
- return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
+ if str_current.length() <= expected:
+ return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
Comparator.GREATER_EQUAL:
- if current.length() < expected:
- return report_error(GdAssertMessages.error_has_length(current, expected, comparator))
+ if str_current.length() < expected:
+ return report_error(GdAssertMessages.error_has_length(str_current, expected, comparator))
_:
return report_error("Comparator '%d' not implemented!" % comparator)
return report_success()
diff --git a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd
index 0b35ed73..7b10d6bb 100644
--- a/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd
+++ b/addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd
@@ -1,15 +1,16 @@
extends GdUnitVectorAssert
-var _base: GdUnitAssert
-var _current_type :int
+var _base: GdUnitAssertImpl
+var _current_type: int
+var _type_check: bool
-
-func _init(current :Variant) -> void:
- _base = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript",
- ResourceLoader.CACHE_MODE_REUSE).new(current)
+func _init(current: Variant, type_check := true) -> void:
+ _type_check = type_check
+ _base = GdUnitAssertImpl.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):
+ @warning_ignore("return_value_discarded")
report_error("GdUnitVectorAssert error, the type <%s> is not supported." % GdObjects.typeof_as_string(current))
_current_type = typeof(current)
@@ -39,6 +40,7 @@ func _validate_is_vector_type(value :Variant) -> bool:
var type := typeof(value)
if type == _current_type or _current_type == TYPE_NIL:
return true
+ @warning_ignore("return_value_discarded")
report_error(GdAssertMessages.error_is_wrong_type(_current_type, type))
return false
@@ -62,35 +64,41 @@ func failure_message() -> String:
func override_failure_message(message :String) -> GdUnitVectorAssert:
+ @warning_ignore("return_value_discarded")
_base.override_failure_message(message)
return self
func append_failure_message(message :String) -> GdUnitVectorAssert:
+ @warning_ignore("return_value_discarded")
_base.append_failure_message(message)
return self
func is_null() -> GdUnitVectorAssert:
+ @warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitVectorAssert:
+ @warning_ignore("return_value_discarded")
_base.is_not_null()
return self
-func is_equal(expected :Variant) -> GdUnitVectorAssert:
- if not _validate_is_vector_type(expected):
+func is_equal(expected: Variant) -> GdUnitVectorAssert:
+ if _type_check and not _validate_is_vector_type(expected):
return self
+ @warning_ignore("return_value_discarded")
_base.is_equal(expected)
return self
-func is_not_equal(expected :Variant) -> GdUnitVectorAssert:
- if not _validate_is_vector_type(expected):
+func is_not_equal(expected: Variant) -> GdUnitVectorAssert:
+ if _type_check and not _validate_is_vector_type(expected):
return self
+ @warning_ignore("return_value_discarded")
_base.is_not_equal(expected)
return self
diff --git a/addons/gdUnit4/src/asserts/ValueProvider.gd b/addons/gdUnit4/src/asserts/ValueProvider.gd
index a94aa91d..be01f70b 100644
--- a/addons/gdUnit4/src/asserts/ValueProvider.gd
+++ b/addons/gdUnit4/src/asserts/ValueProvider.gd
@@ -4,3 +4,7 @@ extends RefCounted
func get_value() -> Variant:
return null
+
+
+func dispose() -> void:
+ pass
diff --git a/addons/gdUnit4/src/cmd/CmdArgumentParser.gd b/addons/gdUnit4/src/cmd/CmdArgumentParser.gd
index 1abe67ff..aa023194 100644
--- a/addons/gdUnit4/src/cmd/CmdArgumentParser.gd
+++ b/addons/gdUnit4/src/cmd/CmdArgumentParser.gd
@@ -40,22 +40,23 @@ func options() -> CmdOptions:
return _options
-func _parse_cmd_arguments(option :CmdOption, args :Array) -> int:
+func _parse_cmd_arguments(option: CmdOption, args: Array) -> int:
var command_name := option.short_command()
- var command :CmdCommand = _parsed_commands.get(command_name, CmdCommand.new(command_name))
+ var command: CmdCommand = _parsed_commands.get(command_name, CmdCommand.new(command_name))
if option.has_argument():
if not option.is_argument_optional() and args.is_empty():
return -1
if _is_next_value_argument(args):
- command.add_argument(args.pop_front())
+ var value: String = args.pop_front()
+ command.add_argument(value)
elif not option.is_argument_optional():
return -1
_parsed_commands[command_name] = command
return 0
-func _is_next_value_argument(args :Array) -> bool:
+func _is_next_value_argument(args: PackedStringArray) -> bool:
if args.is_empty():
return false
return _options.get_option(args[0]) == null
diff --git a/addons/gdUnit4/src/cmd/CmdCommand.gd b/addons/gdUnit4/src/cmd/CmdCommand.gd
index 58f09155..92e8c1fe 100644
--- a/addons/gdUnit4/src/cmd/CmdCommand.gd
+++ b/addons/gdUnit4/src/cmd/CmdCommand.gd
@@ -19,6 +19,7 @@ func arguments() -> PackedStringArray:
func add_argument(arg :String) -> void:
+ @warning_ignore("return_value_discarded")
_arguments.append(arg)
diff --git a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd b/addons/gdUnit4/src/cmd/CmdCommandHandler.gd
index 5b3b1cdc..a11c7bcf 100644
--- a/addons/gdUnit4/src/cmd/CmdCommandHandler.gd
+++ b/addons/gdUnit4/src/cmd/CmdCommandHandler.gd
@@ -57,14 +57,17 @@ func _validate() -> GdUnitResult:
if _command_cbs[cmd_name][CB_SINGLE_ARG]
else _command_cbs[cmd_name][CB_MULTI_ARGS])
if cb != NO_CB and not cb.is_valid():
+ @warning_ignore("return_value_discarded")
errors.append("Invalid function reference for command '%s', Check the function reference!" % cmd_name)
if _cmd_options.get_option(cmd_name) == null:
+ @warning_ignore("return_value_discarded")
errors.append("The command '%s' is unknown, verify your CmdOptions!" % cmd_name)
# verify for multiple registered command callbacks
if _enhanced_fr_test and cb != NO_CB:
var cb_method: = cb.get_method()
if registered_cbs.has(cb_method):
var already_registered_cmd :String = registered_cbs[cb_method]
+ @warning_ignore("return_value_discarded")
errors.append("The function reference '%s' already registerd for command '%s'!" % [cb_method, already_registered_cmd])
else:
registered_cbs[cb_method] = cmd_name
@@ -95,7 +98,8 @@ func execute(commands :Array[CmdCommand]) -> GdUnitResult:
# 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:
+ @warning_ignore("unsafe_cast")
+ if (m["args"] as Array).size() > 1:
cb_m.callv(arguments)
break
else:
diff --git a/addons/gdUnit4/src/cmd/CmdConsole.gd b/addons/gdUnit4/src/cmd/CmdConsole.gd
index 62a2949d..a10c73e7 100644
--- a/addons/gdUnit4/src/cmd/CmdConsole.gd
+++ b/addons/gdUnit4/src/cmd/CmdConsole.gd
@@ -23,14 +23,14 @@ var _color_mode := COLOR_TABLE
func color(p_color :Color) -> CmdConsole:
# using color table 16 - 231 a 6 x 6 x 6 RGB color cube (16 + R * 36 + G * 6 + B)
- if _color_mode == COLOR_TABLE:
- @warning_ignore("integer_division")
- var c2 := 16 + (int(p_color.r8/42) * 36) + (int(p_color.g8/42) * 6) + int(p_color.b8/42)
- if _debug_show_color_codes:
- printraw("%6d" % [c2])
- printraw("[38;5;%dm" % c2 )
- else:
- printraw("[38;2;%d;%d;%dm" % [p_color.r8, p_color.g8, p_color.b8] )
+ #if _color_mode == COLOR_TABLE:
+ # @warning_ignore("integer_division")
+ # var c2 := 16 + (int(p_color.r8/42) * 36) + (int(p_color.g8/42) * 6) + int(p_color.b8/42)
+ # if _debug_show_color_codes:
+ # printraw("%6d" % [c2])
+ # printraw("[38;5;%dm" % c2 )
+ #else:
+ printraw("[38;2;%d;%d;%dm" % [p_color.r8, p_color.g8, p_color.b8] )
return self
@@ -59,6 +59,7 @@ func scroll_area(from :int, to :int) -> CmdConsole:
return self
+@warning_ignore("return_value_discarded")
func progress_bar(p_progress :int, p_color :Color = Color.POWDER_BLUE) -> CmdConsole:
if p_progress < 0:
p_progress = 0
@@ -123,6 +124,7 @@ func print_color(p_message :String, p_color :Color, p_flags := 0) -> CmdConsole:
.end_color()
+@warning_ignore("return_value_discarded")
func print_color_table() -> void:
prints_color("Color Table 6x6x6", Color.ANTIQUE_WHITE)
_debug_show_color_codes = true
diff --git a/addons/gdUnit4/src/core/GdArrayTools.gd b/addons/gdUnit4/src/core/GdArrayTools.gd
index 3e3d3a9f..19cbad0d 100644
--- a/addons/gdUnit4/src/core/GdArrayTools.gd
+++ b/addons/gdUnit4/src/core/GdArrayTools.gd
@@ -28,10 +28,11 @@ static func is_type_array(type :int) -> bool:
## Filters an array by given value[br]
## If the given value not an array it returns null, will remove all occurence of given value.
-static func filter_value(array :Variant, value :Variant) -> Variant:
+@warning_ignore("unsafe_method_access")
+static func filter_value(array: Variant, value: Variant) -> Variant:
if not is_array_type(array):
return null
- var filtered_array :Variant = array.duplicate()
+ var filtered_array: Variant = array.duplicate()
var index :int = filtered_array.find(value)
while index != -1:
filtered_array.remove_at(index)
@@ -72,13 +73,12 @@ static func scan_typed(array :Array) -> int:
## # will result in PackedString(["a", "b"])
## GdArrayTools.as_string(PackedColorArray(Color.RED, COLOR.GREEN))
## [/codeblock]
-static func as_string(elements :Variant, encode_value := true) -> String:
- if not is_array_type(elements):
- return "ERROR: Not an Array Type!"
+static func as_string(elements: Variant, encode_value := true) -> String:
var delemiter := ", "
if elements == null:
return ""
- if elements.is_empty():
+ @warning_ignore("unsafe_cast")
+ if (elements as Array).is_empty():
return ""
var prefix := _typeof_as_string(elements) if encode_value else ""
var formatted := ""
diff --git a/addons/gdUnit4/src/core/GdDiffTool.gd b/addons/gdUnit4/src/core/GdDiffTool.gd
index a918a990..16e567b0 100644
--- a/addons/gdUnit4/src/core/GdDiffTool.gd
+++ b/addons/gdUnit4/src/core/GdDiffTool.gd
@@ -7,7 +7,7 @@ const DIV_ADD :int = 214
const DIV_SUB :int = 215
-static func _diff(lb: PackedByteArray, rb: PackedByteArray, lookup: Array, ldiff: Array, rdiff: Array) -> void:
+static func _diff(lb: PackedByteArray, rb: PackedByteArray, lookup: Array[Array], ldiff: Array, rdiff: Array) -> void:
var loffset := lb.size()
var roffset := rb.size()
@@ -39,17 +39,19 @@ static func _diff(lb: PackedByteArray, rb: PackedByteArray, lookup: Array, ldiff
# lookup[i][j] stores the length of LCS of substring X[0..i-1], Y[0..j-1]
-static func _createLookUp(lb: PackedByteArray, rb: PackedByteArray) -> Array:
- var lookup := Array()
+static func _createLookUp(lb: PackedByteArray, rb: PackedByteArray) -> Array[Array]:
+ var lookup: Array[Array] = []
+ @warning_ignore("return_value_discarded")
lookup.resize(lb.size() + 1)
for i in lookup.size():
var x := []
+ @warning_ignore("return_value_discarded")
x.resize(rb.size() + 1)
lookup[i] = x
return lookup
-static func _buildLookup(lb: PackedByteArray, rb: PackedByteArray) -> Array:
+static func _buildLookup(lb: PackedByteArray, rb: PackedByteArray) -> Array[Array]:
var lookup := _createLookUp(lb, rb)
# first column of the lookup table will be all 0
for i in lookup.size():
@@ -105,6 +107,7 @@ static func longestCommonSubsequence(text1 :String, text2 :String) -> PackedStri
var lcsResultList := PackedStringArray();
while (i < text1WordCount && j < text2WordCount):
if text1Words[i] == text2Words[j]:
+ @warning_ignore("return_value_discarded")
lcsResultList.append(text2Words[j])
i += 1
j += 1
diff --git a/addons/gdUnit4/src/core/GdFunctionDoubler.gd b/addons/gdUnit4/src/core/GdFunctionDoubler.gd
index ade86539..99a522f2 100644
--- a/addons/gdUnit4/src/core/GdFunctionDoubler.gd
+++ b/addons/gdUnit4/src/core/GdFunctionDoubler.gd
@@ -82,7 +82,9 @@ static func get_enum_default(value :String) -> Variant:
return %s.values()[0]
""".dedent() % value
+ @warning_ignore("return_value_discarded")
script.reload()
+ @warning_ignore("unsafe_method_access")
return script.new().call("get_enum_default")
@@ -113,20 +115,19 @@ func _init(push_errors :bool = false) -> void:
@warning_ignore("unused_parameter")
-func get_template(return_type :Variant, is_vararg :bool) -> String:
- push_error("Must be implemented!")
+func get_template(return_type: GdFunctionDescriptor, is_callable: bool) -> String:
+ assert(false, "'get_template' must be implemented!")
return ""
-func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray:
- var func_signature := func_descriptor.typeless()
+
+func double(func_descriptor: GdFunctionDescriptor, is_callable: bool = false) -> PackedStringArray:
var is_static := func_descriptor.is_static()
- var is_vararg := func_descriptor.is_vararg()
var is_coroutine := func_descriptor.is_coroutine()
var func_name := func_descriptor.name()
var args := func_descriptor.args()
var varargs := func_descriptor.varargs()
var return_value := GdFunctionDoubler.default_return_value(func_descriptor)
- var arg_names := extract_arg_names(args)
+ var arg_names := extract_arg_names(args, true)
var vararg_names := extract_arg_names(varargs)
# save original constructor arguments
@@ -135,17 +136,15 @@ func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray:
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'
+ var double_src := "@warning_ignore('shadowed_variable', 'untyped_declaration', 'unsafe_call_argument', 'unsafe_method_access')\n"
if func_descriptor.is_engine():
double_src += '@warning_ignore("native_method_override")\n'
if func_descriptor.return_type() == GdObjects.TYPE_ENUM:
double_src += '@warning_ignore("int_as_enum_without_match")\n'
double_src += '@warning_ignore("int_as_enum_without_cast")\n'
- double_src += '@warning_ignore("shadowed_variable")\n'
- double_src += func_signature
+ double_src += GdFunctionDoubler.extract_func_signature(func_descriptor)
# fix to unix format, this is need when the template is edited under windows than the template is stored with \r\n
- var func_template := get_template(func_descriptor.return_type(), is_vararg).replace("\r\n", "\n")
+ var func_template := get_template(func_descriptor, is_callable).replace("\r\n", "\n")
double_src += func_template\
.replace("$(arguments)", ", ".join(arg_names))\
.replace("$(varargs)", ", ".join(vararg_names))\
@@ -161,25 +160,54 @@ func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray:
return double_src.split("\n")
-func extract_arg_names(argument_signatures :Array[GdFunctionArgument]) -> PackedStringArray:
+func extract_arg_names(argument_signatures: Array[GdFunctionArgument], add_suffix := false) -> PackedStringArray:
var arg_names := PackedStringArray()
for arg in argument_signatures:
- arg_names.append(arg._name)
+ @warning_ignore("return_value_discarded")
+ arg_names.append(arg._name + ("_" if add_suffix else ""))
return arg_names
static func extract_constructor_args(args :Array[GdFunctionArgument]) -> PackedStringArray:
var constructor_args := PackedStringArray()
for arg in args:
- var arg_name := arg._name
+ var arg_name := arg._name + "_"
var default_value := get_default(arg)
if default_value == "null":
+ @warning_ignore("return_value_discarded")
constructor_args.append(arg_name + ":Variant=" + default_value)
else:
+ @warning_ignore("return_value_discarded")
constructor_args.append(arg_name + ":=" + default_value)
return constructor_args
+static func extract_func_signature(descriptor: GdFunctionDescriptor) -> String:
+ var func_signature := ""
+ if descriptor._return_type == TYPE_NIL:
+ func_signature = "func %s(%s) -> void:" % [descriptor.name(), typeless_args(descriptor)]
+ elif descriptor._return_type == GdObjects.TYPE_VARIANT:
+ func_signature = "func %s(%s):" % [descriptor.name(), typeless_args(descriptor)]
+ else:
+ func_signature = "func %s(%s) -> %s:" % [descriptor.name(), typeless_args(descriptor), descriptor.return_type_as_string()]
+ return "static " + func_signature if descriptor.is_static() else func_signature
+
+
+static func typeless_args(descriptor: GdFunctionDescriptor) -> String:
+ var collect := PackedStringArray()
+ for arg in descriptor.args():
+ if arg.has_default():
+ @warning_ignore("return_value_discarded")
+ collect.push_back(arg.name() + "_" + "=" + arg.value_as_string())
+ else:
+ @warning_ignore("return_value_discarded")
+ collect.push_back(arg.name() + "_")
+ for arg in descriptor.varargs():
+ @warning_ignore("return_value_discarded")
+ collect.push_back(arg.name() + "=" + arg.value_as_string())
+ return ", ".join(collect)
+
+
static func get_default(arg :GdFunctionArgument) -> String:
if arg.has_default():
return arg.value_as_string()
diff --git a/addons/gdUnit4/src/core/GdObjects.gd b/addons/gdUnit4/src/core/GdObjects.gd
index 5a2eb5c2..2a780ce8 100644
--- a/addons/gdUnit4/src/core/GdObjects.gd
+++ b/addons/gdUnit4/src/core/GdObjects.gd
@@ -8,17 +8,16 @@ const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
# introduced with Godot 4.3.beta1
const TYPE_PACKED_VECTOR4_ARRAY = 38 #TYPE_PACKED_VECTOR4_ARRAY
-const TYPE_VOID = TYPE_MAX + 1000
-const TYPE_VARARG = TYPE_MAX + 1001
-const TYPE_VARIANT = TYPE_MAX + 1002
-const TYPE_FUNC = TYPE_MAX + 1003
-const TYPE_FUZZER = TYPE_MAX + 1004
-
-const TYPE_NODE = TYPE_MAX + 2001
+const TYPE_VOID = 1000
+const TYPE_VARARG = 1001
+const TYPE_VARIANT = 1002
+const TYPE_FUNC = 1003
+const TYPE_FUZZER = 1004
# missing Godot types
-const TYPE_CONTROL = TYPE_MAX + 2002
-const TYPE_CANVAS = TYPE_MAX + 2003
-const TYPE_ENUM = TYPE_MAX + 2004
+const TYPE_NODE = 2001
+const TYPE_CONTROL = 2002
+const TYPE_CANVAS = 2003
+const TYPE_ENUM = 2004
# used as default value for varargs
@@ -146,7 +145,8 @@ enum COMPARE_MODE {
# prototype of better object to dictionary
-static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary:
+@warning_ignore("unsafe_cast")
+static func obj2dict(obj: Object, hashed_objects := Dictionary()) -> Dictionary:
if obj == null:
return {}
var clazz_name := obj.get_class()
@@ -154,13 +154,20 @@ static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary:
var clazz_path := ""
if is_instance_valid(obj) and obj.get_script() != null:
- var d := inst_to_dict(obj)
- clazz_path = d["@path"]
- if d["@subpath"] != NodePath(""):
- clazz_name = d["@subpath"]
- dict["@inner_class"] = true
+ var script: Script = obj.get_script()
+ # handle build-in scripts
+ if script.resource_path != null and script.resource_path.contains(".tscn"):
+ var path_elements := script.resource_path.split(".tscn")
+ clazz_name = path_elements[0].get_file()
+ clazz_path = script.resource_path
else:
- clazz_name = clazz_path.get_file().replace(".gd", "")
+ var d := inst_to_dict(obj)
+ clazz_path = d["@path"]
+ if d["@subpath"] != NodePath(""):
+ clazz_name = d["@subpath"]
+ dict["@inner_class"] = true
+ else:
+ clazz_name = clazz_path.get_file().replace(".gd", "")
dict["@path"] = clazz_path
for property in obj.get_property_list():
@@ -178,11 +185,14 @@ static func obj2dict(obj :Object, hashed_objects := Dictionary()) -> Dictionary:
dict[property_name] = str(property_value)
continue
hashed_objects[obj] = true
- dict[property_name] = obj2dict(property_value, hashed_objects)
+ dict[property_name] = obj2dict(property_value as Object, hashed_objects)
else:
dict[property_name] = property_value
- if obj.has_method("get_children"):
- var childrens :Array = obj.get_children()
+ if obj is Node:
+ var childrens :Array = (obj as Node).get_children()
+ dict["childrens"] = childrens.map(func (child :Object) -> Dictionary: return obj2dict(child, hashed_objects))
+ if obj is TreeItem:
+ var childrens :Array = (obj as TreeItem).get_children()
dict["childrens"] = childrens.map(func (child :Object) -> Dictionary: return obj2dict(child, hashed_objects))
return {"%s" % clazz_name : dict}
@@ -192,14 +202,15 @@ static func equals(obj_a :Variant, obj_b :Variant, case_sensitive :bool = false,
return _equals(obj_a, obj_b, case_sensitive, compare_mode, [], 0)
-static func equals_sorted(obj_a :Array, obj_b :Array, case_sensitive :bool = false, compare_mode :COMPARE_MODE = COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
- var a := obj_a.duplicate()
- var b := obj_b.duplicate()
+static func equals_sorted(obj_a: Array[Variant], obj_b: Array[Variant], case_sensitive: bool = false, compare_mode: COMPARE_MODE = COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
+ var a: Array[Variant] = obj_a.duplicate()
+ var b: Array[Variant] = obj_b.duplicate()
a.sort()
b.sort()
return equals(a, b, case_sensitive, compare_mode)
+@warning_ignore("unsafe_method_access", "unsafe_cast")
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)
@@ -239,8 +250,8 @@ static func _equals(obj_a :Variant, obj_b :Variant, case_sensitive :bool, compar
return false
if obj_a.get_class() != obj_b.get_class():
return false
- var a := obj2dict(obj_a)
- var b := obj2dict(obj_b)
+ var a := obj2dict(obj_a as Object)
+ var b := obj2dict(obj_b as Object)
return _equals(a, b, case_sensitive, compare_mode, deep_stack, stack_depth)
return obj_a == obj_b
@@ -298,6 +309,7 @@ static func to_pascal_case(value :String) -> String:
return value.capitalize().replace(" ", "")
+@warning_ignore("return_value_discarded")
static func to_snake_case(value :String) -> String:
var result := PackedStringArray()
for ch in value:
@@ -318,6 +330,8 @@ static func is_snake_case(value :String) -> bool:
static func type_as_string(type :int) -> String:
+ if type < TYPE_MAX:
+ return type_string(type)
return TYPE_AS_STRING_MAPPINGS.get(type, "Variant")
@@ -350,10 +364,13 @@ static func _is_type_equivalent(type_a :int, type_b :int) -> bool:
or type_a == type_b)
-static func is_engine_type(value :Object) -> bool:
+static func is_engine_type(value :Variant) -> bool:
if value is GDScript or value is ScriptExtension:
return false
- return value.is_class("GDScriptNativeClass")
+ var obj: Object = value
+ if is_instance_valid(obj) and obj.has_method("is_class"):
+ return obj.is_class("GDScriptNativeClass")
+ return false
static func is_type(value :Variant) -> bool:
@@ -364,6 +381,7 @@ static func is_type(value :Variant) -> bool:
if is_engine_type(value):
return true
# is a custom class type
+ @warning_ignore("unsafe_cast")
if value is GDScript and (value as GDScript).can_instantiate():
return true
return false
@@ -377,7 +395,8 @@ static func _is_same(left :Variant, right :Variant) -> bool:
if left_type != right_type:
return false
if left_type == TYPE_OBJECT and right_type == TYPE_OBJECT:
- return left.get_instance_id() == right.get_instance_id()
+ @warning_ignore("unsafe_cast")
+ return (left as Object).get_instance_id() == (right as Object).get_instance_id()
return equals(left, right)
@@ -402,7 +421,8 @@ static func is_scene(value :Variant) -> bool:
static func is_scene_resource_path(value :Variant) -> bool:
- return value is String and value.ends_with(".tscn")
+ @warning_ignore("unsafe_cast")
+ return value is String and (value as String).ends_with(".tscn")
static func is_gd_script(script :Script) -> bool:
@@ -418,8 +438,8 @@ static func is_gd_testsuite(script :Script) -> bool:
if is_gd_script(script):
var stack := [script]
while not stack.is_empty():
- var current := stack.pop_front() as Script
- var base := current.get_base_script() as Script
+ var current: Script = stack.pop_front()
+ var base: Script = current.get_base_script()
if base != null:
if base.resource_path.find("GdUnitTestSuite") != -1:
return true
@@ -427,11 +447,12 @@ static func is_gd_testsuite(script :Script) -> bool:
return false
-static func is_singleton(value :Variant) -> bool:
+static func is_singleton(value: Variant) -> bool:
if not is_instance_valid(value) or is_native_class(value):
return false
for name in Engine.get_singleton_list():
- if value.is_class(name):
+ @warning_ignore("unsafe_cast")
+ if (value as Object).is_class(name):
return true
return false
@@ -439,17 +460,19 @@ static func is_singleton(value :Variant) -> bool:
static func is_instance(value :Variant) -> bool:
if not is_instance_valid(value) or is_native_class(value):
return false
+ @warning_ignore("unsafe_cast")
if is_script(value) and (value as Script).get_instance_base_type() == "":
return true
if is_scene(value):
return true
- return not value.has_method('new') and not value.has_method('instance')
+ @warning_ignore("unsafe_cast")
+ return not (value as Object).has_method('new') and not (value as Object).has_method('instance')
# only object form type Node and attached filename
static func is_instance_scene(instance :Variant) -> bool:
if instance is Node:
- var node := instance as Node
+ var node: Node = instance
return node.get_scene_file_path() != null and not node.get_scene_file_path().is_empty()
return false
@@ -457,7 +480,8 @@ static func is_instance_scene(instance :Variant) -> bool:
static func can_be_instantiate(obj :Variant) -> bool:
if not obj or is_engine_type(obj):
return false
- return obj.has_method("new")
+ @warning_ignore("unsafe_cast")
+ return (obj as Object).has_method("new")
static func create_instance(clazz :Variant) -> GdUnitResult:
@@ -466,48 +490,54 @@ static func create_instance(clazz :Variant) -> GdUnitResult:
# test is given clazz already an instance
if is_instance(clazz):
return GdUnitResult.success(clazz)
+ @warning_ignore("unsafe_method_access")
return GdUnitResult.success(clazz.new())
TYPE_STRING:
- if ClassDB.class_exists(clazz):
- if Engine.has_singleton(clazz):
- return GdUnitResult.error("Not allowed to create a instance for singelton '%s'." % clazz)
- if not ClassDB.can_instantiate(clazz):
- return GdUnitResult.error("Can't instance Engine class '%s'." % clazz)
- return GdUnitResult.success(ClassDB.instantiate(clazz))
+ var clazz_name: String = clazz
+ if ClassDB.class_exists(clazz_name):
+ if Engine.has_singleton(clazz_name):
+ return GdUnitResult.error("Not allowed to create a instance for singelton '%s'." % clazz_name)
+ if not ClassDB.can_instantiate(clazz_name):
+ return GdUnitResult.error("Can't instance Engine class '%s'." % clazz_name)
+ return GdUnitResult.success(ClassDB.instantiate(clazz_name))
else:
- var clazz_path :String = extract_class_path(clazz)[0]
+ var clazz_path :String = extract_class_path(clazz_name)[0]
if not FileAccess.file_exists(clazz_path):
- return GdUnitResult.error("Class '%s' not found." % clazz)
- var script := load(clazz_path)
+ return GdUnitResult.error("Class '%s' not found." % clazz_name)
+ var script: GDScript = load(clazz_path)
if script != null:
return GdUnitResult.success(script.new())
else:
- return GdUnitResult.error("Can't create instance for '%s'." % clazz)
- return GdUnitResult.error("Can't create instance for class '%s'." % clazz)
+ return GdUnitResult.error("Can't create instance for '%s'." % clazz_name)
+ return GdUnitResult.error("Can't create instance for class '%s'." % str(clazz))
+@warning_ignore("return_value_discarded")
static func extract_class_path(clazz :Variant) -> PackedStringArray:
var clazz_path := PackedStringArray()
if clazz is String:
- clazz_path.append(clazz)
+ @warning_ignore("unsafe_cast")
+ clazz_path.append(clazz as String)
return clazz_path
if is_instance(clazz):
# is instance a script instance?
- var script := clazz.script as GDScript
+ var script: GDScript = clazz.script
if script != null:
return extract_class_path(script)
return clazz_path
if clazz is GDScript:
- if not clazz.resource_path.is_empty():
- clazz_path.append(clazz.resource_path)
+ var script: GDScript = clazz
+ if not script.resource_path.is_empty():
+ clazz_path.append(script.resource_path)
return clazz_path
# if not found we go the expensive way and extract the path form the script by creating an instance
- var arg_list := build_function_default_arguments(clazz, "_init")
- var instance :Variant = clazz.callv("new", arg_list)
+ var arg_list := build_function_default_arguments(script, "_init")
+ var instance: Object = script.callv("new", arg_list)
var clazz_info := inst_to_dict(instance)
GdUnitTools.free_instance(instance)
- clazz_path.append(clazz_info["@path"])
+ @warning_ignore("unsafe_cast")
+ clazz_path.append(clazz_info["@path"] as String)
if clazz_info.has("@subpath"):
var sub_path :String = clazz_info["@subpath"]
if not sub_path.is_empty():
@@ -534,33 +564,38 @@ static func extract_class_name(clazz :Variant) -> GdUnitResult:
if is_instance(clazz):
# is instance a script instance?
- var script := clazz.script as GDScript
+ var script: GDScript = clazz.script
if script != null:
return extract_class_name(script)
+ @warning_ignore("unsafe_cast")
return GdUnitResult.success((clazz as Object).get_class())
# extract name form full qualified class path
if clazz is String:
- if ClassDB.class_exists(clazz):
- return GdUnitResult.success(clazz)
- var source_sript :Script = load(clazz)
- var clazz_name :String = load("res://addons/gdUnit4/src/core/parse/GdScriptParser.gd").new().get_class_name(source_sript)
+ var clazz_name: String = clazz
+ if ClassDB.class_exists(clazz_name):
+ return GdUnitResult.success(clazz_name)
+ var source_script :GDScript = load(clazz_name)
+ clazz_name = GdScriptParser.new().get_class_name(source_script)
return GdUnitResult.success(to_pascal_case(clazz_name))
if is_primitive_type(clazz):
return GdUnitResult.error("Can't extract class name for an primitive '%s'" % type_as_string(typeof(clazz)))
if is_script(clazz):
- if clazz.resource_path.is_empty():
+ @warning_ignore("unsafe_cast")
+ if (clazz as Script).resource_path.is_empty():
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
+ @warning_ignore("unsafe_method_access")
var instance :Variant = clazz.new()
if instance == null:
- return GdUnitResult.error("Can't create a instance for class '%s'" % clazz)
+ return GdUnitResult.error("Can't create a instance for class '%s'" % str(clazz))
var result := extract_class_name(instance)
+ @warning_ignore("return_value_discarded")
GdUnitTools.free_instance(instance)
return result
@@ -576,6 +611,7 @@ static func extract_inner_clazz_names(clazz_name :String, script_path :PackedStr
var value :Variant = map.get(key)
if value is GDScript:
var class_path := extract_class_path(value)
+ @warning_ignore("return_value_discarded")
inner_classes.append(class_path[1])
return inner_classes
@@ -654,6 +690,7 @@ static func default_value_by_type(type :int) -> Variant:
TYPE_NODE_PATH: return NodePath()
TYPE_RID: return RID()
TYPE_OBJECT: return null
+ TYPE_CALLABLE: return Callable()
TYPE_ARRAY: return []
TYPE_DICTIONARY: return {}
TYPE_PACKED_BYTE_ARRAY: return PackedByteArray()
diff --git a/addons/gdUnit4/src/core/GdUnit4Version.gd b/addons/gdUnit4/src/core/GdUnit4Version.gd
index 5918353d..777eb92c 100644
--- a/addons/gdUnit4/src/core/GdUnit4Version.gd
+++ b/addons/gdUnit4/src/core/GdUnit4Version.gd
@@ -16,6 +16,7 @@ func _init(major :int, minor :int, patch :int) -> void:
static func parse(value :String) -> GdUnit4Version:
var regex := RegEx.new()
+ @warning_ignore("return_value_discarded")
regex.compile("[a-zA-Z:,-]+")
var cleaned := regex.sub(value, "", true)
var parts := cleaned.split(".")
@@ -27,8 +28,10 @@ static func parse(value :String) -> GdUnit4Version:
static func current() -> GdUnit4Version:
var config := ConfigFile.new()
+ @warning_ignore("return_value_discarded")
config.load('addons/gdUnit4/plugin.cfg')
- return parse(config.get_value('plugin', 'version'))
+ @warning_ignore("unsafe_cast")
+ return parse(config.get_value('plugin', 'version') as String)
func equals(other :GdUnit4Version) -> bool:
@@ -45,12 +48,13 @@ func is_greater(other :GdUnit4Version) -> bool:
static func init_version_label(label :Control) -> void:
var config := ConfigFile.new()
+ @warning_ignore("return_value_discarded")
config.load('addons/gdUnit4/plugin.cfg')
var version :String = config.get_value('plugin', 'version')
if label is RichTextLabel:
- label.text = VERSION_PATTERN.replace('${version}', version)
+ (label as RichTextLabel).text = VERSION_PATTERN.replace('${version}', version)
else:
- label.text = "gdUnit4 " + version
+ (label as Label).text = "gdUnit4 " + version
func _to_string() -> String:
diff --git a/addons/gdUnit4/src/core/GdUnitClassDoubler.gd b/addons/gdUnit4/src/core/GdUnitClassDoubler.gd
index 4f0d2bbc..8a96a29b 100644
--- a/addons/gdUnit4/src/core/GdUnitClassDoubler.gd
+++ b/addons/gdUnit4/src/core/GdUnitClassDoubler.gd
@@ -37,12 +37,15 @@ static func check_leaked_instances() -> void:
# class_info = { "class_name": <>, "class_path" : <>}
static func load_template(template :String, class_info :Dictionary, instance :Object) -> PackedStringArray:
# store instance id
+ var clazz_name: String = class_info.get("class_name")
var source_code := template\
.replace("${instance_id}", "%s%d" % [DOUBLER_INSTANCE_ID_PREFIX, abs(instance.get_instance_id())])\
- .replace("${source_class}", class_info.get("class_name"))
+ .replace("${source_class}", clazz_name)
var lines := GdScriptParser.to_unix_format(source_code).split("\n")
# replace template class_name with Doubled name and extends form source class
- lines.insert(0, "class_name Doubled%s" % class_info.get("class_name").replace(".", "_"))
+ @warning_ignore("return_value_discarded")
+ lines.insert(0, "class_name Doubled%s" % clazz_name.replace(".", "_"))
+ @warning_ignore("return_value_discarded")
lines.insert(1, extends_clazz(class_info))
# append Object interactions stuff
lines.append_array(GdScriptParser.to_unix_format(DOUBLER_TEMPLATE.source_code).split("\n"))
@@ -74,16 +77,14 @@ static func double_functions(instance :Object, clazz_name :String, clazz_path :P
push_error(result.error_message())
return PackedStringArray()
var class_descriptor :GdClassDescriptor = result.value()
- while class_descriptor != null:
- for func_descriptor in class_descriptor.functions():
- if instance != null and not instance.has_method(func_descriptor.name()):
- #prints("no virtual func implemented",clazz_name, func_descriptor.name() )
- continue
- if functions.has(func_descriptor.name()) or exclude_override_functions.has(func_descriptor.name()):
- continue
- doubled_source += func_doubler.double(func_descriptor)
- functions.append(func_descriptor.name())
- class_descriptor = class_descriptor.parent()
+ for func_descriptor in class_descriptor.functions():
+ if instance != null and not instance.has_method(func_descriptor.name()):
+ #prints("no virtual func implemented",clazz_name, func_descriptor.name() )
+ continue
+ if functions.has(func_descriptor.name()) or exclude_override_functions.has(func_descriptor.name()):
+ continue
+ doubled_source += func_doubler.double(func_descriptor, instance is CallableDoubler)
+ functions.append(func_descriptor.name())
# double regular class functions
var clazz_functions := GdObjects.extract_class_functions(clazz_name, clazz_path)
@@ -103,7 +104,7 @@ static func double_functions(instance :Object, clazz_name :String, clazz_path :P
#prints("no virtual func implemented",clazz_name, func_descriptor.name() )
continue
functions.append(func_descriptor.name())
- doubled_source.append_array(func_doubler.double(func_descriptor))
+ doubled_source.append_array(func_doubler.double(func_descriptor, instance is CallableDoubler))
return doubled_source
diff --git a/addons/gdUnit4/src/core/GdUnitFileAccess.gd b/addons/gdUnit4/src/core/GdUnitFileAccess.gd
index 01022ddb..e73216d0 100644
--- a/addons/gdUnit4/src/core/GdUnitFileAccess.gd
+++ b/addons/gdUnit4/src/core/GdUnitFileAccess.gd
@@ -23,6 +23,7 @@ static func create_temp_file(relative_path :String, file_name :String, mode := F
static func temp_dir() -> String:
if not DirAccess.dir_exists_absolute(GDUNIT_TEMP):
+ @warning_ignore("return_value_discarded")
DirAccess.make_dir_recursive_absolute(GDUNIT_TEMP)
return GDUNIT_TEMP
@@ -30,6 +31,7 @@ static func temp_dir() -> String:
static func create_temp_dir(folder_name :String) -> String:
var new_folder := temp_dir() + "/" + folder_name
if not DirAccess.dir_exists_absolute(new_folder):
+ @warning_ignore("return_value_discarded")
DirAccess.make_dir_recursive_absolute(new_folder)
return new_folder
@@ -61,6 +63,7 @@ static func copy_directory(from_dir :String, to_dir :String, recursive :bool = f
var source_dir := DirAccess.open(from_dir)
var dest_dir := DirAccess.open(to_dir)
if source_dir != null:
+ @warning_ignore("return_value_discarded")
source_dir.list_dir_begin()
var next := "."
@@ -72,6 +75,7 @@ static func copy_directory(from_dir :String, to_dir :String, recursive :bool = f
var dest := dest_dir.get_current_dir() + "/" + next
if source_dir.current_is_dir():
if recursive:
+ @warning_ignore("return_value_discarded")
copy_directory(source + "/", dest, recursive)
continue
var err := source_dir.copy(source, dest)
@@ -88,6 +92,7 @@ static func copy_directory(from_dir :String, to_dir :String, recursive :bool = f
static func delete_directory(path :String, only_content := false) -> void:
var dir := DirAccess.open(path)
if dir != null:
+ @warning_ignore("return_value_discarded")
dir.list_dir_begin()
var file_name := "."
while file_name != "":
@@ -113,6 +118,7 @@ static func delete_path_index_lower_equals_than(path :String, prefix :String, in
if dir == null:
return 0
var deleted := 0
+ @warning_ignore("return_value_discarded")
dir.list_dir_begin()
var next := "."
while next != "":
@@ -134,6 +140,7 @@ static func find_last_path_index(path :String, prefix :String) -> int:
if dir == null:
return 0
var last_iteration := 0
+ @warning_ignore("return_value_discarded")
dir.list_dir_begin()
var next := "."
while next != "":
@@ -152,12 +159,14 @@ static func scan_dir(path :String) -> PackedStringArray:
if dir == null or not dir.dir_exists(path):
return PackedStringArray()
var content := PackedStringArray()
+ @warning_ignore("return_value_discarded")
dir.list_dir_begin()
var next := "."
while next != "":
next = dir.get_next()
if next.is_empty() or next == "." or next == "..":
continue
+ @warning_ignore("return_value_discarded")
content.append(next)
return content
@@ -169,6 +178,7 @@ static func resource_as_array(resource_path :String) -> PackedStringArray:
return PackedStringArray()
var file_content := PackedStringArray()
while not file.eof_reached():
+ @warning_ignore("return_value_discarded")
file_content.append(file.get_line())
return file_content
@@ -203,9 +213,11 @@ static func extract_zip(zip_package :String, dest_path :String) -> GdUnitResult:
for zip_entry in zip_entries:
var new_file_path: String = dest_path + "/" + zip_entry.replace(archive_path, "")
if zip_entry.ends_with("/"):
+ @warning_ignore("return_value_discarded")
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))
+ @warning_ignore("return_value_discarded")
zip.close()
return GdUnitResult.success(dest_path)
diff --git a/addons/gdUnit4/src/core/GdUnitObjectInteractions.gd b/addons/gdUnit4/src/core/GdUnitObjectInteractions.gd
index 36930fc7..c3ea021a 100644
--- a/addons/gdUnit4/src/core/GdUnitObjectInteractions.gd
+++ b/addons/gdUnit4/src/core/GdUnitObjectInteractions.gd
@@ -5,13 +5,15 @@ extends RefCounted
static func verify(interaction_object :Object, interactions_times :int) -> Variant:
if not _is_mock_or_spy(interaction_object, "__verify"):
return interaction_object
+ @warning_ignore("unsafe_method_access")
return interaction_object.__do_verify_interactions(interactions_times)
static func verify_no_interactions(interaction_object :Object) -> GdUnitAssert:
- var __gd_assert :GdUnitAssert = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new("")
+ var __gd_assert := GdUnitAssertImpl.new("")
if not _is_mock_or_spy(interaction_object, "__verify"):
return __gd_assert.report_success()
+ @warning_ignore("unsafe_method_access")
var __summary :Dictionary = interaction_object.__verify_no_interactions()
if __summary.is_empty():
return __gd_assert.report_success()
@@ -19,9 +21,10 @@ static func verify_no_interactions(interaction_object :Object) -> GdUnitAssert:
static func verify_no_more_interactions(interaction_object :Object) -> GdUnitAssert:
- var __gd_assert :GdUnitAssert = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE).new("")
+ var __gd_assert := GdUnitAssertImpl.new("")
if not _is_mock_or_spy(interaction_object, "__verify_no_more_interactions"):
return __gd_assert
+ @warning_ignore("unsafe_method_access")
var __summary :Dictionary = interaction_object.__verify_no_more_interactions()
if __summary.is_empty():
return __gd_assert
@@ -31,12 +34,14 @@ static func verify_no_more_interactions(interaction_object :Object) -> GdUnitAss
static func reset(interaction_object :Object) -> Object:
if not _is_mock_or_spy(interaction_object, "__reset"):
return interaction_object
+ @warning_ignore("unsafe_method_access")
interaction_object.__reset_interactions()
return interaction_object
static func _is_mock_or_spy(interaction_object :Object, mock_function_signature :String) -> bool:
- if interaction_object is GDScript and not interaction_object.get_script().has_script_method(mock_function_signature):
+ @warning_ignore("unsafe_cast")
+ if interaction_object is GDScript and not (interaction_object.get_script() as GDScript).has_method(mock_function_signature):
push_error("Error: You try to use a non mock or spy!")
return false
return true
diff --git a/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd b/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd
index c06b1d4d..94c435f4 100644
--- a/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd
+++ b/addons/gdUnit4/src/core/GdUnitObjectInteractionsTemplate.gd
@@ -1,4 +1,3 @@
-const GdUnitAssertImpl := preload("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd")
var __expected_interactions :int = -1
var __saved_interactions := Dictionary()
@@ -47,8 +46,10 @@ func __verify_interactions(function_args :Array[Variant]) -> void:
__error_message = GdAssertMessages.error_validate_interactions(__current_summary, __expected_summary)
else:
__error_message = GdAssertMessages.error_validate_interactions(__summary, __expected_summary)
+ @warning_ignore("return_value_discarded")
__gd_assert.report_error(__error_message)
else:
+ @warning_ignore("return_value_discarded")
__gd_assert.report_success()
__expected_interactions = -1
diff --git a/addons/gdUnit4/src/core/GdUnitProperty.gd b/addons/gdUnit4/src/core/GdUnitProperty.gd
index 6e338a3f..138dd9f7 100644
--- a/addons/gdUnit4/src/core/GdUnitProperty.gd
+++ b/addons/gdUnit4/src/core/GdUnitProperty.gd
@@ -31,6 +31,10 @@ func value() -> Variant:
return _value
+func value_as_string() -> String:
+ return _value
+
+
func value_set() -> PackedStringArray:
return _value_set
@@ -44,11 +48,11 @@ func set_value(p_value :Variant) -> void:
TYPE_STRING:
_value = str(p_value)
TYPE_BOOL:
- _value = bool(p_value)
+ _value = convert(p_value, TYPE_BOOL)
TYPE_INT:
- _value = int(p_value)
+ _value = convert(p_value, TYPE_INT)
TYPE_FLOAT:
- _value = float(p_value)
+ _value = convert(p_value, TYPE_FLOAT)
_:
_value = p_value
diff --git a/addons/gdUnit4/src/core/GdUnitResult.gd b/addons/gdUnit4/src/core/GdUnitResult.gd
index f2d297f9..c7187d48 100644
--- a/addons/gdUnit4/src/core/GdUnitResult.gd
+++ b/addons/gdUnit4/src/core/GdUnitResult.gd
@@ -8,7 +8,7 @@ enum {
EMPTY
}
-var _state :Variant
+var _state: int
var _warn_message := ""
var _error_message := ""
var _value :Variant = null
@@ -66,6 +66,10 @@ func value() -> Variant:
return _value
+func value_as_string() -> String:
+ return _value
+
+
func or_else(p_value :Variant) -> Variant:
if not is_success():
return p_value
@@ -97,7 +101,8 @@ static func serialize(result :GdUnitResult) -> Dictionary:
static func deserialize(config :Dictionary) -> GdUnitResult:
var result := GdUnitResult.new()
- result._value = str_to_var(config.get("value", ""))
+ var cfg_value: String = config.get("value", "")
+ result._value = str_to_var(cfg_value)
result._warn_message = config.get("warn_msg", null)
result._error_message = config.get("err_msg", null)
result._state = config.get("state")
diff --git a/addons/gdUnit4/src/core/GdUnitRunner.gd b/addons/gdUnit4/src/core/GdUnitRunner.gd
index 68aadee3..65ae140e 100644
--- a/addons/gdUnit4/src/core/GdUnitRunner.gd
+++ b/addons/gdUnit4/src/core/GdUnitRunner.gd
@@ -36,7 +36,9 @@ func _ready() -> void:
push_error(config_result.error_message())
_state = EXIT
return
+ @warning_ignore("return_value_discarded")
_client.connect("connection_failed", _on_connection_failed)
+ @warning_ignore("return_value_discarded")
GdUnitSignals.instance().gdunit_event.connect(_on_gdunit_event)
var result := _client.start("127.0.0.1", _config.server_port())
if result.is_error():
@@ -75,11 +77,14 @@ func _process(_delta :float) -> void:
# process next test suite
set_process(false)
var test_suite :Node = _test_suites_to_process.pop_front()
+ @warning_ignore("unsafe_method_access")
if _cs_executor != null and _cs_executor.IsExecutable(test_suite):
+ @warning_ignore("unsafe_method_access")
_cs_executor.Execute(test_suite)
+ @warning_ignore("unsafe_property_access")
await _cs_executor.ExecutionCompleted
else:
- await _executor.execute(test_suite)
+ await _executor.execute(test_suite as GdUnitTestSuite)
set_process(true)
STOP:
_state = EXIT
@@ -133,6 +138,7 @@ func _do_filter_test_case(test_suite :Node, test_case :Node, included_tests :Pac
# we have a paremeterized test selection
if test_meta.size() > 1:
var test_param_index := test_meta[1]
+ @warning_ignore("unsafe_method_access")
test_case.set_test_parameter_index(test_param_index.to_int())
return
# the test is filtered out
diff --git a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd
index 173b536d..820f2482 100644
--- a/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd
+++ b/addons/gdUnit4/src/core/GdUnitRunnerConfig.gd
@@ -38,6 +38,7 @@ func server_port() -> int:
return _config.get(SERVER_PORT, -1)
+@warning_ignore("return_value_discarded")
func self_test() -> GdUnitRunnerConfig:
add_test_suite("res://addons/gdUnit4/test/")
add_test_suite("res://addons/gdUnit4/mono/test/")
@@ -52,6 +53,7 @@ func add_test_suite(p_resource_path :String) -> GdUnitRunnerConfig:
func add_test_suites(resource_paths :PackedStringArray) -> GdUnitRunnerConfig:
for resource_path_ in resource_paths:
+ @warning_ignore("return_value_discarded")
add_test_suite(resource_path_)
return self
@@ -60,8 +62,10 @@ func add_test_case(p_resource_path :String, test_name :StringName, test_param_in
var to_execute_ := to_execute()
var test_cases :PackedStringArray = to_execute_.get(p_resource_path, PackedStringArray())
if test_param_index != -1:
+ @warning_ignore("return_value_discarded")
test_cases.append("%s:%d" % [test_name, test_param_index])
else:
+ @warning_ignore("return_value_discarded")
test_cases.append(test_name)
to_execute_[p_resource_path] = test_cases
return self
@@ -72,18 +76,22 @@ 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 = GdUnitFileAccess.make_qualified_path(value).rsplit(":")
+ var parts: PackedStringArray = GdUnitFileAccess.make_qualified_path(value).rsplit(":")
if parts[0] == "res":
- parts.pop_front()
+ parts.remove_at(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])
+ 1:
+ skipped()[parts[0]] = PackedStringArray()
+ 2:
+ @warning_ignore("return_value_discarded")
+ skip_test_case(parts[0], parts[1])
return self
func skip_test_suites(resource_paths :PackedStringArray) -> GdUnitRunnerConfig:
for resource_path_ in resource_paths:
+ @warning_ignore("return_value_discarded")
skip_test_suite(resource_path_)
return self
@@ -91,6 +99,7 @@ func skip_test_suites(resource_paths :PackedStringArray) -> GdUnitRunnerConfig:
func skip_test_case(p_resource_path :String, test_name :StringName) -> GdUnitRunnerConfig:
var to_ignore := skipped()
var test_cases :PackedStringArray = to_ignore.get(p_resource_path, PackedStringArray())
+ @warning_ignore("return_value_discarded")
test_cases.append(test_name)
to_ignore[p_resource_path] = test_cases
return self
@@ -129,19 +138,20 @@ func load_config(path :String = CONFIG_FILE) -> GdUnitResult:
var error := test_json_conv.parse(content)
if error != OK:
return GdUnitResult.error("The runner configuration '%s' is invalid! The format is changed please delete it manually and start a new test run." % path)
- _config = test_json_conv.get_data() as Dictionary
+ _config = test_json_conv.get_data()
if not _config.has(VERSION):
return GdUnitResult.error("The runner configuration '%s' is invalid! The format is changed please delete it manually and start a new test run." % path)
fix_value_types()
return GdUnitResult.success(path)
+@warning_ignore("unsafe_cast")
func fix_value_types() -> void:
# fix float value to int json stores all numbers as float
var server_port_ :int = _config.get(SERVER_PORT, -1)
_config[SERVER_PORT] = server_port_
- convert_Array_to_PackedStringArray(_config[INCLUDED])
- convert_Array_to_PackedStringArray(_config[SKIPPED])
+ convert_Array_to_PackedStringArray(_config[INCLUDED] as Dictionary)
+ convert_Array_to_PackedStringArray(_config[SKIPPED] as Dictionary)
func convert_Array_to_PackedStringArray(data :Dictionary) -> void:
diff --git a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd
index bd2da3c8..c70be022 100644
--- a/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd
+++ b/addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd
@@ -3,7 +3,7 @@ class_name GdUnitSceneRunnerImpl
extends GdUnitSceneRunner
-var GdUnitFuncAssertImpl := ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE)
+var GdUnitFuncAssertImpl: GDScript = ResourceLoader.load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd", "GDScript", ResourceLoader.CACHE_MODE_REUSE)
# mapping of mouse buttons and his masks
@@ -19,29 +19,36 @@ const MAP_MOUSE_BUTTON_MASKS := {
}
var _is_disposed := false
-var _current_scene :Node = null
-var _awaiter :GdUnitAwaiter = GdUnitAwaiter.new()
-var _verbose :bool
-var _simulate_start_time :LocalTime
-var _last_input_event :InputEvent = null
+var _current_scene: Node = null
+var _awaiter: GdUnitAwaiter = GdUnitAwaiter.new()
+var _verbose: bool
+var _simulate_start_time: LocalTime
+var _last_input_event: InputEvent = null
var _mouse_button_on_press := []
var _key_on_press := []
var _action_on_press := []
-var _curent_mouse_position :Vector2
+var _curent_mouse_position: Vector2
+# holds the touch position for each touch index
+# { index: int = position: Vector2}
+var _current_touch_position: Dictionary = {}
+# holds the curretn touch drag position
+var _current_touch_drag_position: Vector2 = Vector2.ZERO
# time factor settings
var _time_factor := 1.0
-var _saved_iterations_per_second :float
+var _saved_iterations_per_second: float
var _scene_auto_free := false
-func _init(p_scene :Variant, p_verbose :bool, p_hide_push_errors := false) -> void:
+func _init(p_scene: Variant, p_verbose: bool, p_hide_push_errors := false) -> void:
_verbose = p_verbose
_saved_iterations_per_second = Engine.get_physics_ticks_per_second()
+ @warning_ignore("return_value_discarded")
set_time_factor(1)
# handle scene loading by resource path
if typeof(p_scene) == TYPE_STRING:
- if !ResourceLoader.exists(p_scene):
+ @warning_ignore("unsafe_cast")
+ if !ResourceLoader.exists(p_scene as String):
if not p_hide_push_errors:
push_error("GdUnitSceneRunner: Can't load scene by given resource path: '%s'. The resource does not exists." % p_scene)
return
@@ -49,7 +56,8 @@ func _init(p_scene :Variant, p_verbose :bool, p_hide_push_errors := false) -> vo
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()
+ @warning_ignore("unsafe_cast")
+ _current_scene = (load(p_scene as String) as PackedScene).instantiate()
_scene_auto_free = true
else:
# verify we have a node instance
@@ -62,10 +70,14 @@ func _init(p_scene :Variant, p_verbose :bool, p_hide_push_errors := false) -> vo
if not p_hide_push_errors:
push_error("GdUnitSceneRunner: Scene must be not null!")
return
+
_scene_tree().root.add_child(_current_scene)
# do finally reset all open input events when the scene is removed
+ @warning_ignore("return_value_discarded")
_scene_tree().root.child_exiting_tree.connect(func f(child :Node) -> void:
if child == _current_scene:
+ # we need to disable the processing to avoid input flush buffer errors
+ _current_scene.process_mode = Node.PROCESS_MODE_DISABLED
_reset_input_to_default()
)
_simulate_start_time = LocalTime.now()
@@ -78,7 +90,7 @@ func _init(p_scene :Variant, p_verbose :bool, p_hide_push_errors := false) -> vo
max_iteration_to_wait += 1
-func _notification(what :int) -> void:
+func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE and is_instance_valid(self):
# reset time factor to normal
__deactivate_time_factor()
@@ -89,45 +101,52 @@ func _notification(what :int) -> void:
_current_scene.free()
_is_disposed = true
_current_scene = null
- # we hide the scene/main window after runner is finished
- DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED)
func _scene_tree() -> SceneTree:
return Engine.get_main_loop() as SceneTree
-func simulate_action_pressed(action :String) -> GdUnitSceneRunner:
+@warning_ignore("return_value_discarded")
+func simulate_action_pressed(action: String) -> GdUnitSceneRunner:
simulate_action_press(action)
simulate_action_release(action)
return self
-func simulate_action_press(action :String) -> GdUnitSceneRunner:
+func simulate_action_press(action: String) -> GdUnitSceneRunner:
__print_current_focus()
var event := InputEventAction.new()
event.pressed = true
event.action = action
+ if Engine.get_version_info().hex >= 0x40300:
+ @warning_ignore("unsafe_property_access")
+ event.event_index = InputMap.get_actions().find(action)
_action_on_press.append(action)
return _handle_input_event(event)
-func simulate_action_release(action :String) -> GdUnitSceneRunner:
+func simulate_action_release(action: String) -> GdUnitSceneRunner:
__print_current_focus()
var event := InputEventAction.new()
event.pressed = false
event.action = action
+ if Engine.get_version_info().hex >= 0x40300:
+ @warning_ignore("unsafe_property_access")
+ event.event_index = InputMap.get_actions().find(action)
_action_on_press.erase(action)
return _handle_input_event(event)
-func simulate_key_pressed(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
+@warning_ignore("return_value_discarded")
+func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
simulate_key_press(key_code, shift_pressed, ctrl_pressed)
+ await _scene_tree().process_frame
simulate_key_release(key_code, shift_pressed, ctrl_pressed)
return self
-func simulate_key_press(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
+func simulate_key_press(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
__print_current_focus()
var event := InputEventKey.new()
event.pressed = true
@@ -141,7 +160,7 @@ func simulate_key_press(key_code :int, shift_pressed := false, ctrl_pressed := f
return _handle_input_event(event)
-func simulate_key_release(key_code :int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
+func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
__print_current_focus()
var event := InputEventKey.new()
event.pressed = false
@@ -155,7 +174,11 @@ func simulate_key_release(key_code :int, shift_pressed := false, ctrl_pressed :=
return _handle_input_event(event)
-func set_mouse_pos(pos :Vector2) -> GdUnitSceneRunner:
+func set_mouse_pos(pos: Vector2) -> GdUnitSceneRunner:
+ return set_mouse_position(pos)
+
+
+func set_mouse_position(pos: Vector2) -> GdUnitSceneRunner:
var event := InputEventMouseMotion.new()
event.position = pos
event.global_position = get_global_mouse_position()
@@ -165,7 +188,7 @@ func set_mouse_pos(pos :Vector2) -> GdUnitSceneRunner:
func get_mouse_position() -> Vector2:
if _last_input_event is InputEventMouse:
- return _last_input_event.position
+ return (_last_input_event as InputEventMouse).position
var current_scene := scene()
if current_scene != null:
return current_scene.get_viewport().get_mouse_position()
@@ -173,19 +196,20 @@ func get_mouse_position() -> Vector2:
func get_global_mouse_position() -> Vector2:
- return Engine.get_main_loop().root.get_mouse_position()
+ return (Engine.get_main_loop() as SceneTree).root.get_mouse_position()
-func simulate_mouse_move(pos :Vector2) -> GdUnitSceneRunner:
+func simulate_mouse_move(position: Vector2) -> GdUnitSceneRunner:
var event := InputEventMouseMotion.new()
- event.position = pos
- event.relative = pos - get_mouse_position()
+ event.position = position
+ event.relative = position - get_mouse_position()
event.global_position = get_global_mouse_position()
_apply_input_mouse_mask(event)
_apply_input_modifiers(event)
return _handle_input_event(event)
+@warning_ignore("return_value_discarded")
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()
@@ -199,6 +223,7 @@ func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_ty
return self
+@warning_ignore("return_value_discarded")
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()
@@ -211,36 +236,166 @@ func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_ty
return self
-func simulate_mouse_button_pressed(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner:
- simulate_mouse_button_press(buttonIndex, double_click)
- simulate_mouse_button_release(buttonIndex)
+@warning_ignore("return_value_discarded")
+func simulate_mouse_button_pressed(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner:
+ simulate_mouse_button_press(button_index, double_click)
+ simulate_mouse_button_release(button_index)
return self
-func simulate_mouse_button_press(buttonIndex :MouseButton, double_click := false) -> GdUnitSceneRunner:
+func simulate_mouse_button_press(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner:
var event := InputEventMouseButton.new()
- event.button_index = buttonIndex
+ event.button_index = button_index
event.pressed = true
event.double_click = double_click
_apply_input_mouse_position(event)
_apply_input_mouse_mask(event)
_apply_input_modifiers(event)
- _mouse_button_on_press.append(buttonIndex)
+ _mouse_button_on_press.append(button_index)
return _handle_input_event(event)
-func simulate_mouse_button_release(buttonIndex :MouseButton) -> GdUnitSceneRunner:
+func simulate_mouse_button_release(button_index: MouseButton) -> GdUnitSceneRunner:
var event := InputEventMouseButton.new()
- event.button_index = buttonIndex
+ event.button_index = button_index
event.pressed = false
_apply_input_mouse_position(event)
_apply_input_mouse_mask(event)
_apply_input_modifiers(event)
- _mouse_button_on_press.erase(buttonIndex)
+ _mouse_button_on_press.erase(button_index)
return _handle_input_event(event)
-func set_time_factor(time_factor := 1.0) -> GdUnitSceneRunner:
+@warning_ignore("return_value_discarded")
+func simulate_screen_touch_pressed(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner:
+ simulate_screen_touch_press(index, position, double_tap)
+ simulate_screen_touch_release(index)
+ return self
+
+
+@warning_ignore("return_value_discarded")
+func simulate_screen_touch_press(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner:
+ if is_emulate_mouse_from_touch():
+ # we need to simulate in addition to the touch the mouse events
+ set_mouse_pos(position)
+ simulate_mouse_button_press(MOUSE_BUTTON_LEFT)
+ # push touch press event at position
+ var event := InputEventScreenTouch.new()
+ event.window_id = scene().get_window().get_window_id()
+ event.index = index
+ event.position = position
+ event.double_tap = double_tap
+ event.pressed = true
+ _current_scene.get_viewport().push_input(event)
+ # save current drag position by index
+ _current_touch_position[index] = position
+ return self
+
+
+@warning_ignore("return_value_discarded")
+func simulate_screen_touch_release(index: int, double_tap := false) -> GdUnitSceneRunner:
+ if is_emulate_mouse_from_touch():
+ # we need to simulate in addition to the touch the mouse events
+ simulate_mouse_button_release(MOUSE_BUTTON_LEFT)
+ # push touch release event at position
+ var event := InputEventScreenTouch.new()
+ event.window_id = scene().get_window().get_window_id()
+ event.index = index
+ event.position = get_screen_touch_drag_position(index)
+ event.pressed = false
+ event.double_tap = (_last_input_event as InputEventScreenTouch).double_tap if _last_input_event is InputEventScreenTouch else double_tap
+ _current_scene.get_viewport().push_input(event)
+ return self
+
+
+func simulate_screen_touch_drag_relative(index: int, relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
+ var current_position: Vector2 = _current_touch_position[index]
+ return await _do_touch_drag_at(index, current_position + relative, time, trans_type)
+
+
+func simulate_screen_touch_drag_absolute(index: int, position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
+ return await _do_touch_drag_at(index, position, time, trans_type)
+
+
+@warning_ignore("return_value_discarded")
+func simulate_screen_touch_drag_drop(index: int, position: Vector2, drop_position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner:
+ simulate_screen_touch_press(index, position)
+ return await _do_touch_drag_at(index, drop_position, time, trans_type)
+
+
+@warning_ignore("return_value_discarded")
+func simulate_screen_touch_drag(index: int, position: Vector2) -> GdUnitSceneRunner:
+ if is_emulate_mouse_from_touch():
+ simulate_mouse_move(position)
+ var event := InputEventScreenDrag.new()
+ event.window_id = scene().get_window().get_window_id()
+ event.index = index
+ event.position = position
+ event.relative = _get_screen_touch_drag_position_or_default(index, position) - position
+ event.velocity = event.relative / _scene_tree().root.get_process_delta_time()
+ event.pressure = 1.0
+ _current_touch_position[index] = position
+ _current_scene.get_viewport().push_input(event)
+ return self
+
+
+func get_screen_touch_drag_position(index: int) -> Vector2:
+ if _current_touch_position.has(index):
+ return _current_touch_position[index]
+ push_error("No touch drag position for index '%d' is set!" % index)
+ return Vector2.ZERO
+
+
+func is_emulate_mouse_from_touch() -> bool:
+ return ProjectSettings.get_setting("input_devices/pointing/emulate_mouse_from_touch", true)
+
+
+func _get_screen_touch_drag_position_or_default(index: int, default_position: Vector2) -> Vector2:
+ if _current_touch_position.has(index):
+ return _current_touch_position[index]
+ return default_position
+
+
+@warning_ignore("return_value_discarded")
+func _do_touch_drag_at(index: int, drag_position: Vector2, time: float, trans_type: Tween.TransitionType) -> GdUnitSceneRunner:
+ # start draging
+ var event := InputEventScreenDrag.new()
+ event.window_id = scene().get_window().get_window_id()
+ event.index = index
+ event.position = get_screen_touch_drag_position(index)
+ event.pressure = 1.0
+ _current_touch_drag_position = event.position
+
+ var tween := _scene_tree().create_tween()
+ tween.tween_property(self, "_current_touch_drag_position", drag_position, time).set_trans(trans_type)
+ tween.play()
+
+ while not _current_touch_drag_position.is_equal_approx(drag_position):
+ if is_emulate_mouse_from_touch():
+ # we need to simulate in addition to the drag the mouse move events
+ simulate_mouse_move(event.position)
+ # send touche drag event to new position
+ event.relative = _current_touch_drag_position - event.position
+ event.velocity = event.relative / _scene_tree().root.get_process_delta_time()
+ event.position = _current_touch_drag_position
+ _current_scene.get_viewport().push_input(event)
+ await _scene_tree().process_frame
+
+ # finaly drop it
+ if is_emulate_mouse_from_touch():
+ simulate_mouse_move(drag_position)
+ simulate_mouse_button_release(MOUSE_BUTTON_LEFT)
+ var touch_drop_event := InputEventScreenTouch.new()
+ touch_drop_event.window_id = event.window_id
+ touch_drop_event.index = event.index
+ touch_drop_event.position = drag_position
+ touch_drop_event.pressed = false
+ _current_scene.get_viewport().push_input(touch_drop_event)
+ await _scene_tree().process_frame
+ return self
+
+
+func set_time_factor(time_factor: float = 1.0) -> GdUnitSceneRunner:
_time_factor = min(9.0, time_factor)
__activate_time_factor()
__print("set time factor: %f" % _time_factor)
@@ -248,7 +403,7 @@ func set_time_factor(time_factor := 1.0) -> GdUnitSceneRunner:
return self
-func simulate_frames(frames: int, delta_milli :int = -1) -> GdUnitSceneRunner:
+func simulate_frames(frames: int, delta_milli: int = -1) -> GdUnitSceneRunner:
var time_shift_frames :int = max(1, frames / _time_factor)
for frame in time_shift_frames:
if delta_milli == -1:
@@ -259,74 +414,73 @@ func simulate_frames(frames: int, delta_milli :int = -1) -> GdUnitSceneRunner:
func simulate_until_signal(
- signal_name :String,
- arg0 :Variant = NO_ARG,
- arg1 :Variant = NO_ARG,
- arg2 :Variant = NO_ARG,
- arg3 :Variant = NO_ARG,
- arg4 :Variant = NO_ARG,
- arg5 :Variant = NO_ARG,
- arg6 :Variant = NO_ARG,
- arg7 :Variant = NO_ARG,
- arg8 :Variant = NO_ARG,
- arg9 :Variant = NO_ARG) -> GdUnitSceneRunner:
- var args :Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
+ signal_name: String,
+ arg0: Variant = NO_ARG,
+ arg1: Variant = NO_ARG,
+ arg2: Variant = NO_ARG,
+ arg3: Variant = NO_ARG,
+ arg4: Variant = NO_ARG,
+ arg5: Variant = NO_ARG,
+ arg6: Variant = NO_ARG,
+ arg7: Variant = NO_ARG,
+ arg8: Variant = NO_ARG,
+ arg9: Variant = NO_ARG) -> GdUnitSceneRunner:
+ var args: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
await _awaiter.await_signal_idle_frames(scene(), signal_name, args, 10000)
return self
func simulate_until_object_signal(
- source :Object,
- signal_name :String,
- arg0 :Variant = NO_ARG,
- arg1 :Variant = NO_ARG,
- arg2 :Variant = NO_ARG,
- arg3 :Variant = NO_ARG,
- arg4 :Variant = NO_ARG,
- arg5 :Variant = NO_ARG,
- arg6 :Variant = NO_ARG,
- arg7 :Variant = NO_ARG,
- arg8 :Variant = NO_ARG,
- arg9 :Variant = NO_ARG) -> GdUnitSceneRunner:
- var args :Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
+ source: Object,
+ signal_name: String,
+ arg0: Variant = NO_ARG,
+ arg1: Variant = NO_ARG,
+ arg2: Variant = NO_ARG,
+ arg3: Variant = NO_ARG,
+ arg4: Variant = NO_ARG,
+ arg5: Variant = NO_ARG,
+ arg6: Variant = NO_ARG,
+ arg7: Variant = NO_ARG,
+ arg8: Variant = NO_ARG,
+ arg9: Variant = NO_ARG) -> GdUnitSceneRunner:
+ var args: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
await _awaiter.await_signal_idle_frames(source, signal_name, args, 10000)
return self
-func await_func(func_name :String, args := []) -> GdUnitFuncAssert:
+func await_func(func_name: String, args := []) -> GdUnitFuncAssert:
return GdUnitFuncAssertImpl.new(scene(), func_name, args)
-func await_func_on(instance :Object, func_name :String, args := []) -> GdUnitFuncAssert:
+func await_func_on(instance: Object, func_name: String, args := []) -> GdUnitFuncAssert:
return GdUnitFuncAssertImpl.new(instance, func_name, args)
-func await_signal(signal_name :String, args := [], timeout := 2000 ) -> void:
+func await_signal(signal_name: String, args := [], timeout := 2000 ) -> void:
await _awaiter.await_signal_on(scene(), signal_name, args, timeout)
-func await_signal_on(source :Object, signal_name :String, args := [], timeout := 2000 ) -> void:
+func await_signal_on(source: Object, signal_name: String, args := [], timeout := 2000 ) -> void:
await _awaiter.await_signal_on(source, signal_name, args, timeout)
-# maximizes the window to bring the scene visible
func maximize_view() -> GdUnitSceneRunner:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
DisplayServer.window_move_to_foreground()
return self
-func _property_exists(name :String) -> bool:
+func _property_exists(name: String) -> bool:
return scene().get_property_list().any(func(properties :Dictionary) -> bool: return properties["name"] == name)
-func get_property(name :String) -> Variant:
+func get_property(name: String) -> Variant:
if not _property_exists(name):
return "The property '%s' not exist checked loaded scene." % name
return scene().get(name)
-func set_property(name :String, value :Variant) -> bool:
+func set_property(name: String, value: Variant) -> bool:
if not _property_exists(name):
push_error("The property named '%s' cannot be set, it does not exist!" % name)
return false;
@@ -335,24 +489,24 @@ func set_property(name :String, value :Variant) -> bool:
func invoke(
- name :String,
- arg0 :Variant = NO_ARG,
- arg1 :Variant = NO_ARG,
- arg2 :Variant = NO_ARG,
- arg3 :Variant = NO_ARG,
- arg4 :Variant = NO_ARG,
- arg5 :Variant = NO_ARG,
- arg6 :Variant = NO_ARG,
- arg7 :Variant = NO_ARG,
- arg8 :Variant = NO_ARG,
- arg9 :Variant = NO_ARG) -> Variant:
- var args :Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
+ name: String,
+ arg0: Variant = NO_ARG,
+ arg1: Variant = NO_ARG,
+ arg2: Variant = NO_ARG,
+ arg3: Variant = NO_ARG,
+ arg4: Variant = NO_ARG,
+ arg5: Variant = NO_ARG,
+ arg6: Variant = NO_ARG,
+ arg7: Variant = NO_ARG,
+ arg8: Variant = NO_ARG,
+ arg9: Variant = NO_ARG) -> Variant:
+ var args: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
if scene().has_method(name):
return scene().callv(name, args)
return "The method '%s' not exist checked loaded scene." % name
-func find_child(name :String, recursive :bool = true, owned :bool = false) -> Node:
+func find_child(name: String, recursive: bool = true, owned: bool = false) -> Node:
return scene().find_child(name, recursive, owned)
@@ -377,37 +531,40 @@ func __deactivate_time_factor() -> void:
# copy over current active modifiers
-func _apply_input_modifiers(event :InputEvent) -> void:
+func _apply_input_modifiers(event: InputEvent) -> void:
if _last_input_event is InputEventWithModifiers and event is InputEventWithModifiers:
- event.meta_pressed = event.meta_pressed or _last_input_event.meta_pressed
- event.alt_pressed = event.alt_pressed or _last_input_event.alt_pressed
- event.shift_pressed = event.shift_pressed or _last_input_event.shift_pressed
- event.ctrl_pressed = event.ctrl_pressed or _last_input_event.ctrl_pressed
+ var last_input_event := _last_input_event as InputEventWithModifiers
+ var _event := event as InputEventWithModifiers
+ _event.meta_pressed = _event.meta_pressed or last_input_event.meta_pressed
+ _event.alt_pressed = _event.alt_pressed or last_input_event.alt_pressed
+ _event.shift_pressed = _event.shift_pressed or last_input_event.shift_pressed
+ _event.ctrl_pressed = _event.ctrl_pressed or last_input_event.ctrl_pressed
# this line results into reset the control_pressed state!!!
#event.command_or_control_autoremap = event.command_or_control_autoremap or _last_input_event.command_or_control_autoremap
# copy over current active mouse mask and combine with curren mask
-func _apply_input_mouse_mask(event :InputEvent) -> void:
+func _apply_input_mouse_mask(event: InputEvent) -> void:
# first apply last mask
if _last_input_event is InputEventMouse and event is InputEventMouse:
- event.button_mask |= _last_input_event.button_mask
+ (event as InputEventMouse).button_mask |= (_last_input_event as InputEventMouse).button_mask
if event is InputEventMouseButton:
- var button_mask :int = MAP_MOUSE_BUTTON_MASKS.get(event.get_button_index(), 0)
- if event.is_pressed():
- event.button_mask |= button_mask
+ var _event := event as InputEventMouseButton
+ var button_mask :int = MAP_MOUSE_BUTTON_MASKS.get(_event.get_button_index(), 0)
+ if _event.is_pressed():
+ _event.button_mask |= button_mask
else:
- event.button_mask ^= button_mask
+ _event.button_mask ^= button_mask
# copy over last mouse position if need
-func _apply_input_mouse_position(event :InputEvent) -> void:
+func _apply_input_mouse_position(event: InputEvent) -> void:
if _last_input_event is InputEventMouse and event is InputEventMouseButton:
- event.position = _last_input_event.position
+ (event as InputEventMouseButton).position = (_last_input_event as InputEventMouse).position
## handle input action via Input modifieres
-func _handle_actions(event :InputEventAction) -> bool:
+func _handle_actions(event: InputEventAction) -> bool:
if not InputMap.event_is_action(event, event.action, true):
return false
__print(" process action %s (%s) <- %s" % [scene(), _scene_name(), event.as_text()])
@@ -419,20 +576,23 @@ func _handle_actions(event :InputEventAction) -> bool:
# 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) -> GdUnitSceneRunner:
+@warning_ignore("return_value_discarded")
+func _handle_input_event(event: InputEvent) -> GdUnitSceneRunner:
if event is InputEventMouse:
- Input.warp_mouse(event.position)
+ Input.warp_mouse((event as InputEventMouse).position as Vector2)
Input.parse_input_event(event)
if event is InputEventAction:
- _handle_actions(event)
+ _handle_actions(event as InputEventAction)
- Input.flush_buffered_events()
var current_scene := scene()
if is_instance_valid(current_scene):
+ # do not flush events if node processing disabled otherwise we run into errors at tree removed
+ if _current_scene.process_mode != Node.PROCESS_MODE_DISABLED:
+ Input.flush_buffered_events()
__print(" process event %s (%s) <- %s" % [current_scene, _scene_name(), event.as_text()])
if(current_scene.has_method("_gui_input")):
- current_scene._gui_input(event)
+ (current_scene as Control)._gui_input(event)
if(current_scene.has_method("_unhandled_input")):
current_scene._unhandled_input(event)
current_scene.get_viewport().set_input_as_handled()
@@ -442,6 +602,7 @@ func _handle_input_event(event :InputEvent) -> GdUnitSceneRunner:
return self
+@warning_ignore("return_value_discarded")
func _reset_input_to_default() -> void:
# reset all mouse button to inital state if need
for m_button :int in _mouse_button_on_press.duplicate():
@@ -459,11 +620,12 @@ func _reset_input_to_default() -> void:
simulate_action_release(action)
_action_on_press.clear()
- Input.flush_buffered_events()
+ if is_instance_valid(_current_scene) and _current_scene.process_mode != Node.PROCESS_MODE_DISABLED:
+ Input.flush_buffered_events()
_last_input_event = null
-func __print(message :String) -> void:
+func __print(message: String) -> void:
if _verbose:
prints(message)
diff --git a/addons/gdUnit4/src/core/GdUnitSettings.gd b/addons/gdUnit4/src/core/GdUnitSettings.gd
index cbb6abc9..e4bf4781 100644
--- a/addons/gdUnit4/src/core/GdUnitSettings.gd
+++ b/addons/gdUnit4/src/core/GdUnitSettings.gd
@@ -14,8 +14,10 @@ const SERVER_TIMEOUT = GROUP_COMMON + "/server_connection_timeout_minutes"
const GROUP_TEST = COMMON_SETTINGS + "/test"
const TEST_TIMEOUT = GROUP_TEST + "/test_timeout_seconds"
const TEST_LOOKUP_FOLDER = GROUP_TEST + "/test_lookup_folder"
-const TEST_SITE_NAMING_CONVENTION = GROUP_TEST + "/test_suite_naming_convention"
+const TEST_SUITE_NAMING_CONVENTION = GROUP_TEST + "/test_suite_naming_convention"
const TEST_DISCOVER_ENABLED = GROUP_TEST + "/test_discovery"
+const TEST_FLAKY_CHECK = GROUP_TEST + "/flaky_check_enable"
+const TEST_FLAKY_MAX_RETRIES = GROUP_TEST + "/flaky_max_retries"
# Report Setiings
@@ -81,7 +83,7 @@ const DEFAULT_TEST_TIMEOUT :int = 60*5
const DEFAULT_TEST_LOOKUP_FOLDER := "test"
# help texts
-const HELP_TEST_LOOKUP_FOLDER := "Sets the subfolder for the search/creation of test suites. (leave empty to use source folder)"
+const HELP_TEST_LOOKUP_FOLDER := "Subfolder where test suites are located (or empty to use source folder directly)"
enum NAMING_CONVENTIONS {
AUTO_DETECT,
@@ -90,29 +92,36 @@ enum NAMING_CONVENTIONS {
}
+const _VALUE_SET_SEPARATOR = "\f" # ASCII Form-feed character (AKA page break)
+
+
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.")
+ create_property_if_need(UPDATE_NOTIFICATION_ENABLED, true, "Show notification if new gdUnit4 version is found")
+ # test settings
+ create_property_if_need(SERVER_TIMEOUT, DEFAULT_SERVER_TIMEOUT, "Server connection timeout in minutes")
+ create_property_if_need(TEST_TIMEOUT, DEFAULT_TEST_TIMEOUT, "Test case runtime timeout in seconds")
create_property_if_need(TEST_LOOKUP_FOLDER, DEFAULT_TEST_LOOKUP_FOLDER, HELP_TEST_LOOKUP_FOLDER)
- create_property_if_need(TEST_SITE_NAMING_CONVENTION, NAMING_CONVENTIONS.AUTO_DETECT, "Sets test-suite genrate script name convention.", NAMING_CONVENTIONS.keys())
- create_property_if_need(TEST_DISCOVER_ENABLED, false, "Enables/Disables the automatic detection of tests by finding tests in test lookup folders at runtime.")
- create_property_if_need(REPORT_PUSH_ERRORS, false, "Enables/Disables report of push_error() as failure!")
- create_property_if_need(REPORT_SCRIPT_ERRORS, true, "Enables/Disables report of script errors as failure!")
- create_property_if_need(REPORT_ORPHANS, true, "Enables/Disables orphan reporting.")
- create_property_if_need(REPORT_ASSERT_ERRORS, true, "Enables/Disables error reporting checked asserts.")
- create_property_if_need(REPORT_ASSERT_WARNINGS, true, "Enables/Disables warning reporting checked asserts")
- create_property_if_need(REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE, true, "Enabled/disabled number values will be compared strictly by type. (real vs int)")
+ create_property_if_need(TEST_SUITE_NAMING_CONVENTION, NAMING_CONVENTIONS.AUTO_DETECT, "Naming convention to use when generating testsuites", NAMING_CONVENTIONS.keys())
+ create_property_if_need(TEST_DISCOVER_ENABLED, false, "Automatically detect new tests in test lookup folders at runtime")
+ create_property_if_need(TEST_FLAKY_CHECK, false, "Rerun tests on failure and mark them as FLAKY")
+ create_property_if_need(TEST_FLAKY_MAX_RETRIES, 3, "Sets the number of retries for rerunning a flaky test")
+ # report settings
+ create_property_if_need(REPORT_PUSH_ERRORS, false, "Report push_error() as failure")
+ create_property_if_need(REPORT_SCRIPT_ERRORS, true, "Report script errors as failure")
+ create_property_if_need(REPORT_ORPHANS, true, "Report orphaned nodes after tests finish")
+ create_property_if_need(REPORT_ASSERT_ERRORS, true, "Report assertion failures as errors")
+ create_property_if_need(REPORT_ASSERT_WARNINGS, true, "Report assertion failures as warnings")
+ create_property_if_need(REPORT_ASSERT_STRICT_NUMBER_TYPE_COMPARE, true, "Compare number values strictly by type (real vs int)")
# inspector
create_property_if_need(INSPECTOR_NODE_COLLAPSE, true,
- "Enables/Disables that the testsuite node is closed after a successful test run.")
+ "Close testsuite node after a successful test run.")
create_property_if_need(INSPECTOR_TREE_VIEW_MODE, GdUnitInspectorTreeConstants.TREE_VIEW_MODE.TREE,
- "Sets the inspector panel presentation.", GdUnitInspectorTreeConstants.TREE_VIEW_MODE.keys())
+ "Inspector panel presentation mode", GdUnitInspectorTreeConstants.TREE_VIEW_MODE.keys())
create_property_if_need(INSPECTOR_TREE_SORT_MODE, GdUnitInspectorTreeConstants.SORT_MODE.UNSORTED,
- "Sets the inspector panel presentation.", GdUnitInspectorTreeConstants.SORT_MODE.keys())
+ "Inspector panel sorting mode", GdUnitInspectorTreeConstants.SORT_MODE.keys())
create_property_if_need(INSPECTOR_TOOLBAR_BUTTON_RUN_OVERALL, false,
- "Shows/Hides the 'Run overall Tests' button in the inspector toolbar.")
- create_property_if_need(TEMPLATE_TS_GD, GdUnitTestSuiteTemplate.default_GD_template(), "Defines the test suite template")
+ "Show 'Run overall Tests' button in the inspector toolbar")
+ create_property_if_need(TEMPLATE_TS_GD, GdUnitTestSuiteTemplate.default_GD_template(), "Test suite template to use")
create_shortcut_properties_if_need()
migrate_properties()
@@ -129,17 +138,17 @@ static func migrate_properties() -> void:
static func create_shortcut_properties_if_need() -> void:
# inspector
- create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS), "Rerun of the last tests performed.")
- create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG), "Rerun of the last tests performed (Debug).")
- create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_OVERALL, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL), "Runs all tests (Debug).")
- create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_STOP, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.STOP_TEST_RUN), "Stops the current test execution.")
+ create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS), "Rerun the most recently executed tests")
+ create_property_if_need(SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG), "Rerun the most recently executed tests (Debug mode)")
+ create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_OVERALL, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL), "Runs all tests (Debug mode)")
+ create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_STOP, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.STOP_TEST_RUN), "Stop the current test execution")
# script editor
- create_property_if_need(SHORTCUT_EDITOR_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTCASE), "Runs the currently selected test.")
- create_property_if_need(SHORTCUT_EDITOR_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG), "Runs the currently selected test (Debug).")
- create_property_if_need(SHORTCUT_EDITOR_CREATE_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.CREATE_TEST), "Creates a new test case for the currently selected function.")
+ create_property_if_need(SHORTCUT_EDITOR_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTCASE), "Run the currently selected test")
+ create_property_if_need(SHORTCUT_EDITOR_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG), "Run the currently selected test (Debug mode).")
+ create_property_if_need(SHORTCUT_EDITOR_CREATE_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.CREATE_TEST), "Create a new test case for the currently selected function")
# filesystem
- create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Runs all test suites on the selected folder or file.")
- create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Runs all test suites on the selected folder or file (Debug).")
+ create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Run all test suites in the selected folder or file")
+ create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.NONE), "Run all test suites in the selected folder or file (Debug)")
static func create_property_if_need(name :String, default :Variant, help :="", value_set := PackedStringArray()) -> void:
@@ -148,7 +157,7 @@ static func create_property_if_need(name :String, default :Variant, help :="", v
ProjectSettings.set_setting(name, default)
ProjectSettings.set_initial_value(name, default)
- help += "" if value_set.is_empty() else " %s" % value_set
+ help = help if value_set.is_empty() else "%s%s%s" % [help, _VALUE_SET_SEPARATOR, value_set]
set_help(name, default, help)
@@ -175,6 +184,7 @@ static func is_update_notification_enabled() -> bool:
static func set_update_notification(enable :bool) -> void:
ProjectSettings.set_setting(UPDATE_NOTIFICATION_ENABLED, enable)
+ @warning_ignore("return_value_discarded")
ProjectSettings.save()
@@ -185,6 +195,7 @@ static func get_log_path() -> String:
static func set_log_path(path :String) -> void:
ProjectSettings.set_setting(STDOUT_ENABLE_TO_FILE, true)
ProjectSettings.set_setting(STDOUT_WITE_TO_FILE, path)
+ @warning_ignore("return_value_discarded")
ProjectSettings.save()
@@ -261,6 +272,14 @@ static func is_test_discover_enabled() -> bool:
return get_setting(TEST_DISCOVER_ENABLED, false)
+static func is_test_flaky_check_enabled() -> bool:
+ return get_setting(TEST_FLAKY_CHECK, false)
+
+
+static func get_flaky_max_retries() -> int:
+ return get_setting(TEST_FLAKY_MAX_RETRIES, 3)
+
+
static func set_test_discover_enabled(enable :bool) -> void:
var property := get_property(TEST_DISCOVER_ENABLED)
property.set_value(enable)
@@ -271,29 +290,34 @@ static func is_log_enabled() -> bool:
return ProjectSettings.get_setting(STDOUT_ENABLE_TO_FILE)
-static func list_settings(category :String) -> Array[GdUnitProperty]:
- var settings :Array[GdUnitProperty] = []
+static func list_settings(category: String) -> Array[GdUnitProperty]:
+ var settings: Array[GdUnitProperty] = []
for property in ProjectSettings.get_property_list():
var property_name :String = property["name"]
if property_name.begins_with(category):
- 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)
- settings.append(GdUnitProperty.new(property_name, property["type"], value, default, help, value_set))
+ settings.append(build_property(property_name, property))
return settings
static func extract_value_set_from_help(value :String) -> PackedStringArray:
+ var split_value := value.split(_VALUE_SET_SEPARATOR)
+ if not split_value.size() > 1:
+ return PackedStringArray()
+
var regex := RegEx.new()
+ @warning_ignore("return_value_discarded")
regex.compile("\\[(.+)\\]")
- var matches := regex.search_all(value)
+ var matches := regex.search_all(split_value[1])
if matches.is_empty():
return PackedStringArray()
- var values :String = matches[0].get_string(1)
+ var values: String = matches[0].get_string(1)
return values.replacen(" ", "").replacen("\"", "").split(",", false)
+static func extract_help_text(value :String) -> String:
+ return value.split(_VALUE_SET_SEPARATOR)[0]
+
+
static func update_property(property :GdUnitProperty) -> Variant:
var current_value :Variant = ProjectSettings.get_setting(property.name())
if current_value != property.value():
@@ -315,7 +339,7 @@ static func reset_property(property :GdUnitProperty) -> void:
static func validate_property_value(property :GdUnitProperty) -> Variant:
match property.name():
TEST_LOOKUP_FOLDER:
- return validate_lookup_folder(property.value())
+ return validate_lookup_folder(property.value_as_string())
_: return null
@@ -349,14 +373,19 @@ static func get_property(name :String) -> GdUnitProperty:
for property in ProjectSettings.get_property_list():
var property_name :String = property["name"]
if property_name == 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)
- return GdUnitProperty.new(property_name, property["type"], value, default, help, value_set)
+ return build_property(name, property)
return null
+static func build_property(property_name: String, property: Dictionary) -> GdUnitProperty:
+ var value: Variant = ProjectSettings.get_setting(property_name)
+ var value_type: int = property["type"]
+ var default: Variant = ProjectSettings.property_get_revert(property_name)
+ var help: String = property["hint_string"]
+ var value_set := extract_value_set_from_help(help)
+ return GdUnitProperty.new(property_name, value_type, value, default, extract_help_text(help), value_set)
+
+
static func migrate_property(old_property :String, new_property :String, default_value :Variant, help :String, converter := Callable()) -> void:
var property := get_property(old_property)
if property == null:
@@ -371,8 +400,10 @@ static func migrate_property(old_property :String, new_property :String, default
static func dump_to_tmp() -> void:
+ @warning_ignore("return_value_discarded")
ProjectSettings.save_custom("user://project_settings.godot")
static func restore_dump_from_tmp() -> void:
+ @warning_ignore("return_value_discarded")
DirAccess.copy_absolute("user://project_settings.godot", "res://project.godot")
diff --git a/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd b/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd
index b2bb8348..ccc013fb 100644
--- a/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd
+++ b/addons/gdUnit4/src/core/GdUnitSignalAwaiter.gd
@@ -41,12 +41,15 @@ func elapsed_time() -> float:
func on_signal(source :Object, signal_name :String, expected_signal_args :Array) -> Variant:
# register checked signal to wait for
+ @warning_ignore("return_value_discarded")
source.connect(signal_name, _on_signal_emmited)
# install timeout timer
+ var scene_tree := Engine.get_main_loop() as SceneTree
var timer := Timer.new()
- Engine.get_main_loop().root.add_child(timer)
+ scene_tree.root.add_child(timer)
timer.add_to_group("GdUnitTimers")
timer.set_one_shot(true)
+ @warning_ignore("return_value_discarded")
timer.timeout.connect(_do_interrupt, CONNECT_DEFERRED)
timer.start(_timeout_millis * 0.001 * Engine.get_time_scale())
@@ -61,12 +64,13 @@ func on_signal(source :Object, signal_name :String, expected_signal_args :Array)
value = [value]
if expected_signal_args.size() == 0 or GdObjects.equals(value, expected_signal_args):
break
- await Engine.get_main_loop().process_frame
+ await scene_tree.process_frame
source.disconnect(signal_name, _on_signal_emmited)
_time_left = timer.time_left
- await Engine.get_main_loop().process_frame
- if value is Array and value.size() == 1:
+ await scene_tree.process_frame
+ @warning_ignore("unsafe_cast")
+ if value is Array and (value as Array).size() == 1:
return value[0]
return value
diff --git a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd b/addons/gdUnit4/src/core/GdUnitSignalCollector.gd
index 12e22b0e..10bacc4a 100644
--- a/addons/gdUnit4/src/core/GdUnitSignalCollector.gd
+++ b/addons/gdUnit4/src/core/GdUnitSignalCollector.gd
@@ -30,8 +30,8 @@ func register_emitter(emitter :Object) -> void:
return
_collected_signals[emitter] = Dictionary()
# connect to 'tree_exiting' of the emitter to finally release all acquired resources/connections.
- if emitter is Node and !emitter.tree_exiting.is_connected(unregister_emitter):
- emitter.tree_exiting.connect(unregister_emitter.bind(emitter))
+ if emitter is Node and !(emitter as Node).tree_exiting.is_connected(unregister_emitter):
+ (emitter as Node).tree_exiting.connect(unregister_emitter.bind(emitter))
# connect to all signals of the emitter we want to collect
for signal_def in emitter.get_signal_list():
var signal_name :String = signal_def["name"]
@@ -54,6 +54,7 @@ func unregister_emitter(emitter :Object) -> void:
var signal_name :String = signal_def["name"]
if emitter.is_connected(signal_name, _on_signal_emmited):
emitter.disconnect(signal_name, _on_signal_emmited.bind(emitter, signal_name))
+ @warning_ignore("return_value_discarded")
_collected_signals.erase(emitter)
@@ -77,7 +78,8 @@ func _on_signal_emmited(
var emitter :Object = signal_args.pop_back()
#prints("_on_signal_emmited:", emitter, signal_name, signal_args)
if is_signal_collecting(emitter, signal_name):
- _collected_signals[emitter][signal_name].append(signal_args)
+ @warning_ignore("unsafe_cast")
+ (_collected_signals[emitter][signal_name] as Array).append(signal_args)
func reset_received_signals(emitter :Object, signal_name: String, signal_args :Array) -> void:
@@ -85,12 +87,14 @@ func reset_received_signals(emitter :Object, signal_name: String, signal_args :A
if _collected_signals.has(emitter):
var signals_by_emitter :Dictionary = _collected_signals[emitter]
if signals_by_emitter.has(signal_name):
- _collected_signals[emitter][signal_name].erase(signal_args)
+ @warning_ignore("unsafe_cast")
+ (_collected_signals[emitter][signal_name] as Array).erase(signal_args)
#_debug_signal_list("after claer");
func is_signal_collecting(emitter :Object, signal_name :String) -> bool:
- return _collected_signals.has(emitter) and _collected_signals[emitter].has(signal_name)
+ @warning_ignore("unsafe_cast")
+ return _collected_signals.has(emitter) and (_collected_signals[emitter] as Dictionary).has(signal_name)
func match(emitter :Object, signal_name :String, args :Array) -> bool:
diff --git a/addons/gdUnit4/src/core/GdUnitSignals.gd b/addons/gdUnit4/src/core/GdUnitSignals.gd
index 652b7af6..b67d7a19 100644
--- a/addons/gdUnit4/src/core/GdUnitSignals.gd
+++ b/addons/gdUnit4/src/core/GdUnitSignals.gd
@@ -17,8 +17,6 @@ signal gdunit_add_test_suite(test_suite :GdUnitTestSuiteDto)
@warning_ignore("unused_signal")
signal gdunit_message(message :String)
@warning_ignore("unused_signal")
-signal gdunit_report(execution_context_id :int, report :GdUnitReport)
-@warning_ignore("unused_signal")
signal gdunit_set_test_failed(is_failed :bool)
@warning_ignore("unused_signal")
signal gdunit_settings_changed(property :GdUnitProperty)
@@ -38,7 +36,10 @@ static func dispose() -> void:
var signals := instance()
# cleanup connected signals
for signal_ in signals.get_signal_list():
- for connection in signals.get_signal_connection_list(signal_["name"]):
- connection["signal"].disconnect(connection["callable"])
+ @warning_ignore("unsafe_cast")
+ for connection in signals.get_signal_connection_list(signal_["name"] as StringName):
+ var _signal: Signal = connection["signal"]
+ var _callable: Callable = connection["callable"]
+ _signal.disconnect(_callable)
signals = null
Engine.remove_meta(META_KEY)
diff --git a/addons/gdUnit4/src/core/GdUnitSingleton.gd b/addons/gdUnit4/src/core/GdUnitSingleton.gd
index ed6103e7..83a186f3 100644
--- a/addons/gdUnit4/src/core/GdUnitSingleton.gd
+++ b/addons/gdUnit4/src/core/GdUnitSingleton.gd
@@ -17,12 +17,14 @@ static func instance(name :String, clazz :Callable) -> Variant:
return Engine.get_meta(name)
var singleton :Variant = clazz.call()
if is_instance_of(singleton, RefCounted):
- push_error("Invalid singleton implementation detected for '%s' is `%s`!" % [name, singleton.get_class()])
+ @warning_ignore("unsafe_cast")
+ push_error("Invalid singleton implementation detected for '%s' is `%s`!" % [name, (singleton as RefCounted).get_class()])
return
Engine.set_meta(name, singleton)
GdUnitTools.prints_verbose("Register singleton '%s:%s'" % [name, singleton])
var singletons :PackedStringArray = Engine.get_meta(MEATA_KEY, PackedStringArray())
+ @warning_ignore("return_value_discarded")
singletons.append(name)
Engine.set_meta(MEATA_KEY, singletons)
return singleton
@@ -36,6 +38,7 @@ static func unregister(p_singleton :String, use_call_deferred :bool = false) ->
singletons.remove_at(index)
var instance_ :Object = Engine.get_meta(p_singleton)
GdUnitTools.prints_verbose(" Free singleton instance '%s:%s'" % [p_singleton, instance_])
+ @warning_ignore("return_value_discarded")
GdUnitTools.free_instance(instance_, use_call_deferred)
Engine.remove_meta(p_singleton)
GdUnitTools.prints_verbose(" Successfully freed '%s'" % p_singleton)
@@ -44,7 +47,7 @@ static func unregister(p_singleton :String, use_call_deferred :bool = false) ->
static func dispose(use_call_deferred :bool = false) -> void:
# use a copy because unregister is modify the singletons array
- var singletons := PackedStringArray(Engine.get_meta(MEATA_KEY, PackedStringArray()))
+ var singletons: PackedStringArray = Engine.get_meta(MEATA_KEY, PackedStringArray())
GdUnitTools.prints_verbose("----------------------------------------------------------------")
GdUnitTools.prints_verbose("Cleanup singletons %s" % singletons)
for singleton in singletons:
diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd
index 6cd2ee4b..aabbac75 100644
--- a/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd
+++ b/addons/gdUnit4/src/core/GdUnitTestSuiteBuilder.gd
@@ -5,7 +5,9 @@ extends RefCounted
static func create(source :Script, line_number :int) -> GdUnitResult:
var test_suite_path := GdUnitTestSuiteScanner.resolve_test_suite_path(source.resource_path, GdUnitSettings.test_root_folder())
# we need to save and close the testsuite and source if is current opened before modify
+ @warning_ignore("return_value_discarded")
ScriptEditorControls.save_an_open_script(source.resource_path)
+ @warning_ignore("return_value_discarded")
ScriptEditorControls.save_an_open_script(test_suite_path, true)
if GdObjects.is_cs_script(source):
return GdUnit4CSharpApiLoader.create_test_suite(source.resource_path, line_number+1, test_suite_path)
diff --git a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd
index 4ae3ea66..395958c3 100644
--- a/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd
+++ b/addons/gdUnit4/src/core/GdUnitTestSuiteScanner.gd
@@ -30,8 +30,10 @@ func prescan_testsuite_classes() -> void:
var base_class :String = script_meta["base"]
var resource_path :String = script_meta["path"]
if base_class == "GdUnitTestSuite":
+ @warning_ignore("return_value_discarded")
_included_resources.append(resource_path)
elif ClassDB.class_exists(base_class):
+ @warning_ignore("return_value_discarded")
_excluded_resources.append(resource_path)
@@ -54,6 +56,7 @@ func _scan_test_suites(dir :DirAccess, collected_suites :Array[Node]) -> Array[N
if exclude_scan_directories.has(dir.get_current_dir()):
return collected_suites
prints("Scanning for test suites in:", dir.get_current_dir())
+ @warning_ignore("return_value_discarded")
dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
var file_name := dir.get_next()
while file_name != "":
@@ -61,6 +64,7 @@ func _scan_test_suites(dir :DirAccess, collected_suites :Array[Node]) -> Array[N
if dir.current_is_dir():
var sub_dir := DirAccess.open(resource_path)
if sub_dir != null:
+ @warning_ignore("return_value_discarded")
_scan_test_suites(sub_dir, collected_suites)
else:
var time := LocalTime.now()
@@ -91,7 +95,7 @@ func _parse_is_test_suite(resource_path :String) -> Node:
return null
# Check in the global class cache whether the GdUnitTestSuite class has been extended.
if _included_resources.has(resource_path):
- return _parse_test_suite(ResourceLoader.load(resource_path))
+ return _parse_test_suite(GdUnitTestSuiteScanner.load_with_disabled_warnings(resource_path))
# Otherwise we need to scan manual, we need to exclude classes where direct extends form Godot classes
# the resource loader can fail to load e.g. plugin classes with do preload other scripts
@@ -100,10 +104,25 @@ func _parse_is_test_suite(resource_path :String) -> Node:
if extends_from.is_empty() or ClassDB.class_exists(extends_from):
return null
# Finally, we need to load the class to determine it is a test suite
- var script := ResourceLoader.load(resource_path)
+ var script := GdUnitTestSuiteScanner.load_with_disabled_warnings(resource_path)
if not GdObjects.is_test_suite(script):
return null
- return _parse_test_suite(ResourceLoader.load(resource_path))
+ return _parse_test_suite(script)
+
+
+# We load the test suites with disabled unsafe_method_access to avoid spamming loading errors
+# `unsafe_method_access` will happen when using `assert_that`
+static func load_with_disabled_warnings(resource_path: String) -> GDScript:
+ # grap current level
+ var unsafe_method_access: Variant = ProjectSettings.get_setting("debug/gdscript/warnings/unsafe_method_access")
+
+ # disable and load the script
+ ProjectSettings.set_setting("debug/gdscript/warnings/unsafe_method_access", 0)
+ var script: GDScript = ResourceLoader.load(resource_path)
+
+ # restore
+ ProjectSettings.set_setting("debug/gdscript/warnings/unsafe_method_access", unsafe_method_access)
+ return script
static func _is_script_format_supported(resource_path :String) -> bool:
@@ -113,7 +132,7 @@ static func _is_script_format_supported(resource_path :String) -> bool:
return GdUnit4CSharpApiLoader.is_csharp_file(resource_path)
-func _parse_test_suite(script :Script) -> GdUnitTestSuite:
+func _parse_test_suite(script: Script) -> GdUnitTestSuite:
if not GdObjects.is_test_suite(script):
return null
@@ -122,61 +141,49 @@ func _parse_test_suite(script :Script) -> GdUnitTestSuite:
return GdUnit4CSharpApiLoader.parse_test_suite(script.resource_path)
# Do pares as GDScript
- var test_suite :GdUnitTestSuite = script.new()
+ var test_suite: GdUnitTestSuite = (script as GDScript).new()
test_suite.set_name(GdUnitTestSuiteScanner.parse_test_suite_name(script))
# add test cases to test suite and parse test case line nummber
- var test_case_names := _extract_test_case_names(script)
- _parse_and_add_test_cases(test_suite, script, test_case_names)
- # not all test case parsed?
- # we have to scan the base class to
- if not test_case_names.is_empty():
- var base_script :GDScript = test_suite.get_script().get_base_script()
- while base_script is GDScript:
- # do not parse testsuite itself
- if base_script.resource_path.find("GdUnitTestSuite") == -1:
- _parse_and_add_test_cases(test_suite, base_script, test_case_names)
- base_script = base_script.get_base_script()
+ var test_case_names := _extract_test_case_names(script as GDScript)
+ _parse_and_add_test_cases(test_suite, script as GDScript, test_case_names)
return test_suite
func _extract_test_case_names(script :GDScript) -> PackedStringArray:
- var names := PackedStringArray()
- for method in script.get_script_method_list():
- var funcName :String = method["name"]
- if funcName.begins_with("test"):
- names.append(funcName)
- return names
+ return script.get_script_method_list()\
+ .map(func(descriptor: Dictionary) -> String: return descriptor["name"])\
+ .filter(func(func_name: String) -> bool: return func_name.begins_with("test"))
static func parse_test_suite_name(script :Script) -> String:
return script.resource_path.get_file().replace(".gd", "")
-func _handle_test_suite_arguments(test_suite :Node, script :GDScript, fd :GdFunctionDescriptor) -> void:
+func _handle_test_suite_arguments(test_suite: GdUnitTestSuite, script: GDScript, fd: GdFunctionDescriptor) -> void:
for arg in fd.args():
match arg.name():
_TestCase.ARGUMENT_SKIP:
- var result :Variant = _expression_runner.execute(script, arg.value_as_string())
+ var result: Variant = _expression_runner.execute(script, arg.plain_value())
if result is bool:
test_suite.__is_skipped = result
else:
- push_error("Test expression '%s' cannot be evaluated because it is not of type bool!" % arg.value_as_string())
+ push_error("Test expression '%s' cannot be evaluated because it is not of type bool!" % arg.plain_value())
_TestCase.ARGUMENT_SKIP_REASON:
- test_suite.__skip_reason = arg.value_as_string()
+ test_suite.__skip_reason = arg.plain_value()
_:
push_error("Unsuported argument `%s` found on before() at '%s'!" % [arg.name(), script.resource_path])
-func _handle_test_case_arguments(test_suite :Node, script :GDScript, fd :GdFunctionDescriptor) -> void:
+func _handle_test_case_arguments(test_suite: GdUnitTestSuite, script: GDScript, fd: GdFunctionDescriptor) -> void:
var timeout := _TestCase.DEFAULT_TIMEOUT
var iterations := Fuzzer.ITERATION_DEFAULT_COUNT
var seed_value := -1
var is_skipped := false
var skip_reason := "Unknown."
- var fuzzers :Array[GdFunctionArgument] = []
+ var fuzzers: Array[GdFunctionArgument] = []
var test := _TestCase.new()
- for arg in fd.args():
+ for arg: GdFunctionArgument in fd.args():
# verify argument is allowed
# is test using fuzzers?
if arg.type() == GdObjects.TYPE_FUZZER:
@@ -186,31 +193,33 @@ func _handle_test_case_arguments(test_suite :Node, script :GDScript, fd :GdFunct
_TestCase.ARGUMENT_TIMEOUT:
timeout = arg.default()
_TestCase.ARGUMENT_SKIP:
- var result :Variant = _expression_runner.execute(script, arg.value_as_string())
+ var result :Variant = _expression_runner.execute(script, arg.plain_value())
if result is bool:
is_skipped = result
else:
- push_error("Test expression '%s' cannot be evaluated because it is not of type bool!" % arg.value_as_string())
+ push_error("Test expression '%s' cannot be evaluated because it is not of type bool!" % arg.plain_value())
_TestCase.ARGUMENT_SKIP_REASON:
- skip_reason = arg.value_as_string()
+ skip_reason = arg.plain_value()
Fuzzer.ARGUMENT_ITERATIONS:
iterations = arg.default()
Fuzzer.ARGUMENT_SEED:
seed_value = arg.default()
# create new test
- test.configure(fd.name(), fd.line_number(), script.resource_path, timeout, fuzzers, iterations, seed_value)
+ @warning_ignore("return_value_discarded")
+ test.configure(fd.name(), fd.line_number(), fd.source_path(), timeout, fuzzers, iterations, seed_value)
test.set_function_descriptor(fd)
test.skip(is_skipped, skip_reason)
_validate_argument(fd, test)
test_suite.add_child(test)
-func _parse_and_add_test_cases(test_suite :Node, script :GDScript, test_case_names :PackedStringArray) -> void:
+func _parse_and_add_test_cases(test_suite: GdUnitTestSuite, script: GDScript, test_case_names: PackedStringArray) -> void:
var test_cases_to_find := Array(test_case_names)
var functions_to_scan := test_case_names.duplicate()
+ @warning_ignore("return_value_discarded")
functions_to_scan.append("before")
- var source := _script_parser.load_source_code(script, [script.resource_path])
- var function_descriptors := _script_parser.parse_functions(source, "", [script.resource_path], functions_to_scan)
+
+ var function_descriptors := _script_parser.get_function_descriptors(script, functions_to_scan)
for fd in function_descriptors:
if fd.name() == "before":
_handle_test_suite_arguments(test_suite, script, fd)
@@ -231,7 +240,7 @@ func _validate_argument(fd :GdFunctionDescriptor, test_case :_TestCase) -> void:
# converts given file name by configured naming convention
static func _to_naming_convention(file_name :String) -> String:
- var nc :int = GdUnitSettings.get_setting(GdUnitSettings.TEST_SITE_NAMING_CONVENTION, 0)
+ var nc :int = GdUnitSettings.get_setting(GdUnitSettings.TEST_SUITE_NAMING_CONVENTION, 0)
match nc:
GdUnitSettings.NAMING_CONVENTIONS.AUTO_DETECT:
if GdObjects.is_snake_case(file_name):
@@ -296,16 +305,15 @@ static func create_test_suite(test_suite_path :String, source_path :String) -> G
static func get_test_case_line_number(resource_path :String, func_name :String) -> int:
var file := FileAccess.open(resource_path, FileAccess.READ)
if file != null:
- var script_parser := GdScriptParser.new()
var line_number := 0
while not file.eof_reached():
- var row := GdScriptParser.clean_up_row(file.get_line())
+ var row := file.get_line()
line_number += 1
# ignore comments and empty lines and not test functions
- if row.begins_with("#") || row.length() == 0 || row.find("functest") == -1:
+ if row.begins_with("#") || row.length() == 0 || row.find("func test_") == -1:
continue
# abort if test case name found
- if script_parser.parse_func_name(row) == "test_" + func_name:
+ if row.find("func") != -1 and row.find("test_" + func_name) != -1:
return line_number
return -1
@@ -328,7 +336,7 @@ func get_extends_classname(resource_path :String) -> String:
static func add_test_case(resource_path :String, func_name :String) -> GdUnitResult:
- var script := load(resource_path) as GDScript
+ var script := load_with_disabled_warnings(resource_path)
# count all exiting lines and add two as space to add new test case
var line_number := count_lines(script) + 2
var func_body := TEST_FUNC_TEMPLATE.replace("${func_name}", func_name)
@@ -355,7 +363,7 @@ static func test_suite_exists(test_suite_path :String) -> bool:
static func test_case_exists(test_suite_path :String, func_name :String) -> bool:
if not test_suite_exists(test_suite_path):
return false
- var script := ResourceLoader.load(test_suite_path) as GDScript
+ var script := load_with_disabled_warnings(test_suite_path)
for f in script.get_script_method_list():
if f["name"] == "test_" + func_name:
return true
diff --git a/addons/gdUnit4/src/core/GdUnitTools.gd b/addons/gdUnit4/src/core/GdUnitTools.gd
index b3782716..2015fb8c 100644
--- a/addons/gdUnit4/src/core/GdUnitTools.gd
+++ b/addons/gdUnit4/src/core/GdUnitTools.gd
@@ -27,11 +27,13 @@ static func prints_verbose(message :String) -> void:
prints(message)
+@warning_ignore("unsafe_cast")
static func free_instance(instance :Variant, use_call_deferred :bool = false, is_stdout_verbose := false) -> bool:
if instance is Array:
for element :Variant in instance:
+ @warning_ignore("return_value_discarded")
free_instance(element)
- instance.clear()
+ (instance as Array).clear()
return true
# do not free an already freed instance
if not is_instance_valid(instance):
@@ -41,37 +43,39 @@ static func free_instance(instance :Variant, use_call_deferred :bool = false, is
return false
if is_stdout_verbose:
print_verbose("GdUnit4:gc():free instance ", instance)
- release_double(instance)
+ release_double(instance as Object)
if instance is RefCounted:
(instance as RefCounted).notification(Object.NOTIFICATION_PREDELETE)
+ # If scene runner freed we explicit await all inputs are processed
+ if instance is GdUnitSceneRunnerImpl:
+ await (instance as GdUnitSceneRunnerImpl).await_input_processed()
return true
else:
- # is instance already freed?
- #if not is_instance_valid(instance) or ClassDB.class_get_property(instance, "new"):
- # return false
- #release_connections(instance)
if instance is Timer:
- instance.stop()
+ var timer := instance as Timer
+ timer.stop()
if use_call_deferred:
- instance.call_deferred("free")
+ timer.call_deferred("free")
else:
- instance.free()
- await Engine.get_main_loop().process_frame
+ timer.free()
+ await (Engine.get_main_loop() as SceneTree).process_frame
return true
- if instance is Node and instance.get_parent() != null:
+
+ if instance is Node and (instance as Node).get_parent() != null:
+ var node := instance as Node
if is_stdout_verbose:
- print_verbose("GdUnit4:gc():remove node from parent ", instance.get_parent(), instance)
+ print_verbose("GdUnit4:gc():remove node from parent ", node.get_parent(), node)
if use_call_deferred:
- instance.get_parent().remove_child.call_deferred(instance)
+ node.get_parent().remove_child.call_deferred(node)
#instance.call_deferred("set_owner", null)
else:
- instance.get_parent().remove_child(instance)
+ node.get_parent().remove_child(node)
if is_stdout_verbose:
print_verbose("GdUnit4:gc():freeing `free()` the instance ", instance)
if use_call_deferred:
- instance.call_deferred("free")
+ (instance as Object).call_deferred("free")
else:
- instance.free()
+ (instance as Object).free()
return !is_instance_valid(instance)
@@ -92,13 +96,14 @@ static func _release_connections(instance :Object) -> void:
static func release_timers() -> void:
# we go the new way to hold all gdunit timers in group 'GdUnitTimers'
- if Engine.get_main_loop().root == null:
+ var scene_tree := Engine.get_main_loop() as SceneTree
+ if scene_tree.root == null:
return
- for node :Node in Engine.get_main_loop().root.get_children():
+ for node :Node in scene_tree.root.get_children():
if is_instance_valid(node) and node.is_in_group("GdUnitTimers"):
if is_instance_valid(node):
- Engine.get_main_loop().root.remove_child.call_deferred(node)
- node.stop()
+ scene_tree.root.remove_child.call_deferred(node)
+ (node as Timer).stop()
node.queue_free()
@@ -115,12 +120,6 @@ static func release_double(instance :Object) -> void:
instance.call("__release_double")
-static func clear_push_errors() -> void:
- 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 :Node = test_suite.find_child(test_case_name, false, false)
+ var test_case: _TestCase = test_suite.find_child(test_case_name, false, false)
test_case.expect_to_interupt()
diff --git a/addons/gdUnit4/src/core/LocalTime.gd b/addons/gdUnit4/src/core/LocalTime.gd
index 64b9ba00..cf6c22c8 100644
--- a/addons/gdUnit4/src/core/LocalTime.gd
+++ b/addons/gdUnit4/src/core/LocalTime.gd
@@ -61,6 +61,7 @@ func plus(time_unit :TimeUnit, value :int) -> LocalTime:
addValue = value * MILLIS_PER_MINUTE
TimeUnit.HOUR:
addValue = value * MILLIS_PER_HOUR
+ @warning_ignore("return_value_discarded")
_init(_time + addValue)
return self
diff --git a/addons/gdUnit4/src/core/_TestCase.gd b/addons/gdUnit4/src/core/_TestCase.gd
index 31be7810..b8d9273d 100644
--- a/addons/gdUnit4/src/core/_TestCase.gd
+++ b/addons/gdUnit4/src/core/_TestCase.gd
@@ -22,7 +22,6 @@ var _expect_to_interupt := false
var _timer: Timer
var _interupted: bool = false
var _failed := false
-var _report: GdUnitReport = null
var _parameter_set_resolver: GdUnitTestParameterSetResolver
var _is_disposed := false
@@ -80,14 +79,13 @@ func dispose() -> void:
stop_timer()
_remove_failure_handler()
_fuzzers.clear()
- _report = null
@warning_ignore("shadowed_variable_base_class", "redundant_await")
func _execute_test_case(name: String, test_parameter: Array) -> void:
# needs at least on await otherwise it breaks the awaiting chain
await get_parent().callv(name, test_parameter)
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
completed.emit()
@@ -104,22 +102,30 @@ func set_timeout() -> void:
_timer = Timer.new()
add_child(_timer)
_timer.set_name("gdunit_test_case_timer_%d" % _timer.get_instance_id())
- _timer.timeout.connect(func do_interrupt() -> void:
- if is_fuzzed():
- _report = GdUnitReport.new().create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.fuzzer_interuped(_current_iteration, "timedout"))
- else:
- _report = GdUnitReport.new().create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.test_timeout(timeout))
- _interupted = true
- completed.emit()
- , CONNECT_DEFERRED)
+ @warning_ignore("return_value_discarded")
+ _timer.timeout.connect(do_interrupt, CONNECT_DEFERRED)
_timer.set_one_shot(true)
_timer.set_wait_time(time)
_timer.set_autostart(false)
_timer.start()
+func do_interrupt() -> void:
+ _interupted = true
+ if not is_expect_interupted():
+ var execution_context:= GdUnitThreadManager.get_current_context().get_execution_context()
+ if is_fuzzed():
+ execution_context.add_report(GdUnitReport.new()\
+ .create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.fuzzer_interuped(_current_iteration, "timedout")))
+ else:
+ execution_context.add_report(GdUnitReport.new()\
+ .create(GdUnitReport.INTERUPTED, line_number(), GdAssertMessages.test_timeout(timeout)))
+ completed.emit()
+
+
func _set_failure_handler() -> void:
if not GdUnitSignals.instance().gdunit_set_test_failed.is_connected(_failure_received):
+ @warning_ignore("return_value_discarded")
GdUnitSignals.instance().gdunit_set_test_failed.connect(_failure_received)
@@ -164,10 +170,6 @@ func is_skipped() -> bool:
return _skipped
-func report() -> GdUnitReport:
- return _report
-
-
func skip_info() -> String:
return _skip_reason
diff --git a/addons/gdUnit4/src/core/assets/touch-button.png b/addons/gdUnit4/src/core/assets/touch-button.png
new file mode 100644
index 00000000..23f46eff
Binary files /dev/null and b/addons/gdUnit4/src/core/assets/touch-button.png differ
diff --git a/addons/gdUnit4/src/core/assets/touch-button.png.import b/addons/gdUnit4/src/core/assets/touch-button.png.import
new file mode 100644
index 00000000..a84ce13b
--- /dev/null
+++ b/addons/gdUnit4/src/core/assets/touch-button.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://btx5kcrsngasl"
+path="res://.godot/imported/touch-button.png-2fff40c8520d8e97a57db1b2b043f641.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/gdUnit4/src/core/assets/touch-button.png"
+dest_files=["res://.godot/imported/touch-button.png-2fff40c8520d8e97a57db1b2b043f641.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd b/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd
index 339fb8cd..6f4c1251 100644
--- a/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd
+++ b/addons/gdUnit4/src/core/command/GdUnitCommandHandler.gd
@@ -50,6 +50,7 @@ static func instance() -> GdUnitCommandHandler:
return GdUnitSingleton.instance("GdUnitCommandHandler", func() -> GdUnitCommandHandler: return GdUnitCommandHandler.new())
+@warning_ignore("return_value_discarded")
func _init() -> void:
assert_shortcut_mappings(SETTINGS_SHORTCUT_MAPPING)
@@ -58,6 +59,7 @@ func _init() -> void:
GdUnitSignals.instance().gdunit_client_disconnected.connect(_on_client_disconnected)
GdUnitSignals.instance().gdunit_settings_changed.connect(_on_settings_changed)
# preload previous test execution
+ @warning_ignore("return_value_discarded")
_runner_config.load_config()
init_shortcuts()
@@ -75,7 +77,8 @@ func _init() -> void:
# schedule discover tests if enabled and running inside the editor
if Engine.is_editor_hint() and GdUnitSettings.is_test_discover_enabled():
- var timer :SceneTreeTimer = Engine.get_main_loop().create_timer(5)
+ var timer :SceneTreeTimer = (Engine.get_main_loop() as SceneTree).create_timer(5)
+ @warning_ignore("return_value_discarded")
timer.timeout.connect(cmd_discover_tests)
@@ -119,7 +122,7 @@ func init_shortcuts() -> void:
register_shortcut(shortcut, inputEvent)
-func create_shortcut_input_even(key_codes : PackedInt32Array) -> InputEventKey:
+func create_shortcut_input_even(key_codes: PackedInt32Array) -> InputEventKey:
var inputEvent :InputEventKey = InputEventKey.new()
inputEvent.pressed = true
for key_code in key_codes:
@@ -204,7 +207,8 @@ func cmd_run(debug :bool) -> void:
if _is_running:
return
# save current selected excution config
- var result := _runner_config.set_server_port(Engine.get_meta("gdunit_server_port")).save_config()
+ var server_port: int = Engine.get_meta("gdunit_server_port")
+ var result := _runner_config.set_server_port(server_port).save_config()
if result.is_error():
push_error(result.error_message())
return
@@ -239,6 +243,7 @@ func cmd_editor_run_test(debug :bool) -> void:
var cursor_line := active_base_editor().get_caret_line()
#run test case?
var regex := RegEx.new()
+ @warning_ignore("return_value_discarded")
regex.compile("(^func[ ,\t])(test_[a-zA-Z0-9_]*)")
var result := regex.search(active_base_editor().get_line(cursor_line))
if result:
@@ -259,8 +264,10 @@ func cmd_create_test() -> void:
# show error dialog
push_error("Failed to create test case: %s" % result.error_message())
return
- var info := result.value() as Dictionary
- ScriptEditorControls.edit_script(info.get("path"), info.get("line"))
+ var info: Dictionary = result.value()
+ var script_path: String = info.get("path")
+ var script_line: int = info.get("line")
+ ScriptEditorControls.edit_script(script_path, script_line)
func cmd_discover_tests() -> void:
@@ -276,8 +283,10 @@ static func scan_test_directorys(base_directory :String, test_directory: String,
if GdUnitTestSuiteScanner.exclude_scan_directories.has(current_directory):
continue
if match_test_directory(directory, test_directory):
+ @warning_ignore("return_value_discarded")
test_suite_paths.append(current_directory)
else:
+ @warning_ignore("return_value_discarded")
scan_test_directorys(current_directory, test_directory, test_suite_paths)
return test_suite_paths
@@ -339,11 +348,13 @@ func _on_run_overall_pressed(_debug := false) -> void:
func _on_settings_changed(property :GdUnitProperty) -> void:
if SETTINGS_SHORTCUT_MAPPING.has(property.name()):
var shortcut :GdUnitShortcut.ShortCut = SETTINGS_SHORTCUT_MAPPING.get(property.name())
- var input_event := create_shortcut_input_even(property.value())
+ var value: PackedInt32Array = property.value()
+ var input_event := create_shortcut_input_even(value)
prints("Shortcut changed: '%s' to '%s'" % [GdUnitShortcut.ShortCut.keys()[shortcut], input_event.as_text()])
register_shortcut(shortcut, input_event)
if property.name() == GdUnitSettings.TEST_DISCOVER_ENABLED:
- var timer :SceneTreeTimer = Engine.get_main_loop().create_timer(3)
+ var timer :SceneTreeTimer = (Engine.get_main_loop() as SceneTree).create_timer(3)
+ @warning_ignore("return_value_discarded")
timer.timeout.connect(cmd_discover_tests)
diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd
index 793d5144..438ddedf 100644
--- a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd
+++ b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd
@@ -8,6 +8,7 @@ var _discover_cache := {}
func _init() -> void:
# Register for discovery events to sync the cache
+ @warning_ignore("return_value_discarded")
GdUnitSignals.instance().gdunit_add_test_suite.connect(sync_cache)
@@ -45,6 +46,7 @@ func discover(script: Script) -> void:
var tests_removed := PackedStringArray()
for test_case in discovered_test_cases:
if not script_test_cases.has(test_case):
+ @warning_ignore("return_value_discarded")
tests_removed.append(test_case)
# second detect new added tests
var tests_added :Array[String] = []
@@ -80,7 +82,8 @@ func extract_test_functions(test_suite :Node) -> PackedStringArray:
func rebuild_project(script: Script) -> void:
var class_path := ProjectSettings.globalize_path(script.resource_path)
print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard: CSharpScript change detected on: '%s' [/color]" % class_path)
- await Engine.get_main_loop().process_frame
+ var scene_tree := Engine.get_main_loop() as SceneTree
+ await scene_tree.process_frame
var output := []
var exit_code := OS.execute("dotnet", ["--version"], output)
@@ -95,4 +98,4 @@ func rebuild_project(script: Script) -> void:
print_rich("[color=CORNFLOWER_BLUE]GdUnitTestDiscoverGuard:[/color] [color=DEEP_SKY_BLUE]Rebuild the project ... [/color]")
for out:Variant in output:
print_rich("[color=DEEP_SKY_BLUE] %s" % out.strip_edges())
- await Engine.get_main_loop().process_frame
+ await scene_tree.process_frame
diff --git a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd
index 7e8dc81e..22b36d15 100644
--- a/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd
+++ b/addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverer.gd
@@ -5,10 +5,11 @@ extends RefCounted
static func run() -> void:
prints("Running test discovery ..")
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverStart.new())
- await Engine.get_main_loop().create_timer(.5).timeout
+ await (Engine.get_main_loop() as SceneTree).create_timer(.5).timeout
# We run the test discovery in an extra thread so that the main thread is not blocked
var t:= Thread.new()
+ @warning_ignore("return_value_discarded")
t.start(func () -> void:
var test_suite_directories :PackedStringArray = GdUnitCommandHandler.scan_test_directorys("res://" , GdUnitSettings.test_root_folder(), [])
var scanner := GdUnitTestSuiteScanner.new()
@@ -18,7 +19,7 @@ static func run() -> void:
_test_suites_to_process.append_array(scanner.scan(test_suite_dir))
# Do sync the main thread before emit the discovered test suites to the inspector
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
var test_case_count :int = 0
for test_suite in _test_suites_to_process:
test_case_count += test_suite.get_child_count()
@@ -32,6 +33,6 @@ static func run() -> void:
)
# wait unblocked to the tread is finished
while t.is_alive():
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
# needs finally to wait for finish
await t.wait_to_finish()
diff --git a/addons/gdUnit4/src/core/event/GdUnitEvent.gd b/addons/gdUnit4/src/core/event/GdUnitEvent.gd
index 0f8ad7d1..0fd2af2f 100644
--- a/addons/gdUnit4/src/core/event/GdUnitEvent.gd
+++ b/addons/gdUnit4/src/core/event/GdUnitEvent.gd
@@ -3,6 +3,7 @@ extends Resource
const WARNINGS = "warnings"
const FAILED = "failed"
+const FLAKY = "flaky"
const ERRORS = "errors"
const SKIPPED = "skipped"
const ELAPSED_TIME = "elapsed_time"
@@ -10,6 +11,7 @@ const ORPHAN_NODES = "orphan_nodes"
const ERROR_COUNT = "error_count"
const FAILED_COUNT = "failed_count"
const SKIPPED_COUNT = "skipped_count"
+const RETRY_COUNT = "retry_count"
enum {
INIT,
@@ -18,6 +20,7 @@ enum {
TESTSUITE_AFTER,
TESTCASE_BEFORE,
TESTCASE_AFTER,
+ TESTCASE_STATISTICS,
DISCOVER_START,
DISCOVER_END,
DISCOVER_SUITE_ADDED,
@@ -71,6 +74,15 @@ func test_after(p_resource_path :String, p_suite_name :String, p_test_name :Stri
return self
+func test_statistics(p_resource_path :String, p_suite_name :String, p_test_name :String, p_statistics :Dictionary = {}) -> GdUnitEvent:
+ _event_type = TESTCASE_STATISTICS
+ _resource_path = p_resource_path
+ _suite_name = p_suite_name
+ _test_name = p_test_name
+ _statistics = p_statistics
+ return self
+
+
func type() -> int:
return _event_type
@@ -135,6 +147,10 @@ func is_error() -> bool:
return _statistics.get(ERRORS, false)
+func is_flaky() -> bool:
+ return _statistics.get(FLAKY, false)
+
+
func is_skipped() -> bool:
return _statistics.get(SKIPPED, false)
@@ -170,7 +186,8 @@ func deserialize(serialized :Dictionary) -> GdUnitEvent:
if serialized.has("reports"):
# needs this workaround to copy typed values in the array
var reports_to_deserializ :Array[Dictionary] = []
- reports_to_deserializ.append_array(serialized.get("reports"))
+ @warning_ignore("unsafe_cast")
+ reports_to_deserializ.append_array(serialized.get("reports") as Array)
_reports = _deserialize_reports(reports_to_deserializ)
return self
diff --git a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd b/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd
index 25d69b09..904da013 100644
--- a/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd
+++ b/addons/gdUnit4/src/core/execution/GdUnitExecutionContext.gd
@@ -2,50 +2,65 @@
## It contains all the necessary information about the executed stage, such as memory observers, reports, orphan monitor
class_name GdUnitExecutionContext
-var _parent_context :GdUnitExecutionContext
-var _sub_context :Array[GdUnitExecutionContext] = []
-var _orphan_monitor :GdUnitOrphanNodesMonitor
-var _memory_observer :GdUnitMemoryObserver
-var _report_collector :GdUnitTestReportCollector
-var _timer :LocalTime
+var _parent_context: GdUnitExecutionContext
+var _sub_context: Array[GdUnitExecutionContext] = []
+var _orphan_monitor: GdUnitOrphanNodesMonitor
+var _memory_observer: GdUnitMemoryObserver
+var _report_collector: GdUnitTestReportCollector
+var _timer: LocalTime
var _test_case_name: StringName
-var _name :String
-
-
-var error_monitor : GodotGdErrorMonitor = null:
- set (value):
- error_monitor = value
+var _test_case_parameter_set: Array
+var _name: String
+var _test_execution_iteration: int = 0
+var _flaky_test_check := GdUnitSettings.is_test_flaky_check_enabled()
+var _flaky_test_retries := GdUnitSettings.get_flaky_max_retries()
+
+
+# execution states
+var _is_calculated := false
+var _is_success: bool
+var _is_flaky: bool
+var _is_skipped: bool
+var _has_warnings: bool
+var _has_failures: bool
+var _has_errors: bool
+var _failure_count := 0
+var _orphan_count := 0
+var _error_count := 0
+var _skipped_count := 0
+
+
+var error_monitor: GodotGdErrorMonitor = null:
get:
if _parent_context != null:
return _parent_context.error_monitor
+ if error_monitor == null:
+ error_monitor = GodotGdErrorMonitor.new()
return error_monitor
-var test_suite : GdUnitTestSuite = null:
- set (value):
- test_suite = value
+var test_suite: GdUnitTestSuite = null:
get:
if _parent_context != null:
return _parent_context.test_suite
return test_suite
-var test_case : _TestCase = null:
+var test_case: _TestCase = null:
get:
- if _test_case_name.is_empty():
- return null
- return test_suite.find_child(_test_case_name, false, false)
+ if test_case == null and _parent_context != null:
+ return _parent_context.test_case
+ return test_case
-func _init(name :String, parent_context :GdUnitExecutionContext = null) -> void:
+func _init(name: StringName, parent_context: GdUnitExecutionContext = null) -> void:
_name = name
_parent_context = parent_context
_timer = LocalTime.now()
_orphan_monitor = GdUnitOrphanNodesMonitor.new(name)
_orphan_monitor.start()
_memory_observer = GdUnitMemoryObserver.new()
- error_monitor = GodotGdErrorMonitor.new()
- _report_collector = GdUnitTestReportCollector.new(get_instance_id())
+ _report_collector = GdUnitTestReportCollector.new()
if parent_context != null:
parent_context._sub_context.append(self)
@@ -58,40 +73,55 @@ func dispose() -> void:
_parent_context = null
test_suite = null
test_case = null
+ dispose_sub_contexts()
+
+
+func dispose_sub_contexts() -> void:
for context in _sub_context:
context.dispose()
_sub_context.clear()
-func set_active() -> void:
- test_suite.__execution_context = self
- GdUnitThreadManager.get_current_context().set_execution_context(self)
+static func of(pe: GdUnitExecutionContext) -> GdUnitExecutionContext:
+ var context := GdUnitExecutionContext.new(pe._test_case_name, pe)
+ context._test_case_name = pe._test_case_name
+ context._test_execution_iteration = pe._test_execution_iteration
+ return context
-static func of_test_suite(test_suite_ :GdUnitTestSuite) -> GdUnitExecutionContext:
- assert(test_suite_, "test_suite is null")
- var context := GdUnitExecutionContext.new(test_suite_.get_name())
- context.test_suite = test_suite_
- context.set_active()
+static func of_test_suite(p_test_suite: GdUnitTestSuite) -> GdUnitExecutionContext:
+ assert(p_test_suite, "test_suite is null")
+ var context := GdUnitExecutionContext.new(p_test_suite.get_name())
+ context.test_suite = p_test_suite
return context
-static func of_test_case(pe :GdUnitExecutionContext, test_case_name :StringName) -> GdUnitExecutionContext:
+static func of_test_case(pe: GdUnitExecutionContext, p_test_case: _TestCase) -> GdUnitExecutionContext:
+ assert(p_test_case, "test_case is null")
+ var context := GdUnitExecutionContext.new(p_test_case.get_name(), pe)
+ context.test_case = p_test_case
+ return context
+
+
+static func of_parameterized_test(pe: GdUnitExecutionContext, test_case_name: String, test_case_parameter_set: Array) -> GdUnitExecutionContext:
var context := GdUnitExecutionContext.new(test_case_name, pe)
context._test_case_name = test_case_name
- context.set_active()
+ context._test_case_parameter_set = test_case_parameter_set
return context
-static func of(pe :GdUnitExecutionContext) -> GdUnitExecutionContext:
- var context := GdUnitExecutionContext.new(pe._test_case_name, pe)
- context._test_case_name = pe._test_case_name
- context.set_active()
- return context
+func get_test_suite_path() -> String:
+ return test_suite.get_script().resource_path
+
+
+func get_test_suite_name() -> StringName:
+ return test_suite.get_name()
-func test_failed() -> bool:
- return has_failures() or has_errors()
+func get_test_case_name() -> StringName:
+ if _test_case_name.is_empty():
+ return test_case.get_name()
+ return _test_case_name
func error_monitor_start() -> void:
@@ -102,7 +132,7 @@ 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)
+ _report_collector.push_back(error_report)
func orphan_monitor_start() -> void:
@@ -113,45 +143,164 @@ func orphan_monitor_stop() -> void:
_orphan_monitor.stop()
+func add_report(report: GdUnitReport) -> void:
+ _report_collector.push_back(report)
+
+
func reports() -> Array[GdUnitReport]:
return _report_collector.reports()
-func build_report_statistics(orphans :int, recursive := true) -> Dictionary:
+func collect_reports(recursive: bool) -> Array[GdUnitReport]:
+ if not recursive:
+ return reports()
+ var current_reports := reports()
+ # we combine the reports of test_before(), test_after() and test() to be reported by `fire_test_ended`
+ for sub_context in _sub_context:
+ current_reports.append_array(sub_context.reports())
+ # needs finally to clean the test reports to avoid counting twice
+ sub_context.reports().clear()
+ return current_reports
+
+
+func collect_orphans(p_reports: Array[GdUnitReport]) -> int:
+ var orphans := 0
+ if not _sub_context.is_empty():
+ orphans += collect_testcase_orphan_reports(_sub_context[0], p_reports)
+ orphans += collect_teststage_orphan_reports(p_reports)
+ return orphans
+
+
+func collect_testcase_orphan_reports(context: GdUnitExecutionContext, p_reports: Array[GdUnitReport]) -> int:
+ var orphans := context.count_orphans()
+ if orphans > 0:
+ p_reports.push_front(GdUnitReport.new()\
+ .create(GdUnitReport.WARN, context.test_case.line_number(), GdAssertMessages.orphan_detected_on_test(orphans)))
+ return orphans
+
+
+func collect_teststage_orphan_reports(p_reports: Array[GdUnitReport]) -> int:
+ var orphans := count_orphans()
+ if orphans > 0:
+ p_reports.push_front(GdUnitReport.new()\
+ .create(GdUnitReport.WARN, test_case.line_number(), GdAssertMessages.orphan_detected_on_test_setup(orphans)))
+ return orphans
+
+
+func build_reports(recursive:= true) -> Array[GdUnitReport]:
+ var collected_reports: Array[GdUnitReport] = collect_reports(recursive)
+ if recursive:
+ _orphan_count = collect_orphans(collected_reports)
+ else:
+ _orphan_count = count_orphans()
+ if _orphan_count > 0:
+ collected_reports.push_front(GdUnitReport.new() \
+ .create(GdUnitReport.WARN, 1, GdAssertMessages.orphan_detected_on_suite_setup(_orphan_count)))
+ _is_skipped = is_skipped()
+ _skipped_count = count_skipped(recursive)
+ _is_success = is_success()
+ _is_flaky = is_flaky()
+ _has_warnings = has_warnings()
+ _has_errors = has_errors()
+ _error_count = count_errors(recursive)
+ if !_is_success:
+ _has_failures = has_failures()
+ _failure_count = count_failures(recursive)
+ _is_calculated = true
+ return collected_reports
+
+
+# Evaluates the actual test case status by validate latest execution state (cold be more based on flaky max retry count)
+func evaluate_test_retry_status() -> bool:
+ # get latest test execution status
+ var last_test_status :GdUnitExecutionContext = _sub_context.back()
+ _is_skipped = last_test_status.is_skipped()
+ _skipped_count = last_test_status.count_skipped(false)
+ _is_success = last_test_status.is_success()
+ # if success but it have more than one sub contexts the test was rerurn becouse of failures and will be marked as flaky
+ _is_flaky = _is_success and _sub_context.size() > 1
+ _has_warnings = last_test_status.has_warnings()
+ _has_errors = last_test_status.has_errors()
+ _error_count = last_test_status.count_errors(false)
+ _has_failures = last_test_status.has_failures()
+ _failure_count = last_test_status.count_failures(false)
+ _orphan_count = last_test_status.collect_orphans(collect_reports(false))
+ _is_calculated = true
+ # finally cleanup the retry execution contexts
+ dispose_sub_contexts()
+ return _is_success
+
+
+func get_execution_statistics() -> Dictionary:
return {
- GdUnitEvent.ORPHAN_NODES: orphans,
+ GdUnitEvent.RETRY_COUNT: _test_execution_iteration,
+ GdUnitEvent.ORPHAN_NODES: _orphan_count,
GdUnitEvent.ELAPSED_TIME: _timer.elapsed_since_ms(),
- GdUnitEvent.FAILED: has_failures(),
- GdUnitEvent.ERRORS: has_errors(),
- GdUnitEvent.WARNINGS: has_warnings(),
- GdUnitEvent.SKIPPED: has_skipped(),
- GdUnitEvent.FAILED_COUNT: count_failures(recursive),
- GdUnitEvent.ERROR_COUNT: count_errors(recursive),
- GdUnitEvent.SKIPPED_COUNT: count_skipped(recursive)
+ GdUnitEvent.FAILED: !_is_success,
+ GdUnitEvent.ERRORS: _has_errors,
+ GdUnitEvent.WARNINGS: _has_warnings,
+ GdUnitEvent.FLAKY: _is_flaky,
+ GdUnitEvent.SKIPPED: _is_skipped,
+ GdUnitEvent.FAILED_COUNT: _failure_count,
+ GdUnitEvent.ERROR_COUNT: _error_count,
+ GdUnitEvent.SKIPPED_COUNT: _skipped_count
}
func has_failures() -> bool:
- return _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
- return c.has_failures()) or _report_collector.has_failures()
+ return (
+ _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
+ return c._has_failures if c._is_calculated else c.has_failures())
+ or _report_collector.has_failures()
+ )
func has_errors() -> bool:
- return _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
- return c.has_errors()) or _report_collector.has_errors()
+ return (
+ _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
+ return c._has_errors if c._is_calculated else c.has_errors())
+ or _report_collector.has_errors()
+ )
func has_warnings() -> bool:
- return _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
- return c.has_warnings()) or _report_collector.has_warnings()
+ return (
+ _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
+ return c._has_warnings if c._is_calculated else c.has_warnings())
+ or _report_collector.has_warnings()
+ )
+
+
+func is_flaky() -> bool:
+ return (
+ _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
+ return c._is_flaky if c._is_calculated else c.is_flaky())
+ or _test_execution_iteration > 1
+ )
-func has_skipped() -> bool:
- return _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
- return c.has_skipped()) or _report_collector.has_skipped()
+func is_success() -> bool:
+ if _sub_context.is_empty():
+ return not has_failures()
+ var failed_context := _sub_context.filter(func(c :GdUnitExecutionContext) -> bool:
+ return !(c._is_success if c._is_calculated else c.is_success()))
+ return failed_context.is_empty() and not has_failures()
-func count_failures(recursive :bool) -> int:
+
+func is_skipped() -> bool:
+ return (
+ _sub_context.any(func(c :GdUnitExecutionContext) -> bool:
+ return c._is_skipped if c._is_calculated else c.is_skipped())
+ or test_case.is_skipped() if test_case != null else false
+ )
+
+
+func is_interupted() -> bool:
+ return false if test_case == null else test_case.is_interupted()
+
+
+func count_failures(recursive: bool) -> int:
if not recursive:
return _report_collector.count_failures()
return _sub_context\
@@ -159,7 +308,7 @@ func count_failures(recursive :bool) -> int:
return c.count_failures(recursive)).reduce(sum, _report_collector.count_failures())
-func count_errors(recursive :bool) -> int:
+func count_errors(recursive: bool) -> int:
if not recursive:
return _report_collector.count_errors()
return _sub_context\
@@ -167,7 +316,7 @@ func count_errors(recursive :bool) -> int:
return c.count_errors(recursive)).reduce(sum, _report_collector.count_errors())
-func count_skipped(recursive :bool) -> int:
+func count_skipped(recursive: bool) -> int:
if not recursive:
return _report_collector.count_skipped()
return _sub_context\
@@ -182,11 +331,18 @@ func count_orphans() -> int:
return _orphan_monitor.orphan_nodes() - orphans
-func sum(accum :int, number :int) -> int:
+func sum(accum: int, number: int) -> int:
return accum + number
-func register_auto_free(obj :Variant) -> Variant:
+func retry_execution() -> bool:
+ var retry := _test_execution_iteration < 1 if not _flaky_test_check else _test_execution_iteration < _flaky_test_retries
+ if retry:
+ _test_execution_iteration += 1
+ return retry
+
+
+func register_auto_free(obj: Variant) -> Variant:
return _memory_observer.register_auto_free(obj)
diff --git a/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd b/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd
index 69b15f84..274b66f4 100644
--- a/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd
+++ b/addons/gdUnit4/src/core/execution/GdUnitMemoryObserver.gd
@@ -18,6 +18,7 @@ func register_auto_free(obj :Variant) -> Variant:
if not is_instance_valid(obj):
return obj
# do not register on GDScriptNativeClass
+ @warning_ignore("unsafe_cast")
if typeof(obj) == TYPE_OBJECT and (obj as Object).is_class("GDScriptNativeClass") :
return obj
#if obj is GDScript or obj is ScriptExtension:
@@ -41,6 +42,7 @@ static func _is_instance_guard_enabled() -> bool:
return false
+@warning_ignore("unsafe_method_access")
static func debug_observe(name :String, obj :Object, indent :int = 0) -> void:
if not _show_debug:
return
@@ -54,7 +56,7 @@ static func debug_observe(name :String, obj :Object, indent :int = 0) -> void:
prints(name, obj, obj.get_class(), obj.get_name())
-static func guard_instance(obj :Object) -> Object:
+static func guard_instance(obj :Object) -> void:
if not _is_instance_guard_enabled():
return
var tag := TAG_OBSERVE_INSTANCE + str(abs(obj.get_instance_id()))
@@ -62,7 +64,6 @@ static func guard_instance(obj :Object) -> Object:
return
debug_observe("Gard on instance", obj)
Engine.set_meta(tag, obj)
- return obj
static func unguard_instance(obj :Object, verbose := true) -> void:
@@ -78,7 +79,7 @@ static func unguard_instance(obj :Object, verbose := true) -> void:
static func gc_guarded_instance(name :String, instance :Object) -> void:
if not _is_instance_guard_enabled():
return
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
unguard_instance(instance, false)
if is_instance_valid(instance) and instance is RefCounted:
# finally do this very hacky stuff
@@ -90,8 +91,8 @@ static func gc_guarded_instance(name :String, instance :Object) -> void:
# if base_script:
# base_script.unreference()
debug_observe(name, instance)
- instance.unreference()
- await Engine.get_main_loop().process_frame
+ (instance as RefCounted).unreference()
+ await (Engine.get_main_loop() as SceneTree).process_frame
static func gc_on_guarded_instances() -> void:
@@ -106,7 +107,7 @@ static func gc_on_guarded_instances() -> void:
# store the object into global store aswell to be verified by 'is_marked_auto_free'
func _tag_object(obj :Variant) -> void:
- var tagged_object := Engine.get_meta(TAG_AUTO_FREE, []) as Array
+ var tagged_object: Array = Engine.get_meta(TAG_AUTO_FREE, [])
tagged_object.append(obj)
Engine.set_meta(TAG_AUTO_FREE, tagged_object)
@@ -116,16 +117,18 @@ func gc() -> void:
if _store.is_empty():
return
# give engine time to free objects to process objects marked by queue_free()
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
if _is_stdout_verbose:
print_verbose("GdUnit4:gc():running", " freeing %d objects .." % _store.size())
- var tagged_objects := Engine.get_meta(TAG_AUTO_FREE, []) as Array
+ var tagged_objects: Array = Engine.get_meta(TAG_AUTO_FREE, [])
while not _store.is_empty():
var value :Variant = _store.pop_front()
tagged_objects.erase(value)
await GdUnitTools.free_instance(value, _is_stdout_verbose)
+ assert(_store.is_empty(), "The memory observer has still entries in the store!")
## Checks whether the specified object is registered for automatic release
-static func is_marked_auto_free(obj :Object) -> bool:
- return Engine.get_meta(TAG_AUTO_FREE, []).has(obj)
+static func is_marked_auto_free(obj: Variant) -> bool:
+ var tagged_objects: Array = Engine.get_meta(TAG_AUTO_FREE, [])
+ return tagged_objects.has(obj)
diff --git a/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd b/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd
index 8484f0d1..62c4b02f 100644
--- a/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd
+++ b/addons/gdUnit4/src/core/execution/GdUnitTestReportCollector.gd
@@ -3,7 +3,6 @@ class_name GdUnitTestReportCollector
extends RefCounted
-var _execution_context_id :int
var _reports :Array[GdUnitReport] = []
@@ -23,11 +22,6 @@ static func __filter_is_skipped(report :GdUnitReport) -> bool:
return report.is_skipped()
-func _init(execution_context_id :int) -> void:
- _execution_context_id = execution_context_id
- GdUnitSignals.instance().gdunit_report.connect(on_reports)
-
-
func count_failures() -> int:
return _reports.filter(__filter_is_failure).size()
@@ -64,7 +58,5 @@ func reports() -> Array[GdUnitReport]:
return _reports
-# Consumes reports emitted by tests
-func on_reports(execution_context_id :int, report :GdUnitReport) -> void:
- if execution_context_id == _execution_context_id:
- _reports.append(report)
+func push_back(report :GdUnitReport) -> void:
+ _reports.push_back(report)
diff --git a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd
index c9194694..0dd0ba0f 100644
--- a/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd
+++ b/addons/gdUnit4/src/core/execution/GdUnitTestSuiteExecutor.gd
@@ -5,7 +5,7 @@ class_name GdUnitTestSuiteExecutor
# preload all asserts here
@warning_ignore("unused_private_class_variable")
var _assertions := GdUnitAssertions.new()
-var _executeStage :IGdUnitExecutionStage = GdUnitTestSuiteExecutionStage.new()
+var _executeStage := GdUnitTestSuiteExecutionStage.new()
func _init(debug_mode :bool = false) -> void:
@@ -17,8 +17,8 @@ func execute(test_suite :GdUnitTestSuite) -> void:
if not orphan_detection_enabled:
prints("!!! Reporting orphan nodes is disabled. Please check GdUnit settings.")
- Engine.get_main_loop().root.call_deferred("add_child", test_suite)
- await Engine.get_main_loop().process_frame
+ (Engine.get_main_loop() as SceneTree).root.call_deferred("add_child", test_suite)
+ await (Engine.get_main_loop() as SceneTree).process_frame
await _executeStage.execute(GdUnitExecutionContext.of_test_suite(test_suite))
diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd
index 50699752..9b78f8a6 100644
--- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd
+++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseAfterStage.gd
@@ -4,16 +4,16 @@ class_name GdUnitTestCaseAfterStage
extends IGdUnitExecutionStage
-var _test_name :StringName = ""
-var _call_stage :bool
+var _call_stage: bool
func _init(call_stage := true) -> void:
_call_stage = call_stage
-func _execute(context :GdUnitExecutionContext) -> void:
+func _execute(context: GdUnitExecutionContext) -> void:
var test_suite := context.test_suite
+
if _call_stage:
@warning_ignore("redundant_await")
await test_suite.after_test()
@@ -21,69 +21,22 @@ func _execute(context :GdUnitExecutionContext) -> void:
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:
- fire_test_ended(context)
- if is_instance_valid(context.test_case):
- context.test_case.dispose()
-
-
-func set_test_name(test_name :StringName) -> void:
- _test_name = test_name
-
-
-func fire_test_ended(context :GdUnitExecutionContext) -> void:
- var test_suite := context.test_suite
- var test_name := context._test_case_name if _test_name.is_empty() else _test_name
- var reports := collect_reports(context)
- var orphans := collect_orphans(context, reports)
-
- fire_event(GdUnitEvent.new()\
- .test_after(test_suite.get_script().resource_path, test_suite.get_name(), test_name, context.build_report_statistics(orphans), reports))
-
-func collect_orphans(context :GdUnitExecutionContext, reports :Array[GdUnitReport]) -> int:
- var orphans := 0
- if not context._sub_context.is_empty():
- orphans += add_orphan_report_test(context._sub_context[0], reports)
- orphans += add_orphan_report_teststage(context, reports)
- return orphans
+ var reports := context.build_reports()
-
-func collect_reports(context :GdUnitExecutionContext) -> Array[GdUnitReport]:
- var reports := context.reports()
- var test_case := context.test_case
- if test_case.is_interupted() and not test_case.is_expect_interupted() and test_case.report() != null:
- reports.push_back(test_case.report())
- # we combine the reports of test_before(), test_after() and test() to be reported by `fire_test_ended`
- if not context._sub_context.is_empty():
- reports.append_array(context._sub_context[0].reports())
- # needs finally to clean the test reports to avoid counting twice
- context._sub_context[0].reports().clear()
- return reports
-
-
-func add_orphan_report_test(context :GdUnitExecutionContext, reports :Array[GdUnitReport]) -> int:
- var orphans := context.count_orphans()
- if orphans > 0:
- reports.push_front(GdUnitReport.new()\
- .create(GdUnitReport.WARN, context.test_case.line_number(), GdAssertMessages.orphan_detected_on_test(orphans)))
- return orphans
-
-
-func add_orphan_report_teststage(context :GdUnitExecutionContext, reports :Array[GdUnitReport]) -> int:
- var orphans := context.count_orphans()
- if orphans > 0:
- reports.push_front(GdUnitReport.new()\
- .create(GdUnitReport.WARN, context.test_case.line_number(), GdAssertMessages.orphan_detected_on_test_setup(orphans)))
- return orphans
+ if context.is_skipped():
+ fire_test_skipped(context)
+ else:
+ fire_event(GdUnitEvent.new() \
+ .test_after(context.get_test_suite_path(),
+ context.get_test_suite_name(),
+ context.get_test_case_name(),
+ context.get_execution_statistics(),
+ reports))
-func fire_test_skipped(context :GdUnitExecutionContext) -> void:
- var test_suite := context.test_suite
+func fire_test_skipped(context: GdUnitExecutionContext) -> void:
var test_case := context.test_case
- var test_case_name := context._test_case_name if _test_name.is_empty() else _test_name
var statistics := {
GdUnitEvent.ORPHAN_NODES: 0,
GdUnitEvent.ELAPSED_TIME: 0,
@@ -95,6 +48,11 @@ func fire_test_skipped(context :GdUnitExecutionContext) -> void:
GdUnitEvent.SKIPPED: true,
GdUnitEvent.SKIPPED_COUNT: 1,
}
- var report := GdUnitReport.new().create(GdUnitReport.SKIPPED, test_case.line_number(), GdAssertMessages.test_skipped(test_case.skip_info()))
- fire_event(GdUnitEvent.new()\
- .test_after(test_suite.get_script().resource_path, test_suite.get_name(), test_case_name, statistics, [report]))
+ var report := GdUnitReport.new() \
+ .create(GdUnitReport.SKIPPED, test_case.line_number(), GdAssertMessages.test_skipped(test_case.skip_info()))
+ fire_event(GdUnitEvent.new() \
+ .test_after(context.get_test_suite_path(),
+ context.get_test_suite_name(),
+ context.get_test_case_name(),
+ statistics,
+ [report]))
diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd
index ebbd6d52..82e90d4a 100644
--- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd
+++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseBeforeStage.gd
@@ -3,8 +3,6 @@
class_name GdUnitTestCaseBeforeStage
extends IGdUnitExecutionStage
-
-var _test_name :StringName = ""
var _call_stage :bool
@@ -14,16 +12,10 @@ func _init(call_stage := true) -> void:
func _execute(context :GdUnitExecutionContext) -> void:
var test_suite := context.test_suite
- var test_case_name := context._test_case_name if _test_name.is_empty() else _test_name
fire_event(GdUnitEvent.new()\
- .test_before(test_suite.get_script().resource_path, test_suite.get_name(), test_case_name))
-
+ .test_before(context.get_test_suite_path(), context.get_test_suite_name(), context.get_test_case_name()))
if _call_stage:
@warning_ignore("redundant_await")
await test_suite.before_test()
context.error_monitor_start()
-
-
-func set_test_name(test_name :StringName) -> void:
- _test_name = test_name
diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd
index 148d9af6..853070d5 100644
--- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd
+++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestCaseExecutionStage.gd
@@ -16,6 +16,9 @@ var _stage_parameterized_test :IGdUnitExecutionStage= GdUnitTestCaseParameterize
@warning_ignore("redundant_await")
func _execute(context :GdUnitExecutionContext) -> void:
var test_case := context.test_case
+
+ context.error_monitor_start()
+
if test_case.is_parameterized():
await _stage_parameterized_test.execute(context)
elif test_case.is_fuzzed():
@@ -23,6 +26,20 @@ func _execute(context :GdUnitExecutionContext) -> void:
else:
await _stage_single_test.execute(context)
+ await context.gc()
+ await context.error_monitor_stop()
+
+ # finally fire test statistics report
+ fire_event(GdUnitEvent.new()\
+ .test_statistics(context.get_test_suite_path(),
+ context.get_test_suite_name(),
+ context.get_test_case_name(),
+ context.get_execution_statistics()))
+
+ # finally free the test instance
+ if is_instance_valid(context.test_case):
+ context.test_case.dispose()
+
func set_debug_mode(debug_mode :bool = false) -> void:
super.set_debug_mode(debug_mode)
diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd
index a6de3187..08bf3e56 100644
--- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd
+++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteAfterStage.gd
@@ -15,14 +15,15 @@ func _execute(context :GdUnitExecutionContext) -> void:
# unreference last used assert form the test to prevent memory leaks
GdUnitThreadManager.get_current_context().set_assert(null)
await context.gc()
-
- var reports := context.reports()
- var orphans := context.count_orphans()
- if orphans > 0:
- reports.push_front(GdUnitReport.new() \
- .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))
+ var reports := context.build_reports(false)
+ fire_event(GdUnitEvent.new()\
+ .suite_after(context.get_test_suite_path(),\
+ test_suite.get_name(),
+ context.get_execution_statistics(),
+ reports))
GdUnitFileAccess.clear_tmp()
# Guard that checks if all doubled (spy/mock) objects are released
GdUnitClassDoubler.check_leaked_instances()
+ # we hide the scene/main window after runner is finished
+ DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED)
diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd
index 2260edb3..e9fa7186 100644
--- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd
+++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteBeforeStage.gd
@@ -8,7 +8,7 @@ func _execute(context :GdUnitExecutionContext) -> void:
var test_suite := context.test_suite
fire_event(GdUnitEvent.new()\
- .suite_before(test_suite.get_script().resource_path, test_suite.get_name(), test_suite.get_child_count()))
+ .suite_before(context.get_test_suite_path(), test_suite.get_name(), test_suite.get_child_count()))
@warning_ignore("redundant_await")
await test_suite.before()
diff --git a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd
index ba223910..5aad57d7 100644
--- a/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd
+++ b/addons/gdUnit4/src/core/execution/stages/GdUnitTestSuiteExecutionStage.gd
@@ -19,6 +19,7 @@ func _execute(context :GdUnitExecutionContext) -> void:
if context.test_suite.__is_skipped:
await fire_test_suite_skipped(context)
else:
+ @warning_ignore("return_value_discarded")
GdUnitMemoryObserver.guard_instance(context.test_suite.__awaiter)
await _stage_before.execute(context)
for test_case_index in context.test_suite.get_child_count():
@@ -27,9 +28,9 @@ func _execute(context :GdUnitExecutionContext) -> void:
if not is_instance_valid(test_case):
continue
context.test_suite.set_active_test_case(test_case.get_name())
- await _stage_test.execute(GdUnitExecutionContext.of_test_case(context, test_case.get_name()))
+ await _stage_test.execute(GdUnitExecutionContext.of_test_case(context, test_case))
# stop on first error or if fail fast is enabled
- if _fail_fast and context.test_failed():
+ if _fail_fast and not context.is_success():
break
if test_case.is_interupted():
# it needs to go this hard way to kill the outstanding awaits of a test case when the test timed out
@@ -38,14 +39,14 @@ func _execute(context :GdUnitExecutionContext) -> void:
context.test_suite = await clone_test_suite(context.test_suite)
await _stage_after.execute(context)
GdUnitMemoryObserver.unguard_instance(context.test_suite.__awaiter)
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
context.test_suite.free()
context.dispose()
# clones a test suite and moves the test cases to new instance
func clone_test_suite(test_suite :GdUnitTestSuite) -> GdUnitTestSuite:
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
dispose_timers(test_suite)
await GdUnitMemoryObserver.gc_guarded_instance("Manually free on awaiter", test_suite.__awaiter)
var parent := test_suite.get_parent()
@@ -56,10 +57,11 @@ func clone_test_suite(test_suite :GdUnitTestSuite) -> GdUnitTestSuite:
test_suite.remove_child(child)
_test_suite.add_child(child)
parent.add_child(_test_suite)
+ @warning_ignore("return_value_discarded")
GdUnitMemoryObserver.guard_instance(_test_suite.__awaiter)
# finally free current test suite instance
test_suite.free()
- await Engine.get_main_loop().process_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
return _test_suite
@@ -67,7 +69,7 @@ func dispose_timers(test_suite :GdUnitTestSuite) -> void:
GdUnitTools.release_timers()
for child in test_suite.get_children():
if child is Timer:
- child.stop()
+ (child as Timer).stop()
test_suite.remove_child(child)
child.free()
@@ -86,7 +88,7 @@ func fire_test_suite_skipped(context :GdUnitExecutionContext) -> void:
var test_suite := context.test_suite
var skip_count := test_suite.get_child_count()
fire_event(GdUnitEvent.new()\
- .suite_before(test_suite.get_script().resource_path, test_suite.get_name(), skip_count))
+ .suite_before(context.get_test_suite_path(), test_suite.get_name(), skip_count))
var statistics := {
GdUnitEvent.ORPHAN_NODES: 0,
GdUnitEvent.ELAPSED_TIME: 0,
@@ -99,8 +101,8 @@ func fire_test_suite_skipped(context :GdUnitExecutionContext) -> void:
GdUnitEvent.SKIPPED: true
}
var report := GdUnitReport.new().create(GdUnitReport.SKIPPED, -1, GdAssertMessages.test_suite_skipped(test_suite.__skip_reason, skip_count))
- fire_event(GdUnitEvent.new().suite_after(test_suite.get_script().resource_path, test_suite.get_name(), statistics, [report]))
- await Engine.get_main_loop().process_frame
+ fire_event(GdUnitEvent.new().suite_after(context.get_test_suite_path(), test_suite.get_name(), statistics, [report]))
+ await (Engine.get_main_loop() as SceneTree).process_frame
func set_debug_mode(debug_mode :bool = false) -> void:
diff --git a/addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd
index 0f6ae93a..39de3809 100644
--- a/addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd
+++ b/addons/gdUnit4/src/core/execution/stages/IGdUnitExecutionStage.gd
@@ -14,7 +14,7 @@ var _debug_mode := false
## await MyExecutionStage.new().execute()
## [/codeblock][br]
func execute(context :GdUnitExecutionContext) -> void:
- context.set_active()
+ GdUnitThreadManager.get_current_context().set_execution_context(context)
@warning_ignore("redundant_await")
await _execute(context)
diff --git a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd
index d438b57c..243b889b 100644
--- a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd
+++ b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedExecutionStage.gd
@@ -8,10 +8,16 @@ var _stage_test :IGdUnitExecutionStage = GdUnitTestCaseFuzzedTestStage.new()
func _execute(context :GdUnitExecutionContext) -> void:
- await _stage_before.execute(context)
- if not context.test_case.is_skipped():
- await _stage_test.execute(GdUnitExecutionContext.of(context))
- await _stage_after.execute(context)
+ while context.retry_execution():
+ var test_context := GdUnitExecutionContext.of(context)
+ await _stage_before.execute(test_context)
+ if not context.test_case.is_skipped():
+ await _stage_test.execute(GdUnitExecutionContext.of(test_context))
+ await _stage_after.execute(test_context)
+ if test_context.is_success() or test_context.is_skipped() or test_context.is_interupted():
+ break
+ @warning_ignore("return_value_discarded")
+ context.evaluate_test_retry_status()
func set_debug_mode(debug_mode :bool = false) -> void:
diff --git a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd
index e6d98521..d32c28ed 100644
--- a/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd
+++ b/addons/gdUnit4/src/core/execution/stages/fuzzed/GdUnitTestCaseFuzzedTestStage.gd
@@ -15,6 +15,7 @@ func _execute(context :GdUnitExecutionContext) -> void:
# guard on fuzzers
for fuzzer in fuzzers:
+ @warning_ignore("return_value_discarded")
GdUnitMemoryObserver.guard_instance(fuzzer)
for iteration in test_case.iterations():
@@ -46,7 +47,8 @@ func create_fuzzers(test_suite :GdUnitTestSuite, test_case :_TestCase) -> Array[
test_case.generate_seed()
var fuzzers :Array[Fuzzer] = []
for fuzzer_arg in test_case.fuzzer_arguments():
- var fuzzer := _expression_runner.to_fuzzer(test_suite.get_script(), fuzzer_arg.value_as_string())
+ @warning_ignore("unsafe_cast")
+ var fuzzer := _expression_runner.to_fuzzer(test_suite.get_script() as GDScript, fuzzer_arg.plain_value() as String)
fuzzer._iteration_index = 0
fuzzer._iteration_limit = test_case.iterations()
fuzzers.append(fuzzer)
diff --git a/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterSetTestStage.gd b/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterSetTestStage.gd
new file mode 100644
index 00000000..5de35c07
--- /dev/null
+++ b/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterSetTestStage.gd
@@ -0,0 +1,10 @@
+class_name GdUnitTestCaseParameterSetTestStage
+extends IGdUnitExecutionStage
+
+
+## Executes a parameterized test case 'test_()' by given parameters.[br]
+## It executes synchronized following stages[br]
+## -> test_case() [br]
+func _execute(context: GdUnitExecutionContext) -> void:
+ await context.test_case.execute_paramaterized(context._test_case_parameter_set)
+ await context.gc()
diff --git a/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedTestStage.gd b/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedTestStage.gd
index 99d616e4..820a9d9f 100644
--- a/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedTestStage.gd
+++ b/addons/gdUnit4/src/core/execution/stages/parameterized/GdUnitTestCaseParameterizedTestStage.gd
@@ -4,6 +4,7 @@ extends IGdUnitExecutionStage
var _stage_before: IGdUnitExecutionStage = GdUnitTestCaseBeforeStage.new()
var _stage_after: IGdUnitExecutionStage = GdUnitTestCaseAfterStage.new()
+var _stage_test: IGdUnitExecutionStage = GdUnitTestCaseParameterSetTestStage.new()
## Executes a parameterized test case.[br]
@@ -12,9 +13,6 @@ var _stage_after: IGdUnitExecutionStage = GdUnitTestCaseAfterStage.new()
func _execute(context: GdUnitExecutionContext) -> void:
var test_case := context.test_case
var test_parameter_index := test_case.test_parameter_index()
- var is_fail := false
- var is_error := false
- var failing_index := 0
var parameter_set_resolver := test_case.parameter_set_resolver()
var test_names := parameter_set_resolver.build_test_case_names(test_case)
@@ -28,44 +26,55 @@ func _execute(context: GdUnitExecutionContext) -> void:
if test_parameter_index != -1 and test_parameter_index != parameter_set_index:
continue
var current_test_case_name := test_names[parameter_set_index]
- _stage_before.set_test_name(current_test_case_name)
- _stage_after.set_test_name(current_test_case_name)
+ var test_case_parameter_set: Array
+ if parameter_set_resolver.is_parameter_set_static(parameter_set_index):
+ test_case_parameter_set = parameter_sets[parameter_set_index]
var test_context := GdUnitExecutionContext.of(context)
- await _stage_before.execute(test_context)
- var current_parameter_set :Array
- if parameter_set_resolver.is_parameter_set_static(parameter_set_index):
- current_parameter_set = parameter_sets[parameter_set_index]
- else:
- current_parameter_set = _load_parameter_set(context, parameter_set_index)
- if not test_case.is_interupted():
- await test_case.execute_paramaterized(current_parameter_set)
- await _stage_after.execute(test_context)
- # we need to clean up the reports here so they are not reported twice
- is_fail = is_fail or test_context.count_failures(false) > 0
- is_error = is_error or test_context.count_errors(false) > 0
- failing_index = parameter_set_index - 1
- test_context.reports().clear()
+ test_context._test_case_name = current_test_case_name
+ var has_errors := false
+ while test_context.retry_execution():
+ var retry_test_context := GdUnitExecutionContext.of(test_context)
+
+ retry_test_context._test_case_name = current_test_case_name
+ await _stage_before.execute(retry_test_context)
+ if not test_case.is_interupted():
+ # we need to load paramater set at execution level after the before stage to get the actual variables from the current test
+ if not parameter_set_resolver.is_parameter_set_static(parameter_set_index):
+ test_case_parameter_set = _load_parameter_set(context, parameter_set_index)
+ await _stage_test.execute(GdUnitExecutionContext.of_parameterized_test(retry_test_context, current_test_case_name, test_case_parameter_set))
+ await _stage_after.execute(retry_test_context)
+ has_errors = retry_test_context.has_errors()
+ if retry_test_context.is_success() or retry_test_context.is_skipped() or retry_test_context.is_interupted():
+ break
+
+ var is_success := test_context.evaluate_test_retry_status()
+ report_test_failure(context, !is_success, has_errors, parameter_set_index)
+
if test_case.is_interupted():
break
- # add report to parent execution context if failed or an error is found
- if is_fail:
- context.reports().append(GdUnitReport.new().create(GdUnitReport.FAILURE, test_case.line_number(), "Test failed at parameterized index %d." % failing_index))
- if is_error:
- context.reports().append(GdUnitReport.new().create(GdUnitReport.ABORT, test_case.line_number(), "Test aborted at parameterized index %d." % failing_index))
await context.gc()
+func report_test_failure(test_context: GdUnitExecutionContext, is_failed: bool, has_errors: bool, parameter_set_index: int) -> void:
+ var test_case := test_context.test_case
+
+ if is_failed:
+ test_context.add_report(GdUnitReport.new().create(GdUnitReport.FAILURE, test_case.line_number(), "Test failed at parameterized index %d." % parameter_set_index))
+ if has_errors:
+ test_context.add_report(GdUnitReport.new().create(GdUnitReport.ABORT, test_case.line_number(), "Test aborted at parameterized index %d." % parameter_set_index))
+
+
func _load_parameter_set(context: GdUnitExecutionContext, parameter_set_index: int) -> Array:
var test_case := context.test_case
- var test_suite := context.test_suite
# we need to exchange temporary for parameter resolving the execution context
# this is necessary because of possible usage of `auto_free` and needs to run in the parent execution context
- var save_execution_context: GdUnitExecutionContext = test_suite.__execution_context
- context.set_active()
+ var thread_context := GdUnitThreadManager.get_current_context()
+ var save_execution_context := thread_context.get_execution_context()
+ thread_context.set_execution_context(context)
var parameters := test_case.load_parameter_sets()
# restore the original execution context and restart the orphan monitor to get new instances into account
- save_execution_context.set_active()
+ thread_context.set_execution_context(save_execution_context)
save_execution_context.orphan_monitor_start()
return parameters[parameter_set_index]
@@ -74,3 +83,4 @@ func set_debug_mode(debug_mode: bool=false) -> void:
super.set_debug_mode(debug_mode)
_stage_before.set_debug_mode(debug_mode)
_stage_after.set_debug_mode(debug_mode)
+ _stage_test.set_debug_mode(debug_mode)
diff --git a/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd b/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd
index b54d6a55..775b8dbc 100644
--- a/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd
+++ b/addons/gdUnit4/src/core/execution/stages/single/GdUnitTestCaseSingleExecutionStage.gd
@@ -9,10 +9,16 @@ var _stage_test :IGdUnitExecutionStage = GdUnitTestCaseSingleTestStage.new()
func _execute(context :GdUnitExecutionContext) -> void:
- await _stage_before.execute(context)
- if not context.test_case.is_skipped():
- await _stage_test.execute(GdUnitExecutionContext.of(context))
- await _stage_after.execute(context)
+ while context.retry_execution():
+ var test_context := GdUnitExecutionContext.of(context)
+ await _stage_before.execute(test_context)
+ if not test_context.is_skipped():
+ await _stage_test.execute(GdUnitExecutionContext.of(test_context))
+ await _stage_after.execute(test_context)
+ if test_context.is_success() or test_context.is_skipped() or test_context.is_interupted():
+ break
+ @warning_ignore("return_value_discarded")
+ context.evaluate_test_retry_status()
func set_debug_mode(debug_mode :bool = false) -> void:
diff --git a/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd b/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd
index 87b1aeb5..fc83742c 100644
--- a/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd
+++ b/addons/gdUnit4/src/core/parse/GdClassDescriptor.gd
@@ -3,7 +3,6 @@ extends RefCounted
var _name :String
-var _parent :GdClassDescriptor = null
var _is_inner_class :bool
var _functions :Array[GdFunctionDescriptor]
@@ -14,18 +13,10 @@ func _init(p_name :String, p_is_inner_class :bool, p_functions :Array[GdFunction
_functions = p_functions
-func set_parent_clazz(p_parent :GdClassDescriptor) -> void:
- _parent = p_parent
-
-
func name() -> String:
return _name
-func parent() -> GdClassDescriptor:
- return _parent
-
-
func is_inner_class() -> bool:
return _is_inner_class
diff --git a/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd b/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd
index b1cc38a4..33022fd8 100644
--- a/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd
+++ b/addons/gdUnit4/src/core/parse/GdDefaultValueDecoder.gd
@@ -64,11 +64,11 @@ func _on_type_StringName(value :StringName) -> String:
return 'StringName("%s")' % value
-func _on_type_Object(value :Object, _type :int) -> String:
+func _on_type_Object(value: Variant, _type: int) -> String:
return str(value)
-func _on_type_Color(color :Color) -> String:
+func _on_type_Color(color: Color) -> String:
if color == Color.BLACK:
return "Color()"
return "Color%s" % color
@@ -101,7 +101,8 @@ func _on_type_Array(value :Variant, type :int) -> String:
TYPE_PACKED_COLOR_ARRAY:
var colors := PackedStringArray()
- for color in value as PackedColorArray:
+ for color: Color in value:
+ @warning_ignore("return_value_discarded")
colors.append(_on_type_Color(color))
if colors.is_empty():
return "PackedColorArray()"
@@ -109,7 +110,8 @@ func _on_type_Array(value :Variant, type :int) -> String:
TYPE_PACKED_VECTOR2_ARRAY:
var vectors := PackedStringArray()
- for vector in value as PackedVector2Array:
+ for vector: Vector2 in value:
+ @warning_ignore("return_value_discarded")
vectors.append(_on_type_Vector(vector, TYPE_VECTOR2))
if vectors.is_empty():
return "PackedVector2Array()"
@@ -117,7 +119,8 @@ func _on_type_Array(value :Variant, type :int) -> String:
TYPE_PACKED_VECTOR3_ARRAY:
var vectors := PackedStringArray()
- for vector in value as PackedVector3Array:
+ for vector: Vector3 in value:
+ @warning_ignore("return_value_discarded")
vectors.append(_on_type_Vector(vector, TYPE_VECTOR3))
if vectors.is_empty():
return "PackedVector3Array()"
@@ -125,7 +128,8 @@ func _on_type_Array(value :Variant, type :int) -> String:
GdObjects.TYPE_PACKED_VECTOR4_ARRAY:
var vectors := PackedStringArray()
- for vector:Variant in value as Array:
+ for vector: Vector4 in value:
+ @warning_ignore("return_value_discarded")
vectors.append(_on_type_Vector(vector, TYPE_VECTOR4))
if vectors.is_empty():
return "PackedVector4Array()"
@@ -133,7 +137,8 @@ func _on_type_Array(value :Variant, type :int) -> String:
TYPE_PACKED_STRING_ARRAY:
var values := PackedStringArray()
- for v in value as PackedStringArray:
+ for v: String in value:
+ @warning_ignore("return_value_discarded")
values.append('"%s"' % v)
if values.is_empty():
return "PackedStringArray()"
@@ -145,7 +150,8 @@ func _on_type_Array(value :Variant, type :int) -> String:
TYPE_PACKED_INT32_ARRAY,\
TYPE_PACKED_INT64_ARRAY:
var vectors := PackedStringArray()
- for vector :Variant in value as Array:
+ for vector :Variant in value:
+ @warning_ignore("return_value_discarded")
vectors.append(str(vector))
if vectors.is_empty():
return GdObjects.type_as_string(type) + "()"
@@ -239,11 +245,16 @@ func _on_type_Basis(basis :Basis) -> String:
return "Basis(Vector3%s, Vector3%s, Vector3%s)" % [basis.x, basis.y, basis.z]
+@warning_ignore("unsafe_cast")
static func decode(value :Variant) -> String:
var type := typeof(value)
- if GdArrayTools.is_type_array(type) and value.is_empty():
+ if GdArrayTools.is_type_array(type) and (value as Array).is_empty():
return ""
- var decoder :Callable = instance("GdUnitDefaultValueDecoders", func() -> GdDefaultValueDecoder: return GdDefaultValueDecoder.new()).get_decoder(type)
+ var decoder :Callable = (
+ instance("GdUnitDefaultValueDecoders",
+ func() -> GdDefaultValueDecoder: return GdDefaultValueDecoder.new()
+ ) as GdDefaultValueDecoder
+ ).get_decoder(type)
if decoder == null:
push_error("No value decoder registered for type '%d'! Please open a Bug issue at 'https://github.com/MikeSchulze/gdUnit4/issues/new/choose'." % type)
return "null"
@@ -252,10 +263,15 @@ static func decode(value :Variant) -> String:
return decoder.call(value)
+@warning_ignore("unsafe_cast")
static func decode_typed(type :int, value :Variant) -> String:
if value == null:
return "null"
- var decoder :Callable = instance("GdUnitDefaultValueDecoders", func() -> GdDefaultValueDecoder: return GdDefaultValueDecoder.new()).get_decoder(type)
+ var decoder: Callable = (
+ instance("GdUnitDefaultValueDecoders",
+ func() -> GdDefaultValueDecoder: return GdDefaultValueDecoder.new()
+ ) as GdDefaultValueDecoder
+ ).get_decoder(type)
if decoder == null:
push_error("No value decoder registered for type '%d'! Please open a Bug issue at 'https://github.com/MikeSchulze/gdUnit4/issues/new/choose'." % type)
return "null"
diff --git a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd
index 57891860..190d9461 100644
--- a/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd
+++ b/addons/gdUnit4/src/core/parse/GdFunctionArgument.gd
@@ -2,23 +2,39 @@ class_name GdFunctionArgument
extends RefCounted
-var _cleanup_leading_spaces := RegEx.create_from_string("(?m)^[ \t]+")
-var _fix_comma_space := RegEx.create_from_string(""", {0,}\t{0,}(?=(?:[^"]*"[^"]*")*[^"]*$)(?!\\s)""")
+const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
+const UNDEFINED: String = "<-NO_ARG->"
+const ARG_PARAMETERIZED_TEST := "test_parameters"
+
+static var _fuzzer_regex: RegEx
+static var _cleanup_leading_spaces: RegEx
+static var _fix_comma_space: RegEx
+
var _name: String
var _type: int
-var _default_value :Variant
-var _parameter_sets :PackedStringArray = []
-
-const UNDEFINED :Variant = "<-NO_ARG->"
-const ARG_PARAMETERIZED_TEST := "test_parameters"
+var _type_hint: int
+var _default_value: Variant
+var _parameter_sets: PackedStringArray = []
-func _init(p_name :String, p_type :int = TYPE_MAX, value :Variant = UNDEFINED) -> void:
+func _init(p_name: String, p_type: int, value: Variant = UNDEFINED, p_type_hint: int = TYPE_NIL) -> void:
+ _init_static_variables()
_name = p_name
_type = p_type
- if p_name == ARG_PARAMETERIZED_TEST:
- _parameter_sets = _parse_parameter_set(value)
+ _type_hint = p_type_hint
+ if value != null and p_name == ARG_PARAMETERIZED_TEST:
+ _parameter_sets = _parse_parameter_set(str(value))
_default_value = value
+ # is argument a fuzzer?
+ if _type == TYPE_OBJECT and _fuzzer_regex.search(_name):
+ _type = GdObjects.TYPE_FUZZER
+
+
+func _init_static_variables() -> void:
+ if _fuzzer_regex == null:
+ _fuzzer_regex = GdUnitTools.to_regex("((?!(fuzzer_(seed|iterations)))fuzzer?\\w+)( ?+= ?+| ?+:= ?+| ?+:Fuzzer ?+= ?+|)")
+ _cleanup_leading_spaces = RegEx.create_from_string("(?m)^[ \t]+")
+ _fix_comma_space = RegEx.create_from_string(""", {0,}\t{0,}(?=(?:[^"]*"[^"]*")*[^"]*$)(?!\\s)""")
func name() -> String:
@@ -29,20 +45,58 @@ func default() -> Variant:
return GodotVersionFixures.convert(_default_value, _type)
+func set_value(value: String) -> void:
+ # we onle need to apply default values for Objects, all others are provided by the method descriptor
+ if _type == GdObjects.TYPE_FUZZER:
+ _default_value = value
+ return
+ if _name == ARG_PARAMETERIZED_TEST:
+ _parameter_sets = _parse_parameter_set(value)
+ _default_value = value
+ return
+
+ if _type == TYPE_NIL or _type == GdObjects.TYPE_VARIANT:
+ _type = _extract_value_type(value)
+ _default_value = value
+ if _default_value == null:
+ _default_value = value
+
+
+func _extract_value_type(value: String) -> int:
+ if value != UNDEFINED:
+ if _fuzzer_regex.search(_name):
+ return GdObjects.TYPE_FUZZER
+ if value.rfind(")") == value.length()-1:
+ return GdObjects.TYPE_FUNC
+ return _type
+
+
func value_as_string() -> String:
if has_default():
- return str(_default_value)
+ return GdDefaultValueDecoder.decode_typed(_type, _default_value)
return ""
+func plain_value() -> Variant:
+ return _default_value
+
+
func type() -> int:
return _type
+func type_hint() -> int:
+ return _type_hint
+
+
func has_default() -> bool:
return not is_same(_default_value, UNDEFINED)
+func is_typed_array() -> bool:
+ return _type == TYPE_ARRAY and _type_hint != TYPE_NIL
+
+
func is_parameter_set() -> bool:
return _name == ARG_PARAMETERIZED_TEST
@@ -60,10 +114,12 @@ static func get_parameter_set(parameters :Array[GdFunctionArgument]) -> GdFuncti
func _to_string() -> String:
var s := _name
- if _type != TYPE_MAX:
+ if _type != TYPE_NIL:
s += ":" + GdObjects.type_as_string(_type)
- if _default_value != UNDEFINED:
- s += "=" + str(_default_value)
+ if _type_hint != TYPE_NIL:
+ s += "[%s]" % GdObjects.type_as_string(_type_hint)
+ if typeof(_default_value) != TYPE_STRING:
+ s += "=" + value_as_string()
return s
@@ -85,6 +141,7 @@ func _parse_parameter_set(input :String) -> PackedStringArray:
for c in buf:
current_index += 1
matched = current_index == buf.size()
+ @warning_ignore("return_value_discarded")
collected_characters.push_back(c)
match c:
@@ -108,6 +165,7 @@ func _parse_parameter_set(input :String) -> PackedStringArray:
if matched:
var parameters := _fix_comma_space.sub(collected_characters.get_string_from_utf8(), ", ", true)
if not parameters.is_empty():
+ @warning_ignore("return_value_discarded")
output.append(parameters)
collected_characters.clear()
matched = false
diff --git a/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd b/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd
index 2633016e..27a21b2f 100644
--- a/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd
+++ b/addons/gdUnit4/src/core/parse/GdFunctionDescriptor.gd
@@ -6,6 +6,7 @@ var _is_static :bool
var _is_engine :bool
var _is_coroutine :bool
var _name :String
+var _source_path: String
var _line_number :int
var _return_type :int
var _return_class :String
@@ -13,6 +14,18 @@ var _args : Array[GdFunctionArgument]
var _varargs :Array[GdFunctionArgument]
+
+static func create(p_name: String, p_source_path: String, p_source_line: int, p_return_type: int, p_args: Array[GdFunctionArgument] = []) -> GdFunctionDescriptor:
+ var fd := GdFunctionDescriptor.new(p_name, p_source_line, false, false, false, p_return_type, "", p_args)
+ fd.enrich_file_info(p_source_path, p_source_line)
+ return fd
+
+static func create_static(p_name: String, p_source_path: String, p_source_line: int, p_return_type: int, p_args: Array[GdFunctionArgument] = []) -> GdFunctionDescriptor:
+ var fd := GdFunctionDescriptor.new(p_name, p_source_line, false, true, false, p_return_type, "", p_args)
+ fd.enrich_file_info(p_source_path, p_source_line)
+ return fd
+
+
func _init(p_name :String,
p_line_number :int,
p_is_virtual :bool,
@@ -34,10 +47,19 @@ func _init(p_name :String,
_varargs = p_varargs
+func with_return_class(clazz_name: String) -> GdFunctionDescriptor:
+ _return_class = clazz_name
+ return self
+
+
func name() -> String:
return _name
+func source_path() -> String:
+ return _source_path
+
+
func line_number() -> int:
return _line_number
@@ -74,7 +96,7 @@ func is_private() -> bool:
return name().begins_with("_") and not is_virtual()
-func return_type() -> Variant:
+func return_type() -> int:
return _return_type
@@ -84,42 +106,34 @@ func return_type_as_string() -> String:
return GdObjects.type_as_string(return_type())
-func args() -> Array[GdFunctionArgument]:
- return _args
+@warning_ignore("unsafe_cast")
+func set_argument_value(arg_name: String, value: String) -> void:
+ (
+ _args.filter(func(arg: GdFunctionArgument) -> bool: return arg.name() == arg_name)\
+ .front() as GdFunctionArgument
+ ).set_value(value)
-func varargs() -> Array[GdFunctionArgument]:
- return _varargs
+func enrich_file_info(p_source_path: String, p_line_number: int) -> void:
+ _source_path = p_source_path
+ _line_number = p_line_number
-func typeless() -> String:
- var func_signature := ""
- if _return_type == TYPE_NIL:
- func_signature = "func %s(%s) -> void:" % [name(), typeless_args()]
- elif _return_type == GdObjects.TYPE_VARIANT:
- func_signature = "func %s(%s) -> Variant:" % [name(), typeless_args()]
- else:
- func_signature = "func %s(%s) -> %s:" % [name(), typeless_args(), return_type_as_string()]
- return "static " + func_signature if is_static() else func_signature
+func args() -> Array[GdFunctionArgument]:
+ return _args
-func typeless_args() -> String:
- var collect := PackedStringArray()
- for arg in args():
- if arg.has_default():
- collect.push_back( arg.name() + "=" + arg.value_as_string())
- else:
- collect.push_back(arg.name())
- for arg in varargs():
- collect.push_back(arg.name() + "=" + arg.value_as_string())
- return ", ".join(collect)
+func varargs() -> Array[GdFunctionArgument]:
+ return _varargs
func typed_args() -> String:
var collect := PackedStringArray()
for arg in args():
+ @warning_ignore("return_value_discarded")
collect.push_back(arg._to_string())
for arg in varargs():
+ @warning_ignore("return_value_discarded")
collect.push_back(arg._to_string())
return ", ".join(collect)
@@ -135,28 +149,23 @@ func _to_string() -> String:
# extract function description given by Object.get_method_list()
-static func extract_from(descriptor :Dictionary) -> GdFunctionDescriptor:
- var function_flags :int = descriptor["flags"]
- var is_virtual_ :bool = function_flags & METHOD_FLAG_VIRTUAL
- var is_static_ :bool = function_flags & METHOD_FLAG_STATIC
- var is_vararg_ :bool = function_flags & METHOD_FLAG_VARARG
- #var is_const :bool = function_flags & METHOD_FLAG_CONST
- #var is_core :bool = function_flags & METHOD_FLAG_OBJECT_CORE
- #var is_default :bool = function_flags & METHOD_FLAGS_DEFAULT
- #prints("is_virtual: ", is_virtual)
- #prints("is_static: ", is_static)
- #prints("is_const: ", is_const)
- #prints("is_core: ", is_core)
- #prints("is_default: ", is_default)
- #prints("is_vararg: ", is_vararg)
+static func extract_from(descriptor :Dictionary, is_engine_ := true) -> GdFunctionDescriptor:
+ var func_name: String = descriptor["name"]
+ var function_flags: int = descriptor["flags"]
+ var return_descriptor: Dictionary = descriptor["return"]
+ var clazz_name: String = return_descriptor["class_name"]
+ var is_virtual_: bool = function_flags & METHOD_FLAG_VIRTUAL
+ var is_static_: bool = function_flags & METHOD_FLAG_STATIC
+ var is_vararg_: bool = function_flags & METHOD_FLAG_VARARG
+
return GdFunctionDescriptor.new(
- descriptor["name"],
+ func_name,
-1,
is_virtual_,
is_static_,
- true,
- _extract_return_type(descriptor["return"]),
- descriptor["return"]["class_name"],
+ is_engine_,
+ _extract_return_type(return_descriptor),
+ clazz_name,
_extract_args(descriptor),
_build_varargs(is_vararg_)
)
@@ -185,13 +194,15 @@ const enum_fix := [
"Control.LayoutMode"]
-static func _extract_return_type(return_info :Dictionary) -> Variant:
+static func _extract_return_type(return_info :Dictionary) -> int:
var type :int = return_info["type"]
var usage :int = return_info["usage"]
if type == TYPE_INT and usage & PROPERTY_USAGE_CLASS_IS_ENUM:
return GdObjects.TYPE_ENUM
if type == TYPE_NIL and usage & PROPERTY_USAGE_NIL_IS_VARIANT:
return GdObjects.TYPE_VARIANT
+ if type == TYPE_NIL and usage == 6:
+ return GdObjects.TYPE_VOID
return type
@@ -204,11 +215,10 @@ static func _extract_args(descriptor :Dictionary) -> Array[GdFunctionArgument]:
var arg :Dictionary = arguments.pop_back()
var arg_name := _argument_name(arg)
var arg_type := _argument_type(arg)
- var arg_default :Variant = GdFunctionArgument.UNDEFINED
- if not defaults.is_empty():
- var default_value :Variant = defaults.pop_back()
- arg_default = GdDefaultValueDecoder.decode_typed(arg_type, default_value)
- args_.push_front(GdFunctionArgument.new(arg_name, arg_type, arg_default))
+ var arg_type_hint := _argument_hint(arg)
+ #var arg_class: StringName = arg["class_name"]
+ var default_value: Variant = GdFunctionArgument.UNDEFINED if defaults.is_empty() else defaults.pop_back()
+ args_.push_front(GdFunctionArgument.new(arg_name, arg_type, default_value, arg_type_hint))
return args_
@@ -219,23 +229,41 @@ static func _build_varargs(p_is_vararg :bool) -> Array[GdFunctionArgument]:
# if function has vararg we need to handle this manually by adding 10 default arguments
var type := GdObjects.TYPE_VARARG
for index in 10:
- varargs_.push_back(GdFunctionArgument.new("vararg%d_" % index, type, "\"%s\"" % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE))
+ varargs_.push_back(GdFunctionArgument.new("vararg%d_" % index, type, '"%s"' % GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE))
return varargs_
static func _argument_name(arg :Dictionary) -> String:
- # add suffix to the name to prevent clash with reserved names
- return (arg["name"] + "_") as String
+ return arg["name"]
static func _argument_type(arg :Dictionary) -> int:
var type :int = arg["type"]
+ var usage :int = arg["usage"]
+
if type == TYPE_OBJECT:
if arg["class_name"] == "Node":
return GdObjects.TYPE_NODE
+ if arg["class_name"] == "Fuzzer":
+ return GdObjects.TYPE_FUZZER
+
+ # if the argument untyped we need to scan the assignef value type
+ if type == TYPE_NIL and usage == PROPERTY_USAGE_NIL_IS_VARIANT:
+ return GdObjects.TYPE_VARIANT
return type
+static func _argument_hint(arg :Dictionary) -> int:
+ var hint :int = arg["hint"]
+ var hint_string :String = arg["hint_string"]
+
+ match hint:
+ PROPERTY_HINT_ARRAY_TYPE:
+ return GdObjects.string_to_type(hint_string)
+ _:
+ return 0
+
+
static func _argument_type_as_string(arg :Dictionary) -> String:
var type := _argument_type(arg)
match type:
diff --git a/addons/gdUnit4/src/core/parse/GdScriptParser.gd b/addons/gdUnit4/src/core/parse/GdScriptParser.gd
index 38468f3e..bc300fff 100644
--- a/addons/gdUnit4/src/core/parse/GdScriptParser.gd
+++ b/addons/gdUnit4/src/core/parse/GdScriptParser.gd
@@ -14,7 +14,7 @@ var TOKEN_CLASS_NAME := Token.new("class_name")
var TOKEN_INNER_CLASS := Token.new("class")
var TOKEN_EXTENDS := Token.new("extends")
var TOKEN_ENUM := Token.new("enum")
-var TOKEN_FUNCTION_STATIC_DECLARATION := Token.new("staticfunc")
+var TOKEN_FUNCTION_STATIC_DECLARATION := Token.new("static func")
var TOKEN_FUNCTION_DECLARATION := Token.new("func")
var TOKEN_FUNCTION := Token.new(".")
var TOKEN_FUNCTION_RETURN_TYPE := Token.new("->")
@@ -64,17 +64,12 @@ var TOKENS :Array[Token] = [
OPERATOR_REMAINDER,
]
-var _regex_clazz_name :RegEx
+var _regex_clazz_name := GdUnitTools.to_regex("(class) ([a-zA-Z0-9_]+) (extends[a-zA-Z]+:)|(class) ([a-zA-Z0-9_]+)")
var _regex_strip_comments := GdUnitTools.to_regex("^([^#\"']|'[^']*'|\"[^\"]*\")*\\K#.*")
-var _base_clazz :String
var _scanned_inner_classes := PackedStringArray()
var _script_constants := {}
-static func clean_up_row(row :String) -> String:
- return to_unix_format(row.replace(" ", "").replace("\t", ""))
-
-
static func to_unix_format(input :String) -> String:
return input.replace("\r\n", "\n")
@@ -263,6 +258,7 @@ class TokenInnerClass extends Token:
func parse(source_rows :PackedStringArray, offset :int) -> void:
# add class signature
+ @warning_ignore("return_value_discarded")
_content.append(source_rows[offset])
# parse class content
for row_index in range(offset+1, source_rows.size()):
@@ -275,8 +271,10 @@ class TokenInnerClass extends Token:
source_row = source_row.trim_prefix("\t")
# refomat invalid empty lines
if source_row.dedent().is_empty():
+ @warning_ignore("return_value_discarded")
_content.append("")
else:
+ @warning_ignore("return_value_discarded")
_content.append(source_row)
continue
break
@@ -287,9 +285,6 @@ class TokenInnerClass extends Token:
return "TokenInnerClass{%s}" % [_clazz_name]
-func _init() -> void:
- _regex_clazz_name = GdUnitTools.to_regex("(class)([a-zA-Z0-9]+)(extends[a-zA-Z]+:)|(class)([a-zA-Z0-9]+)(:)")
-
func get_token(input :String, current_index :int) -> Token:
for t in TOKENS:
@@ -352,38 +347,7 @@ func tokenize_inner_class(source_code: String, current: int, token: Token) -> To
return TokenInnerClass.new(clazz_name)
-@warning_ignore("assert_always_false")
-func _process_values(left: Token, token_stack: Array, operator: Token) -> Token:
- # precheck
- if left.is_variable() and operator.is_operator():
- var lvalue :Variant = left.value()
- var value :Variant = null
- var next_token_ := token_stack.pop_front() as Token
- match operator:
- OPERATOR_ADD:
- value = lvalue + next_token_.value()
- OPERATOR_SUB:
- value = lvalue - next_token_.value()
- OPERATOR_MUL:
- value = lvalue * next_token_.value()
- OPERATOR_DIV:
- value = lvalue / next_token_.value()
- OPERATOR_REMAINDER:
- value = lvalue & next_token_.value()
- _:
- assert(false, "Unsupported operator %s" % operator)
- return Variable.new( str(value))
- return operator
-
-
-func parse_func_return_type(row: String) -> int:
- var token := parse_return_token(row)
- if token == TOKEN_NOT_MATCH:
- return TYPE_NIL
- return token.type()
-
-
-func parse_return_token(input: String) -> Token:
+func parse_return_token(input: String) -> Variable:
var index := input.rfind(TOKEN_FUNCTION_RETURN_TYPE._token)
if index == -1:
return TOKEN_NOT_MATCH
@@ -397,10 +361,26 @@ func parse_return_token(input: String) -> Token:
return token
-# Parses the argument into a argument signature
-# e.g. func foo(arg1 :int, arg2 = 20) -> [arg1, arg2]
-func parse_arguments(input: String) -> Array[GdFunctionArgument]:
- var args :Array[GdFunctionArgument] = []
+func get_function_descriptors(script: GDScript, included_functions: PackedStringArray = []) -> Array[GdFunctionDescriptor]:
+ var fds: Array[GdFunctionDescriptor] = []
+ for method_descriptor in script.get_script_method_list():
+ var func_name: String = method_descriptor["name"]
+ if included_functions.is_empty() or func_name in included_functions:
+ # exclude type set/geters
+ if func_name in ["@type_setter", "@type_getter"]:
+ continue
+ if not fds.any(func(fd: GdFunctionDescriptor) -> bool: return fd.name() == func_name):
+ fds.append(GdFunctionDescriptor.extract_from(method_descriptor, false))
+
+ # we need to enrich it by default arguments and line number by parsing the script
+ # the engine core functions has no valid methods to get this info
+ _prescan_script(script)
+ _enrich_function_descriptor(script, fds)
+ return fds
+
+
+func _parse_function_arguments(input: String) -> Dictionary:
+ var arguments := {}
var current_index := 0
var token :Token = null
var bracket := 0
@@ -431,7 +411,7 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]:
bracket -= 1
# if function end?
if in_function and bracket == 0:
- return args
+ return arguments
# is function
if token == TOKEN_FUNCTION_DECLARATION:
token = next_token(input, current_index)
@@ -441,13 +421,13 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]:
if token is FuzzerToken:
var arg_value := _parse_end_function(input.substr(current_index), true)
current_index += arg_value.length()
- args.append(GdFunctionArgument.new(token.name(), token.type(), arg_value))
+ var arg_name :String = (token as FuzzerToken).name()
+ arguments[arg_name] = arg_value.lstrip(" ")
continue
# is value argument
if in_function and token.is_variable():
- var arg_name :String = token.plain_value()
- var arg_type :int = TYPE_NIL
- var arg_value :Variant = GdFunctionArgument.UNDEFINED
+ var arg_name: String = (token as Variable).plain_value()
+ var arg_value: String = GdFunctionArgument.UNDEFINED
# parse type and default value
while current_index < len(input):
token = next_token(input, current_index)
@@ -460,10 +440,6 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]:
if token == TOKEN_SPACE:
current_index += token._consumed
token = next_token(input, current_index)
- arg_type = GdObjects.string_as_typeof(token._token)
- # handle enum detection as argument
- if arg_type == GdObjects.TYPE_VARIANT and is_class_enum_type(token._token):
- arg_type = GdObjects.TYPE_ENUM
TOKEN_ARGUMENT_TYPE_ASIGNMENT:
arg_value = _parse_end_function(input.substr(current_index), true)
current_index += arg_value.length()
@@ -489,28 +465,8 @@ func parse_arguments(input: String) -> Array[GdFunctionArgument]:
TOKEN_ARGUMENT_SEPARATOR:
if bracket <= 1:
break
- arg_value = arg_value.lstrip(" ")
- if arg_type == TYPE_NIL and arg_value != GdFunctionArgument.UNDEFINED:
- if arg_value.begins_with("Color."):
- arg_type = TYPE_COLOR
- elif arg_value.begins_with("Vector2."):
- arg_type = TYPE_VECTOR2
- elif arg_value.begins_with("Vector3."):
- arg_type = TYPE_VECTOR3
- elif arg_value.begins_with("AABB("):
- arg_type = TYPE_AABB
- elif arg_value.begins_with("["):
- arg_type = TYPE_ARRAY
- elif arg_value.begins_with("{"):
- arg_type = TYPE_DICTIONARY
- else:
- arg_type = typeof(str_to_var(arg_value))
- if arg_value.rfind(")") == arg_value.length()-1:
- arg_type = GdObjects.TYPE_FUNC
- elif arg_type == TYPE_NIL:
- arg_type = TYPE_STRING
- args.append(GdFunctionArgument.new(arg_name, arg_type, arg_value))
- return args
+ arguments[arg_name] = arg_value.lstrip(" ")
+ return arguments
func _parse_end_function(input: String, remove_trailing_char := false) -> String:
@@ -563,9 +519,10 @@ func _parse_end_function(input: String, remove_trailing_char := false) -> String
return input.substr(0, current_index)
+@warning_ignore("unsafe_method_access")
func extract_inner_class(source_rows: PackedStringArray, clazz_name :String) -> PackedStringArray:
for row_index in source_rows.size():
- var input := GdScriptParser.clean_up_row(source_rows[row_index])
+ var input := source_rows[row_index]
var token := next_token(input, 0)
if token.is_inner_class():
if token.is_class_name(clazz_name):
@@ -574,21 +531,6 @@ func extract_inner_class(source_rows: PackedStringArray, clazz_name :String) ->
return PackedStringArray()
-func extract_source_code(script_path :PackedStringArray) -> PackedStringArray:
- if script_path.is_empty():
- push_error("Invalid script path '%s'" % script_path)
- return PackedStringArray()
- #load the source code
- var resource_path := script_path[0]
- var script :GDScript = load(resource_path)
- var source_code := load_source_code(script, script_path)
- var base_script := script.get_base_script()
- if base_script:
- _base_clazz = GdObjects.extract_class_name_from_class_path([base_script.resource_path])
- source_code += load_source_code(base_script, script_path)
- return source_code
-
-
func extract_func_signature(rows :PackedStringArray, index :int) -> String:
var signature := ""
@@ -604,25 +546,6 @@ func extract_func_signature(rows :PackedStringArray, index :int) -> String:
return ""
-func load_source_code(script :GDScript, script_path :PackedStringArray) -> PackedStringArray:
- var map := script.get_script_constant_map()
- for key :String in map.keys():
- var value :Variant = map.get(key)
- if value is GDScript:
- var class_path := GdObjects.extract_class_path(value)
- if class_path.size() > 1:
- _scanned_inner_classes.append(class_path[1])
-
- var source_code := GdScriptParser.to_unix_format(script.source_code)
- var source_rows := source_code.split("\n")
- # extract all inner class names
- # want to extract an inner class?
- if script_path.size() > 1:
- var inner_clazz := script_path[1]
- source_rows = extract_inner_class(source_rows, inner_clazz)
- return PackedStringArray(source_rows)
-
-
func get_class_name(script :GDScript) -> String:
var source_code := GdScriptParser.to_unix_format(script.source_code)
var source_rows := source_code.split("\n")
@@ -635,13 +558,12 @@ func get_class_name(script :GDScript) -> String:
token = next_token(input, current_index)
current_index += token._consumed
token = tokenize_value(input, current_index, token)
- return token.value()
+ return (token as Variable).value()
# if no class_name found extract from file name
return GdObjects.to_pascal_case(script.resource_path.get_basename().get_file())
-func parse_func_name(row :String) -> String:
- var input := GdScriptParser.clean_up_row(row)
+func parse_func_name(input :String) -> String:
var current_index := 0
var token := next_token(input, current_index)
current_index += token._consumed
@@ -653,100 +575,67 @@ func parse_func_name(row :String) -> String:
return token._token
-func parse_functions(rows :PackedStringArray, clazz_name :String, clazz_path :PackedStringArray, included_functions := PackedStringArray()) -> Array[GdFunctionDescriptor]:
- var func_descriptors :Array[GdFunctionDescriptor] = []
- for rowIndex in rows.size():
- var row := rows[rowIndex]
- # step over inner class functions
- if row.begins_with("\t"):
- continue
- var input := GdScriptParser.clean_up_row(row)
- # skip comments and empty lines
- if input.begins_with("#") or input.length() == 0:
- continue
- var token := next_token(input, 0)
- if token == TOKEN_FUNCTION_STATIC_DECLARATION or token == TOKEN_FUNCTION_DECLARATION:
- if _is_func_included(input, included_functions):
- var func_signature := extract_func_signature(rows, rowIndex)
- var fd := parse_func_description(func_signature, clazz_name, clazz_path, rowIndex+1)
- fd._is_coroutine = is_func_coroutine(rows, rowIndex)
- func_descriptors.append(fd)
- return func_descriptors
+## Enriches the function descriptor by line number and argument default values
+## - enrich all function descriptors form current script up to all inherited scrips
+func _enrich_function_descriptor(script: GDScript, fds: Array[GdFunctionDescriptor]) -> void:
+ var enriched_functions := PackedStringArray()
+ var script_to_scan := script
+ while script_to_scan != null:
+ # do not scan the test suite base class itself
+ if script_to_scan.resource_path == "res://addons/gdUnit4/src/GdUnitTestSuite.gd":
+ break
+
+ var rows := script_to_scan.source_code.split("\n")
+ for rowIndex in rows.size():
+ var input := rows[rowIndex]
+ # step over inner class functions
+ if input.begins_with("\t"):
+ continue
+ # skip comments and empty lines
+ if input.begins_with("#") or input.length() == 0:
+ continue
+ var token := next_token(input, 0)
+ if token == TOKEN_FUNCTION_STATIC_DECLARATION or token == TOKEN_FUNCTION_DECLARATION:
+ var function_name := parse_func_name(input)
+ var fd: GdFunctionDescriptor = fds.filter(func(element: GdFunctionDescriptor) -> bool:
+ # is same function name and not already enriched
+ return function_name == element.name() and not enriched_functions.has(element.name())
+ ).pop_front()
+ if fd != null:
+ # add as enriched function to exclude from next iteration (could be inherited)
+ @warning_ignore("return_value_discarded")
+ enriched_functions.append(fd.name())
+ var func_signature := extract_func_signature(rows, rowIndex)
+ var func_arguments := _parse_function_arguments(func_signature)
+ # enrich missing default values
+ for arg_name: String in func_arguments.keys():
+ var func_argument: String = func_arguments[arg_name]
+ fd.set_argument_value(arg_name, func_argument)
+ fd.enrich_file_info(script_to_scan.resource_path, rowIndex + 1)
+ fd._is_coroutine = is_func_coroutine(rows, rowIndex)
+ # enrich return class name if not set
+ if fd.return_type() == TYPE_OBJECT and fd._return_class in ["", "Resource", "RefCounted"]:
+ var var_token := parse_return_token(func_signature)
+ if var_token != TOKEN_NOT_MATCH and var_token.type() == TYPE_OBJECT:
+ fd._return_class = _patch_inner_class_names(var_token.plain_value(), "")
+ # if the script ihnerits we need to scan this also
+ script_to_scan = script_to_scan.get_base_script()
func is_func_coroutine(rows :PackedStringArray, index :int) -> bool:
var is_coroutine := false
- for rowIndex in range( index+1, rows.size()):
- var row := rows[rowIndex]
- is_coroutine = row.contains("await")
+ for rowIndex in range(index+1, rows.size()):
+ var input := rows[rowIndex]
+ is_coroutine = input.contains("await")
if is_coroutine:
return true
- var input := GdScriptParser.clean_up_row(row)
var token := next_token(input, 0)
+ # scan until next function
if token == TOKEN_FUNCTION_STATIC_DECLARATION or token == TOKEN_FUNCTION_DECLARATION:
break
return is_coroutine
-func _is_func_included(row :String, included_functions :PackedStringArray) -> bool:
- if included_functions.is_empty():
- return true
- for name in included_functions:
- if row.find(name) != -1:
- return true
- return false
-
-
-func parse_func_description(func_signature :String, clazz_name :String, clazz_path :PackedStringArray, line_number :int) -> GdFunctionDescriptor:
- var name := parse_func_name(func_signature)
- var return_type :int
- var return_clazz := ""
- var token := parse_return_token(func_signature)
- if token == TOKEN_NOT_MATCH:
- return_type = GdObjects.TYPE_VARIANT
- else:
- return_type = token.type()
- if token.type() == TYPE_OBJECT:
- return_clazz = _patch_inner_class_names(token.value(), clazz_name)
- # is return type an enum?
- if is_class_enum_type(return_clazz):
- return_type = GdObjects.TYPE_ENUM
-
- return GdFunctionDescriptor.new(
- name,
- line_number,
- is_virtual_func(clazz_name, clazz_path, name),
- is_static_func(func_signature),
- false,
- return_type,
- return_clazz,
- parse_arguments(func_signature)
- )
-
-
-# caches already parsed classes for virtual functions
-# key: value: a Array of virtual function names
-var _virtual_func_cache := Dictionary()
-
-func is_virtual_func(clazz_name :String, clazz_path :PackedStringArray, func_name :String) -> bool:
- if _virtual_func_cache.has(clazz_name):
- return _virtual_func_cache[clazz_name].has(func_name)
- var virtual_functions := Array()
- var method_list := GdObjects.extract_class_functions(clazz_name, clazz_path)
- for method_descriptor :Dictionary in method_list:
- var is_virtual_function :bool = method_descriptor["flags"] & METHOD_FLAG_VIRTUAL
- if is_virtual_function:
- virtual_functions.append(method_descriptor["name"])
- _virtual_func_cache[clazz_name] = virtual_functions
- return _virtual_func_cache[clazz_name].has(func_name)
-
-
-func is_static_func(func_signature :String) -> bool:
- var input := GdScriptParser.clean_up_row(func_signature)
- var token := next_token(input, 0)
- return token == TOKEN_FUNCTION_STATIC_DECLARATION
-
-
func is_inner_class(clazz_path :PackedStringArray) -> bool:
return clazz_path.size() > 1
@@ -755,39 +644,24 @@ func is_func_end(row :String) -> bool:
return row.strip_edges(false, true).ends_with(":")
-func is_class_enum_type(value :String) -> bool:
- if value == "Variant":
- return false
- # first check is given value a enum from the current class
- if _script_constants.has(value):
- return true
- # otherwise we need to determie it by reflection
- var script := GDScript.new()
- script.source_code = """
- extends Resource
-
- static func is_class_enum_type() -> bool:
- return typeof(%s) == TYPE_DICTIONARY
-
- """.dedent() % value
- script.reload()
- return script.call("is_class_enum_type")
-
-
-func _patch_inner_class_names(clazz :String, clazz_name :String) -> String:
- var base_clazz := clazz_name.split(".")[0]
+func _patch_inner_class_names(clazz :String, clazz_name :String = "") -> String:
var inner_clazz_name := clazz.split(".")[0]
if _scanned_inner_classes.has(inner_clazz_name):
- return base_clazz + "." + clazz
+ return inner_clazz_name
+ #var base_clazz := clazz_name.split(".")[0]
+ #return base_clazz + "." + clazz
if _script_constants.has(clazz):
return clazz_name + "." + clazz
return clazz
-func extract_functions(script :GDScript, clazz_name :String, clazz_path :PackedStringArray) -> Array[GdFunctionDescriptor]:
- var source_code := load_source_code(script, clazz_path)
+func _prescan_script(script: GDScript) -> void:
_script_constants = script.get_script_constant_map()
- return parse_functions(source_code, clazz_name, clazz_path)
+ for key :String in _script_constants.keys():
+ var value :Variant = _script_constants.get(key)
+ if value is GDScript:
+ @warning_ignore("return_value_discarded")
+ _scanned_inner_classes.append(key)
func parse(clazz_name :String, clazz_path :PackedStringArray) -> GdUnitResult:
@@ -795,13 +669,22 @@ func parse(clazz_name :String, clazz_path :PackedStringArray) -> GdUnitResult:
return GdUnitResult.error("Invalid script path '%s'" % clazz_path)
var is_inner_class_ := is_inner_class(clazz_path)
var script :GDScript = load(clazz_path[0])
- var function_descriptors := extract_functions(script, clazz_name, clazz_path)
+ _prescan_script(script)
+
+ if is_inner_class_:
+ var inner_class_name := clazz_path[1]
+ if _scanned_inner_classes.has(inner_class_name):
+ # do load only on inner class source code and enrich the stored script instance
+ var source_code := _load_inner_class(script, inner_class_name)
+ script = _script_constants.get(inner_class_name)
+ script.source_code = source_code
+ var function_descriptors := get_function_descriptors(script)
var gd_class := GdClassDescriptor.new(clazz_name, is_inner_class_, function_descriptors)
- # iterate over class dependencies
- script = script.get_base_script()
- while script != null:
- clazz_name = GdObjects.extract_class_name_from_class_path([script.resource_path])
- function_descriptors = extract_functions(script, clazz_name, clazz_path)
- gd_class.set_parent_clazz(GdClassDescriptor.new(clazz_name, is_inner_class_, function_descriptors))
- script = script.get_base_script()
return GdUnitResult.success(gd_class)
+
+
+func _load_inner_class(script: GDScript, inner_clazz: String) -> String:
+ var source_rows := GdScriptParser.to_unix_format(script.source_code).split("\n")
+ # extract all inner class names
+ var inner_class_code := extract_inner_class(source_rows, inner_clazz)
+ return "\n".join(inner_class_code)
diff --git a/addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd b/addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd
index f3130a07..9faf830b 100644
--- a/addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd
+++ b/addons/gdUnit4/src/core/parse/GdUnitExpressionRunner.gd
@@ -9,18 +9,66 @@ func __run_expression() -> Variant:
"""
-func execute(src_script :GDScript, expression :String) -> Variant:
+var constructor_args_regex := RegEx.create_from_string("new\\((?.*)\\)")
+
+
+func execute(src_script: GDScript, value: Variant) -> Variant:
+ if typeof(value) != TYPE_STRING:
+ return value
+
+ var expression: String = value
+ var parameter_map := src_script.get_script_constant_map()
+ for key: String in parameter_map.keys():
+ var parameter_value: Variant = parameter_map[key]
+ # check we need to construct from inner class
+ # we need to use the original class instance from the script_constant_map otherwise we run into a runtime error
+ if expression.begins_with(key + ".new") and parameter_value is GDScript:
+ var object: GDScript = parameter_value
+ var args := build_constructor_arguments(parameter_map, expression.substr(expression.find("new")))
+ if args.is_empty():
+ return object.new()
+ return object.callv("new", args)
+
var script := GDScript.new()
var resource_path := "res://addons/gdUnit4/src/Fuzzers.gd" if src_script.resource_path.is_empty() else src_script.resource_path
script.source_code = CLASS_TEMPLATE.dedent()\
.replace("${clazz_path}", resource_path)\
.replace("$expression", expression)
- script.reload(false)
- var runner :Variant = script.new()
+ #script.take_over_path(resource_path)
+ @warning_ignore("return_value_discarded")
+ script.reload(true)
+ var runner: Object = script.new()
if runner.has_method("queue_free"):
- runner.queue_free()
+ (runner as Node).queue_free()
+ @warning_ignore("unsafe_method_access")
return runner.__run_expression()
-func to_fuzzer(src_script :GDScript, expression :String) -> Fuzzer:
- return execute(src_script, expression) as Fuzzer
+func build_constructor_arguments(parameter_map: Dictionary, expression: String) -> Array[Variant]:
+ var result := constructor_args_regex.search(expression)
+ var extracted_arguments := result.get_string("args").strip_edges()
+ if extracted_arguments.is_empty():
+ return []
+ var arguments :Array = extracted_arguments.split(",")
+ return arguments.map(func(argument: String) -> Variant:
+ var value := argument.strip_edges()
+
+ # is argument an constant value
+ if parameter_map.has(value):
+ return parameter_map[value]
+ # is typed named value like Vector3.ONE
+ for type:int in GdObjects.TYPE_AS_STRING_MAPPINGS:
+ var type_as_string:String = GdObjects.TYPE_AS_STRING_MAPPINGS[type]
+ if value.begins_with(type_as_string):
+ return type_convert(value, type)
+ # is value a string
+ if value.begins_with("'") or value.begins_with('"'):
+ return value.trim_prefix("'").trim_suffix("'").trim_prefix('"').trim_suffix('"')
+ # fallback to default value converting
+ return str_to_var(value)
+ )
+
+
+func to_fuzzer(src_script: GDScript, expression: String) -> Fuzzer:
+ @warning_ignore("unsafe_cast")
+ return execute(src_script, expression) as Fuzzer
diff --git a/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd b/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd
index d67ee254..73db59e4 100644
--- a/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd
+++ b/addons/gdUnit4/src/core/parse/GdUnitTestParameterSetResolver.gd
@@ -45,10 +45,11 @@ func validate(input_value_set: Array) -> String:
for input_values :Variant in input_value_set:
var parameter_set_index := input_value_set.find(input_values)
if input_values is Array:
- var current_arg_count :int = input_values.size()
+ var arr_values: Array = input_values
+ var current_arg_count := arr_values.size()
if current_arg_count != expected_arg_count:
return "\n The parameter set at index [%d] does not match the expected input parameters!\n The test case requires [%d] input parameters, but the set contains [%d]" % [parameter_set_index, expected_arg_count, current_arg_count]
- var error := GdUnitTestParameterSetResolver.validate_parameter_types(input_arguments, input_values, parameter_set_index)
+ var error := GdUnitTestParameterSetResolver.validate_parameter_types(input_arguments, arr_values, parameter_set_index)
if not error.is_empty():
return error
else:
@@ -97,6 +98,7 @@ func build_test_case_names(test_case: _TestCase) -> PackedStringArray:
for parameter_set_index in parameter_sets.size():
var parameter_set := parameter_sets[parameter_set_index]
_static_sets_by_index[parameter_set_index] = _is_static_parameter_set(parameter_set, property_names)
+ @warning_ignore("return_value_discarded")
_test_case_names_cache.append(GdUnitTestParameterSetResolver._build_test_case_name(test_case, parameter_set, parameter_set_index))
parameter_set_index += 1
return _test_case_names_cache
@@ -121,6 +123,7 @@ func _extract_test_names_by_reflection(test_case: _TestCase) -> PackedStringArra
var parameter_sets := load_parameter_sets(test_case)
var test_case_names: PackedStringArray = []
for index in parameter_sets.size():
+ @warning_ignore("return_value_discarded")
test_case_names.append(GdUnitTestParameterSetResolver._build_test_case_name(test_case, str(parameter_sets[index]), index))
return test_case_names
@@ -149,10 +152,10 @@ func load_parameter_sets(test_case: _TestCase, do_validate := false) -> Array:
if result != OK:
push_error("Extracting test parameters failed! Script loading error: %s" % result)
return []
- var instance :Variant = script.new()
+ var instance :Object = script.new()
GdUnitTestParameterSetResolver.copy_properties(test_case.get_parent(), instance)
- instance.queue_free()
- var parameter_sets :Variant = instance.call("__extract_test_parameters")
+ (instance as Node).queue_free()
+ var parameter_sets: Array = instance.call("__extract_test_parameters")
if not do_validate:
return parameter_sets
# validate the parameter set
@@ -169,14 +172,32 @@ func load_parameter_sets(test_case: _TestCase, do_validate := false) -> Array:
""".dedent().trim_prefix("\n") % [
GdAssertMessages._error("Internal Error"),
GdAssertMessages._error("Please report this issue as a bug!")]
- test_case.get_parent().__execution_context\
- .reports()\
- .append(GdUnitReport.new().create(GdUnitReport.INTERUPTED, test_case.line_number(), error))
+ GdUnitThreadManager.get_current_context()\
+ .get_execution_context()\
+ .add_report(GdUnitReport.new().create(GdUnitReport.INTERUPTED, test_case.line_number(), error))
test_case.skip(true, error)
test_case._interupted = true
+ @warning_ignore("return_value_discarded")
+ fixure_typed_parameters(parameter_sets, _fd.args())
return parameter_sets
+func fixure_typed_parameters(parameter_sets: Array, arg_descriptors: Array[GdFunctionArgument]) -> Array:
+ for parameter_set_index in parameter_sets.size():
+ var parameter_set: Array = parameter_sets[parameter_set_index]
+ # run over all function arguments
+ for parameter_index in parameter_set.size():
+ var parameter :Variant = parameter_set[parameter_index]
+ var arg_descriptor: GdFunctionArgument = arg_descriptors[parameter_index]
+ if parameter is Array:
+ var as_array: Array = parameter
+ # we need to convert the untyped array to the expected typed version
+ if arg_descriptor.is_typed_array():
+ parameter_set[parameter_index] = Array(as_array, arg_descriptor.type_hint(), "", null)
+ return parameter_sets
+
+
+
static func copy_properties(source: Object, dest: Object) -> void:
for property in source.get_property_list():
var property_name :String = property["name"]
diff --git a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd b/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd
index 3e241f34..6fc282db 100644
--- a/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd
+++ b/addons/gdUnit4/src/core/templates/test_suite/GdUnitTestSuiteTemplate.gd
@@ -86,8 +86,10 @@ static func default_CS_template() -> String:
static func build_template(source_path: String) -> String:
- var clazz_name :String = GdObjects.to_pascal_case(GdObjects.extract_class_name(source_path).value() as String)
- return GdUnitSettings.get_setting(GdUnitSettings.TEMPLATE_TS_GD, default_GD_template())\
+ var clazz_name :String = GdObjects.to_pascal_case(GdObjects.extract_class_name(source_path).value_as_string())
+ var template: String = GdUnitSettings.get_setting(GdUnitSettings.TEMPLATE_TS_GD, default_GD_template())
+
+ return template\
.replace(TAG_TEST_SUITE_CLASS, clazz_name+"Test")\
.replace(TAG_SOURCE_RESOURCE_PATH, source_path)\
.replace(TAG_SOURCE_CLASS_NAME, clazz_name)\
diff --git a/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd b/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd
index fe326a68..8b4ae4ff 100644
--- a/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd
+++ b/addons/gdUnit4/src/core/thread/GdUnitThreadContext.gd
@@ -29,9 +29,8 @@ func dispose() -> void:
_thread = null
-func set_assert(value :GdUnitAssert) -> GdUnitThreadContext:
+func set_assert(value :GdUnitAssert) -> void:
_assert = value
- return self
func get_assert() -> GdUnitAssert:
diff --git a/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd b/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd
index ad124ced..31b10782 100644
--- a/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd
+++ b/addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd
@@ -36,6 +36,7 @@ func _run(name :String, cb :Callable) -> Variant:
var save_current_thread_id := _current_thread_id
var thread := Thread.new()
thread.set_meta("name", name)
+ @warning_ignore("return_value_discarded")
thread.start(cb)
_current_thread_id = thread.get_id() as int
_register_thread(thread, _current_thread_id)
@@ -52,8 +53,9 @@ func _register_thread(thread :Thread, thread_id :int) -> void:
func _unregister_thread(thread_id :int) -> void:
- var context := _thread_context_by_id.get(thread_id) as GdUnitThreadContext
+ var context: GdUnitThreadContext = _thread_context_by_id.get(thread_id)
if context:
+ @warning_ignore("return_value_discarded")
_thread_context_by_id.erase(thread_id)
context.dispose()
diff --git a/addons/gdUnit4/src/doubler/CallableDoubler.gd b/addons/gdUnit4/src/doubler/CallableDoubler.gd
new file mode 100644
index 00000000..14a5947d
--- /dev/null
+++ b/addons/gdUnit4/src/doubler/CallableDoubler.gd
@@ -0,0 +1,210 @@
+## The helper class to allow to double Callable
+## Is just a wrapper to the original callable with the same function signature.
+##
+## Due to interface conflicts between 'Callable' and 'Object',
+## it is not possible to stub the 'call' and 'call_deferred' methods.
+##
+## The Callable interface and the Object class have overlapping method signatures,
+## which causes conflicts when attempting to stub these methods.
+## As a result, you cannot create stubs for 'call' and 'call_deferred' methods.
+
+class_name CallableDoubler
+
+
+const doubler_script :Script = preload("res://addons/gdUnit4/src/doubler/CallableDoubler.gd")
+
+var _cb: Callable
+
+
+func _init(cb: Callable) -> void:
+ assert(cb!=null, "Invalid argument must not be null")
+ _cb = cb
+
+## --- helpers -----------------------------------------------------------------------------------------------------------------------------
+static func map_func_name(method_info: Dictionary) -> String:
+ return method_info["name"]
+
+
+## We do not want to double all functions based on Object for this class
+## Is used on SpyBuilder to excluding functions to be doubled for Callable
+static func excluded_functions() -> PackedStringArray:
+ return ClassDB.class_get_method_list("Object")\
+ .map(CallableDoubler.map_func_name)\
+ .filter(func (name: String) -> bool:
+ return !CallableDoubler.callable_functions().has(name))
+
+
+static func non_callable_functions(name: String) -> bool:
+ return ![
+ # we allow "_init", is need to construct it,
+ "excluded_functions",
+ "non_callable_functions",
+ "callable_functions",
+ "map_func_name"
+ ].has(name)
+
+
+## Returns the list of supported Callable functions
+static func callable_functions() -> PackedStringArray:
+ var supported_functions :Array = doubler_script.get_script_method_list()\
+ .map(CallableDoubler.map_func_name)\
+ .filter(CallableDoubler.non_callable_functions)
+ # We manually add these functions that we cannot/may not overwrite in this class
+ supported_functions.append_array(["call_deferred", "callv"])
+ return supported_functions
+
+
+## -----------------------------------------------------------------------------------------------------------------------------------------
+## Callable functions stubing
+## -----------------------------------------------------------------------------------------------------------------------------------------
+
+@warning_ignore("untyped_declaration")
+func bind(arg0=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg1=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg2=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg3=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg4=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg5=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg6=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg7=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg8=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg9=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE) -> Callable:
+ # save
+ var bind_values: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE)
+ _cb = _cb.bindv(bind_values)
+ return _cb
+
+
+func bindv(caller_args: Array) -> Callable:
+ _cb = _cb.bindv(caller_args)
+ return _cb
+
+
+@warning_ignore("untyped_declaration", "native_method_override", "unused_parameter")
+func call(arg0=null,
+ arg1=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg2=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg3=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg4=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg5=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg6=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg7=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg8=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg9=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE) -> Variant:
+
+ # This is a placeholder function signanture without any functionallity!
+ # It is used by the function doubler to double function signature of Callable:call()
+ # The doubled function calls direct _cb.callv() see GdUnitSpyFunctionDoubler:TEMPLATE_CALLABLE_CALL template
+ assert(false)
+ return null
+
+
+# Is not supported, see class description
+#func call_deferred(a) -> void:
+# pass
+
+
+# Is not supported, see class description
+#func callv(a) -> void:
+# pass
+
+
+
+func get_bound_arguments() -> Array:
+ return _cb.get_bound_arguments()
+
+
+func get_bound_arguments_count() -> int:
+ return _cb.get_bound_arguments_count()
+
+
+func get_method() -> StringName:
+ return _cb.get_method()
+
+
+func get_object() -> Object:
+ return _cb.get_object()
+
+
+func get_object_id() -> int:
+ return _cb.get_object_id()
+
+
+func hash() -> int:
+ return _cb.hash()
+
+
+func is_custom() -> bool:
+ return _cb.is_custom()
+
+
+func is_null() -> bool:
+ return _cb.is_null()
+
+
+func is_standard() -> bool:
+ return _cb.is_standard()
+
+
+func is_valid() -> bool:
+ return _cb.is_valid()
+
+
+@warning_ignore("untyped_declaration")
+func rpc(arg0=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg1=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg2=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg3=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg4=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg5=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg6=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg7=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg8=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg9=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE) -> void:
+
+ var args: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE)
+ match args.size():
+ 0: _cb.rpc(0)
+ 1: _cb.rpc(args[0])
+ 2: _cb.rpc(args[0], args[1])
+ 3: _cb.rpc(args[0], args[1], args[2])
+ 4: _cb.rpc(args[0], args[1], args[2], args[3])
+ 5: _cb.rpc(args[0], args[1], args[2], args[3], args[4])
+ 6: _cb.rpc(args[0], args[1], args[2], args[3], args[4], args[5])
+ 7: _cb.rpc(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
+ 8: _cb.rpc(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7])
+ 9: _cb.rpc(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8])
+ 10: _cb.rpc(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9])
+
+
+@warning_ignore("untyped_declaration")
+func rpc_id(peer_id: int,
+ arg0=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg1=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg2=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg3=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg4=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg5=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg6=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg7=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg8=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE,
+ arg9=GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE) -> void:
+
+ var args: Array = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], GdObjects.TYPE_VARARG_PLACEHOLDER_VALUE)
+ match args.size():
+ 0: _cb.rpc_id(peer_id)
+ 1: _cb.rpc_id(peer_id, args[0])
+ 2: _cb.rpc_id(peer_id, args[0], args[1])
+ 3: _cb.rpc_id(peer_id, args[0], args[1], args[2])
+ 4: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3])
+ 5: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4])
+ 6: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4], args[5])
+ 7: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4], args[5], args[6])
+ 8: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7])
+ 9: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8])
+ 10: _cb.rpc_id(peer_id, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9])
+
+
+func unbind(argcount: int) -> Callable:
+ _cb = _cb.unbind(argcount)
+ return _cb
diff --git a/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd b/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd
index 29cc62b6..124d3d4e 100644
--- a/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd
+++ b/addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd
@@ -1,4 +1,5 @@
# This class defines a value extractor by given function name and args
+class_name GdUnitFuncValueExtractor
extends GdUnitValueExtractor
var _func_names :PackedStringArray
@@ -27,13 +28,14 @@ func args() -> Array:
#
# if the value not a Object or not accesible be `func_name` the value is converted to `"n.a."`
# expecing null values
-func extract_value(value :Variant) -> Variant:
+func extract_value(value: Variant) -> Variant:
if value == null:
return null
for func_name in func_names():
if GdArrayTools.is_array_type(value):
var values := Array()
- for element :Variant in Array(value):
+ @warning_ignore("unsafe_cast")
+ for element: Variant in (value as Array):
values.append(_call_func(element, func_name))
value = values
else:
@@ -50,17 +52,19 @@ func _call_func(value :Variant, func_name :String) -> Variant:
# for array types we need to call explicit by function name, using funcref is only supported for Objects
# TODO extend to all array functions
if GdArrayTools.is_array_type(value) and func_name == "empty":
- return value.is_empty()
+ @warning_ignore("unsafe_cast")
+ return (value as Array).is_empty()
if is_instance_valid(value):
# extract from function
- if value.has_method(func_name):
- var extract := Callable(value, func_name)
+ var obj_value: Object = value
+ if obj_value.has_method(func_name):
+ var extract := Callable(obj_value, func_name)
if extract.is_valid():
- return value.call(func_name) if args().is_empty() else value.callv(func_name, args())
+ return obj_value.call(func_name) if args().is_empty() else obj_value.callv(func_name, args())
else:
# if no function exists than try to extract form parmeters
- var parameter :Variant = value.get(func_name)
+ var parameter: Variant = obj_value.get(func_name)
if parameter != null:
return parameter
# nothing found than return 'n.a.'
diff --git a/addons/gdUnit4/src/fuzzers/StringFuzzer.gd b/addons/gdUnit4/src/fuzzers/StringFuzzer.gd
index 9b13e8aa..ca165d33 100644
--- a/addons/gdUnit4/src/fuzzers/StringFuzzer.gd
+++ b/addons/gdUnit4/src/fuzzers/StringFuzzer.gd
@@ -60,5 +60,6 @@ func next_value() -> String:
var max_char := len(_charset)
var length :int = max(_min_length, randi() % _max_length)
for i in length:
+ @warning_ignore("return_value_discarded")
value.append(_charset[randi() % max_char])
return value.get_string_from_utf8()
diff --git a/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd b/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd
index 2cf07904..b5e3de3a 100644
--- a/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd
+++ b/addons/gdUnit4/src/matchers/AnyClazzArgumentMatcher.gd
@@ -12,12 +12,14 @@ func is_match(value :Variant) -> bool:
if typeof(value) != TYPE_OBJECT:
return false
if is_instance_valid(value) and GdObjects.is_script(_clazz):
- return value.get_script() == _clazz
+ @warning_ignore("unsafe_cast")
+ return (value as Object).get_script() == _clazz
return is_instance_of(value, _clazz)
func _to_string() -> String:
if (_clazz as Object).is_class("GDScriptNativeClass"):
+ @warning_ignore("unsafe_method_access")
var instance :Object = _clazz.new()
var clazz_name := instance.get_class()
if not instance is RefCounted:
diff --git a/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd b/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd
index ec62ecf6..f779bd79 100644
--- a/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd
+++ b/addons/gdUnit4/src/matchers/ChainedArgumentMatcher.gd
@@ -9,13 +9,13 @@ func _init(matchers :Array) -> void:
func is_match(arguments :Variant) -> bool:
- var arg_array := arguments as Array
- if arg_array.size() != _matchers.size():
+ var arg_array: Array = arguments
+ if arg_array == null or arg_array.size() != _matchers.size():
return false
for index in arg_array.size():
- var arg :Variant = arg_array[index]
- var matcher := _matchers[index] as GdUnitArgumentMatcher
+ var arg: Variant = arg_array[index]
+ var matcher: GdUnitArgumentMatcher = _matchers[index]
if not matcher.is_match(arg):
return false
diff --git a/addons/gdUnit4/src/mocking/GdUnitMock.gd b/addons/gdUnit4/src/mocking/GdUnitMock.gd
index bb50e5eb..c520d922 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 :Variant
+var _value: Variant
-func _init(value :Variant) -> void:
+func _init(value: Variant) -> void:
_value = value
@@ -21,9 +21,10 @@ func _init(value :Variant) -> void:
## [codeblock]
## do_return(false).on(myMock).is_selected()
## [/codeblock]
-func on(obj :Object) -> Object:
- if not GdUnitMock._is_mock_or_spy( obj, "__do_return"):
+func on(obj: Variant) -> Variant:
+ if not GdUnitMock._is_mock_or_spy(obj, "__do_return"):
return obj
+ @warning_ignore("unsafe_method_access")
return obj.__do_return(_value)
@@ -33,8 +34,12 @@ func checked(obj :Object) -> Object:
return on(obj)
-static func _is_mock_or_spy(obj :Object, func_sig :String) -> bool:
- if obj is GDScript and not obj.get_script().has_script_method(func_sig):
+static func _is_mock_or_spy(obj: Variant, func_sig: String) -> bool:
+ if obj is Object and not as_object(obj).has_method(func_sig):
push_error("Error: You try to use a non mock or spy!")
return false
return true
+
+
+static func as_object(value: Variant) -> Object:
+ return value
diff --git a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd b/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd
index b3bb41a2..bc26d657 100644
--- a/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd
+++ b/addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd
@@ -9,6 +9,7 @@ static func is_push_errors() -> bool:
return GdUnitSettings.is_report_push_errors()
+@warning_ignore("unsafe_method_access", "unsafe_cast")
static func build(clazz :Variant, mock_mode :String, debug_write := false) -> Variant:
var push_errors := is_push_errors()
if not is_mockable(clazz, push_errors):
@@ -17,7 +18,7 @@ static func build(clazz :Variant, mock_mode :String, debug_write := false) -> Va
if GdObjects.is_scene(clazz):
return mock_on_scene(clazz as PackedScene, debug_write)
elif typeof(clazz) == TYPE_STRING and clazz.ends_with(".tscn"):
- return mock_on_scene(load(clazz), debug_write)
+ return mock_on_scene(load(clazz as String) as PackedScene, debug_write)
# mocking a script
var instance := create_instance(clazz)
var mock := mock_on_script(instance, clazz, [ "get_script"], debug_write)
@@ -25,32 +26,34 @@ static func build(clazz :Variant, mock_mode :String, debug_write := false) -> Va
instance.free()
if mock == null:
return null
- var mock_instance :Variant = mock.new()
+ var mock_instance: Variant = mock.new()
mock_instance.__set_script(mock)
mock_instance.__set_singleton()
mock_instance.__set_mode(mock_mode)
return register_auto_free(mock_instance)
-static func create_instance(clazz :Variant) -> Object:
+@warning_ignore("unsafe_method_access", "unsafe_cast")
+static func create_instance(clazz: Variant) -> Object:
if typeof(clazz) == TYPE_OBJECT and (clazz as Object).is_class("GDScriptNativeClass"):
return clazz.new()
elif (clazz is GDScript) || (typeof(clazz) == TYPE_STRING and clazz.ends_with(".gd")):
- var script :GDScript = null
+ var script: GDScript = null
if clazz is GDScript:
script = clazz
else:
- script = load(clazz)
+ script = load(clazz as String)
var args := GdObjects.build_function_default_arguments(script, "_init")
return script.callv("new", args)
- elif typeof(clazz) == TYPE_STRING and ClassDB.can_instantiate(clazz):
- return ClassDB.instantiate(clazz)
+ elif typeof(clazz) == TYPE_STRING and ClassDB.can_instantiate(clazz as String):
+ return ClassDB.instantiate(clazz as String)
push_error("Can't create a mock validation instance from class: `%s`" % clazz)
return null
-static func mock_on_scene(scene :PackedScene, debug_write :bool) -> Object:
+@warning_ignore("unsafe_method_access")
+static func mock_on_scene(scene :PackedScene, debug_write :bool) -> Variant:
var push_errors := is_push_errors()
if not scene.can_instantiate():
if push_errors:
@@ -61,6 +64,7 @@ static func mock_on_scene(scene :PackedScene, debug_write :bool) -> Object:
if scene_instance.get_script() == null:
if push_errors:
push_error("Can't create a mockable instance for a scene without script '%s'" % scene.resource_path)
+ @warning_ignore("return_value_discarded")
GdUnitTools.free_instance(scene_instance)
return null
@@ -95,11 +99,13 @@ 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 = GdUnitFileAccess.create_temp_dir("mock") + "/Mock%s_%d.gd" % [clazz_name, Time.get_ticks_msec()]
+ mock.resource_name = "Mock%s_%d.gd" % [clazz_name, Time.get_ticks_msec()]
+ mock.resource_path = "%s/%s" % [GdUnitFileAccess.create_temp_dir("mock"), mock.resource_name]
if debug_write:
+ @warning_ignore("return_value_discarded")
DirAccess.remove_absolute(mock.resource_path)
+ @warning_ignore("return_value_discarded")
ResourceSaver.save(mock, mock.resource_path)
var error := mock.reload(true)
if error != OK:
@@ -131,7 +137,7 @@ static func is_mockable(clazz :Variant, push_errors :bool=false) -> bool:
return false
return true
# verify by class name checked registered classes
- var clazz_name := clazz as String
+ var clazz_name: String = clazz
if ClassDB.class_exists(clazz_name):
if Engine.has_singleton(clazz_name):
if push_errors:
diff --git a/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd b/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd
index 4d8d300f..f7464210 100644
--- a/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd
+++ b/addons/gdUnit4/src/mocking/GdUnitMockFunctionDoubler.gd
@@ -3,36 +3,36 @@ extends GdFunctionDoubler
const TEMPLATE_FUNC_WITH_RETURN_VALUE = """
- var args :Array = ["$(func_name)", $(arguments)]
+ var args__: Array = ["$(func_name)", $(arguments)]
if $(instance)__is_prepare_return_value():
- $(instance)__save_function_return_value(args)
+ $(instance)__save_function_return_value(args__)
return ${default_return_value}
if $(instance)__is_verify_interactions():
- $(instance)__verify_interactions(args)
+ $(instance)__verify_interactions(args__)
return ${default_return_value}
else:
- $(instance)__save_function_interaction(args)
+ $(instance)__save_function_interaction(args__)
- if $(instance)__do_call_real_func("$(func_name)", args):
+ if $(instance)__do_call_real_func("$(func_name)", args__):
return $(await)super($(arguments))
- return $(instance)__get_mocked_return_value_or_default(args, ${default_return_value})
+ return $(instance)__get_mocked_return_value_or_default(args__, ${default_return_value})
"""
const TEMPLATE_FUNC_WITH_RETURN_VOID = """
- var args :Array = ["$(func_name)", $(arguments)]
+ var args__: Array = ["$(func_name)", $(arguments)]
if $(instance)__is_prepare_return_value():
if $(push_errors):
push_error(\"Mocking a void function '$(func_name)() -> void:' is not allowed.\")
return
if $(instance)__is_verify_interactions():
- $(instance)__verify_interactions(args)
+ $(instance)__verify_interactions(args__)
return
else:
- $(instance)__save_function_interaction(args)
+ $(instance)__save_function_interaction(args__)
if $(instance)__do_call_real_func("$(func_name)"):
$(await)super($(arguments))
@@ -41,34 +41,34 @@ const TEMPLATE_FUNC_WITH_RETURN_VOID = """
const TEMPLATE_FUNC_VARARG_RETURN_VALUE = """
- var varargs :Array = __filter_vargs([$(varargs)])
- var args :Array = ["$(func_name)", $(arguments)] + varargs
+ var varargs__: Array = __filter_vargs([$(varargs)])
+ var args__: Array = ["$(func_name)", $(arguments)] + varargs__
if $(instance)__is_prepare_return_value():
if $(push_errors):
push_error(\"Mocking a void function '$(func_name)() -> void:' is not allowed.\")
- $(instance)__save_function_return_value(args)
+ $(instance)__save_function_return_value(args__)
return ${default_return_value}
if $(instance)__is_verify_interactions():
- $(instance)__verify_interactions(args)
+ $(instance)__verify_interactions(args__)
return ${default_return_value}
else:
- $(instance)__save_function_interaction(args)
+ $(instance)__save_function_interaction(args__)
- if $(instance)__do_call_real_func("$(func_name)", args):
- match varargs.size():
+ if $(instance)__do_call_real_func("$(func_name)", args__):
+ match varargs__.size():
0: return $(await)super($(arguments))
- 1: return $(await)super($(arguments), varargs[0])
- 2: return $(await)super($(arguments), varargs[0], varargs[1])
- 3: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2])
- 4: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3])
- 5: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4])
- 6: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5])
- 7: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6])
- 8: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7])
- 9: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7], varargs[8])
- 10: return $(await)super($(arguments), varargs[0], varargs[1], varargs[2], varargs[3], varargs[4], varargs[5], varargs[6], varargs[7], varargs[8], varargs[9])
- return __get_mocked_return_value_or_default(args, ${default_return_value})
+ 1: return $(await)super($(arguments), varargs__[0])
+ 2: return $(await)super($(arguments), varargs__[0], varargs__[1])
+ 3: return $(await)super($(arguments), varargs__[0], varargs__[1], varargs__[2])
+ 4: return $(await)super($(arguments), varargs__[0], varargs__[1], varargs__[2], varargs__[3])
+ 5: return $(await)super($(arguments), varargs__[0], varargs__[1], varargs__[2], varargs__[3], varargs__[4])
+ 6: return $(await)super($(arguments), varargs__[0], varargs__[1], varargs__[2], varargs__[3], varargs__[4], varargs__[5])
+ 7: return $(await)super($(arguments), varargs__[0], varargs__[1], varargs__[2], varargs__[3], varargs__[4], varargs__[5], varargs__[6])
+ 8: return $(await)super($(arguments), varargs__[0], varargs__[1], varargs__[2], varargs__[3], varargs__[4], varargs__[5], varargs__[6], varargs__[7])
+ 9: return $(await)super($(arguments), varargs__[0], varargs__[1], varargs__[2], varargs__[3], varargs__[4], varargs__[5], varargs__[6], varargs__[7], varargs__[8])
+ 10: return $(await)super($(arguments), varargs__[0], varargs__[1], varargs__[2], varargs__[3], varargs__[4], varargs__[5], varargs__[6], varargs__[7], varargs__[8], varargs__[9])
+ return __get_mocked_return_value_or_default(args__, ${default_return_value})
"""
@@ -77,9 +77,10 @@ func _init(push_errors :bool = false) -> void:
super._init(push_errors)
-func get_template(return_type :Variant, is_vararg :bool) -> String:
- if is_vararg:
+func get_template(fd: GdFunctionDescriptor, _is_callable: bool) -> String:
+ if fd.is_vararg():
return TEMPLATE_FUNC_VARARG_RETURN_VALUE
+ var return_type :Variant = fd.return_type()
if return_type is StringName:
return TEMPLATE_FUNC_WITH_RETURN_VALUE
return TEMPLATE_FUNC_WITH_RETURN_VOID if (return_type == TYPE_NIL or return_type == GdObjects.TYPE_VOID) else TEMPLATE_FUNC_WITH_RETURN_VALUE
diff --git a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd b/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd
index cbeb782b..de43da98 100644
--- a/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd
+++ b/addons/gdUnit4/src/mocking/GdUnitMockImpl.gd
@@ -77,6 +77,7 @@ func __save_function_return_value(__fuction_args :Array) -> void:
__prepare_return_value = false
+@warning_ignore("unsafe_method_access")
func __is_mocked_args_match(__func_args :Array, __mocked_args :Array) -> bool:
var __is_matching := false
for __index in __mocked_args.size():
@@ -98,6 +99,7 @@ func __is_mocked_args_match(__func_args :Array, __mocked_args :Array) -> bool:
return __is_matching
+@warning_ignore("unsafe_method_access")
func __get_mocked_return_value_or_default(__fuction_args :Array, __default_return_value :Variant) -> Variant:
var __func_name :String = __fuction_args[0]
if not __mocked_return_values.has(__func_name):
@@ -120,6 +122,7 @@ func __set_mode(mock_working_mode :String) -> Object:
return self
+@warning_ignore("unsafe_method_access")
func __do_call_real_func(__func_name :String, __func_args := []) -> bool:
var __is_call_real_func := __mock_working_mode == GdUnitMock.CALL_REAL_FUNC and not __excluded_methods.has(__func_name)
# do not call real funcions for mocked functions
diff --git a/addons/gdUnit4/src/monitor/ErrorLogEntry.gd b/addons/gdUnit4/src/monitor/ErrorLogEntry.gd
index 4f767bb9..5ee14878 100644
--- a/addons/gdUnit4/src/monitor/ErrorLogEntry.gd
+++ b/addons/gdUnit4/src/monitor/ErrorLogEntry.gd
@@ -14,37 +14,48 @@ const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
const PATTERN_SCRIPT_ERROR := "USER SCRIPT ERROR:"
const PATTERN_PUSH_ERROR := "USER ERROR:"
const PATTERN_PUSH_WARNING := "USER WARNING:"
+# With Godot 4.4 the pattern has changed
+const PATTERN_4x4_SCRIPT_ERROR := "SCRIPT ERROR:"
+const PATTERN_4x4_PUSH_ERROR := "ERROR:"
+const PATTERN_4x4_PUSH_WARNING := "WARNING:"
static var _regex_parse_error_line_number: RegEx
-var _type :TYPE
-var _line :int
-var _message :String
-var _details :String
+var _type: TYPE
+var _line: int
+var _message: String
+var _details: String
-func _init(type :TYPE, line :int, message :String, details :String) -> void:
+func _init(type: TYPE, line: int, message: String, details: String) -> void:
_type = type
_line = line
_message = message
_details = details
-static func extract_push_warning(records :PackedStringArray, index :int) -> ErrorLogEntry:
- return _extract(records, index, TYPE.PUSH_WARNING, PATTERN_PUSH_WARNING)
+static func is_godot4x4() -> bool:
+ return Engine.get_version_info().hex >= 0x40400
-static func extract_push_error(records :PackedStringArray, index :int) -> ErrorLogEntry:
- return _extract(records, index, TYPE.PUSH_ERROR, PATTERN_PUSH_ERROR)
+static func extract_push_warning(records: PackedStringArray, index: int) -> ErrorLogEntry:
+ var pattern := PATTERN_4x4_PUSH_WARNING if is_godot4x4() else PATTERN_PUSH_WARNING
+ return _extract(records, index, TYPE.PUSH_WARNING, pattern)
-static func extract_error(records :PackedStringArray, index :int) -> ErrorLogEntry:
- return _extract(records, index, TYPE.SCRIPT_ERROR, PATTERN_SCRIPT_ERROR)
+static func extract_push_error(records: PackedStringArray, index: int) -> ErrorLogEntry:
+ var pattern := PATTERN_4x4_PUSH_ERROR if is_godot4x4() else PATTERN_PUSH_ERROR
+ return _extract(records, index, TYPE.PUSH_ERROR, pattern)
-static func _extract(records :PackedStringArray, index :int, type :TYPE, pattern :String) -> ErrorLogEntry:
+static func extract_error(records: PackedStringArray, index: int) -> ErrorLogEntry:
+ var pattern := PATTERN_4x4_SCRIPT_ERROR if is_godot4x4() else PATTERN_SCRIPT_ERROR
+ return _extract(records, index, TYPE.SCRIPT_ERROR, pattern)
+
+
+static func _extract(records: PackedStringArray, index: int, type: TYPE, pattern: String) -> ErrorLogEntry:
var message := records[index]
- if message.contains(pattern):
+ if message.begins_with(pattern):
var error := message.replace(pattern, "").strip_edges()
var details := records[index+1].strip_edges()
var line := _parse_error_line_number(details)
@@ -52,7 +63,7 @@ static func _extract(records :PackedStringArray, index :int, type :TYPE, pattern
return null
-static func _parse_error_line_number(record :String) -> int:
+static func _parse_error_line_number(record: String) -> int:
if _regex_parse_error_line_number == null:
_regex_parse_error_line_number = GdUnitTools.to_regex("at: .*res://.*:(\\d+)")
var matches := _regex_parse_error_line_number.search(record)
diff --git a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd
index 720fe5c8..2ea2234e 100644
--- a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd
+++ b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd
@@ -1,8 +1,8 @@
class_name GodotGdErrorMonitor
extends GdUnitMonitor
-var _godot_log_file :String
-var _eof :int
+var _godot_log_file: String
+var _eof: int
var _report_enabled := false
var _entries: Array[ErrorLogEntry] = []
@@ -25,14 +25,14 @@ func stop() -> void:
func to_reports() -> Array[GdUnitReport]:
- var reports_ :Array[GdUnitReport] = []
+ var reports_: Array[GdUnitReport] = []
if _report_enabled:
reports_.assign(_entries.map(_to_report))
_entries.clear()
return reports_
-static func _to_report(errorLog :ErrorLogEntry) -> GdUnitReport:
+static func _to_report(errorLog: ErrorLogEntry) -> GdUnitReport:
var failure := "%s\n\t%s\n%s %s" % [
GdAssertMessages._error("Godot Runtime Error !"),
GdAssertMessages._colored_value(errorLog._details),
@@ -42,25 +42,26 @@ static func _to_report(errorLog :ErrorLogEntry) -> GdUnitReport:
func scan(force_collect_reports := false) -> Array[ErrorLogEntry]:
- await Engine.get_main_loop().process_frame
- await Engine.get_main_loop().physics_frame
+ await (Engine.get_main_loop() as SceneTree).process_frame
+ await (Engine.get_main_loop() as SceneTree).physics_frame
_entries.append_array(_collect_log_entries(force_collect_reports))
return _entries
-func erase_log_entry(entry :ErrorLogEntry) -> void:
+func erase_log_entry(entry: ErrorLogEntry) -> void:
_entries.erase(entry)
-func _collect_log_entries(force_collect_reports :bool) -> 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():
+ @warning_ignore("return_value_discarded")
records.append(file.get_line())
file.seek_end(0)
_eof = file.get_length()
- var log_entries :Array[ErrorLogEntry]= []
+ 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():
@@ -70,7 +71,7 @@ func _collect_log_entries(force_collect_reports :bool) -> Array[ErrorLogEntry]:
log_entries.append(ErrorLogEntry.extract_push_error(records, index))
if is_report_script_errors:
log_entries.append(ErrorLogEntry.extract_error(records, index))
- return log_entries.filter(func(value :ErrorLogEntry) -> bool: return value != null )
+ return log_entries.filter(func(value: ErrorLogEntry) -> bool: return value != null )
func _is_reporting_enabled() -> bool:
diff --git a/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd b/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd
index e828a9e8..d6c815c8 100644
--- a/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd
+++ b/addons/gdUnit4/src/mono/GdUnit4CSharpApiLoader.gd
@@ -6,6 +6,7 @@ static func instance() -> Object:
return GdUnitSingleton.instance("GdUnit4CSharpApi", func() -> Object:
if not GdUnit4CSharpApiLoader.is_mono_supported():
return null
+ @warning_ignore("unsafe_method_access")
return load("res://addons/gdUnit4/src/mono/GdUnit4CSharpApi.cs").new()
)
@@ -22,15 +23,17 @@ static func is_mono_supported() -> bool:
static func version() -> String:
if not GdUnit4CSharpApiLoader.is_mono_supported():
return "unknown"
+ @warning_ignore("unsafe_method_access")
return instance().Version()
static func create_test_suite(source_path :String, line_number :int, test_suite_path :String) -> GdUnitResult:
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
+ @warning_ignore("unsafe_method_access")
+ var result: Dictionary = instance().CreateTestSuite(source_path, line_number, test_suite_path)
if result.has("error"):
- return GdUnitResult.error(result.get("error"))
+ return GdUnitResult.error(str(result.get("error")))
return GdUnitResult.success(result)
@@ -42,6 +45,7 @@ static func is_test_suite(resource_path :String) -> bool:
if GdUnitSettings.is_report_push_errors():
push_error("Can't create test suite. Missing resource path.")
return false
+ @warning_ignore("unsafe_method_access")
return instance().IsTestSuite(resource_path)
@@ -50,12 +54,14 @@ static func parse_test_suite(source_path :String) -> Node:
if GdUnitSettings.is_report_push_errors():
push_error("Can't create test suite. No c# support found.")
return null
+ @warning_ignore("unsafe_method_access")
return instance().ParseTestSuite(source_path)
static func create_executor(listener :Node) -> RefCounted:
if not GdUnit4CSharpApiLoader.is_mono_supported():
return null
+ @warning_ignore("unsafe_method_access")
return instance().Executor(listener)
diff --git a/addons/gdUnit4/src/network/GdUnitServer.gd b/addons/gdUnit4/src/network/GdUnitServer.gd
index 7b7be090..ea638c51 100644
--- a/addons/gdUnit4/src/network/GdUnitServer.gd
+++ b/addons/gdUnit4/src/network/GdUnitServer.gd
@@ -4,6 +4,7 @@ extends Node
@onready var _server :GdUnitTcpServer = $TcpServer
+@warning_ignore("return_value_discarded")
func _ready() -> void:
var result := _server.start()
if result.is_error():
diff --git a/addons/gdUnit4/src/network/GdUnitTcpClient.gd b/addons/gdUnit4/src/network/GdUnitTcpClient.gd
index 5cf8b053..953c9f51 100644
--- a/addons/gdUnit4/src/network/GdUnitTcpClient.gd
+++ b/addons/gdUnit4/src/network/GdUnitTcpClient.gd
@@ -51,6 +51,7 @@ func _process(_delta :float) -> void:
set_process(false)
# wait until client is connected to server
for retry in 10:
+ @warning_ignore("return_value_discarded")
_stream.poll()
console("wait to connect ..")
if _stream.get_status() == StreamPeerTCP.STATUS_CONNECTING:
@@ -71,7 +72,7 @@ func _process(_delta :float) -> void:
await get_tree().create_timer(0.500).timeout
rpc_ = rpc_receive()
set_process(true)
- _client_id = rpc_.client_id()
+ _client_id = (rpc_ as RPCClientConnect).client_id()
console("Connected to Server: %d" % _client_id)
connection_succeeded.emit("Connect to TCP Server %s:%d success." % [_host, _port])
_connected = true
@@ -98,6 +99,7 @@ func process_rpc() -> void:
func rpc_send(p_rpc :RPC) -> void:
if _stream != null:
var data := GdUnitServerConstants.JSON_RESPONSE_DELIMITER + p_rpc.serialize() + GdUnitServerConstants.JSON_RESPONSE_DELIMITER
+ @warning_ignore("return_value_discarded")
_stream.put_data(data.to_utf8_buffer())
@@ -106,7 +108,7 @@ func rpc_receive() -> RPC:
while _stream.get_available_bytes() > 0:
var available_bytes := _stream.get_available_bytes()
var data := _stream.get_data(available_bytes)
- var received_data := data[1] as PackedByteArray
+ var received_data: PackedByteArray = data[1]
# data send by Godot has this magic header of 12 bytes
var header := Array(received_data.slice(0, 4))
if header == [0, 0, 0, 124]:
diff --git a/addons/gdUnit4/src/network/GdUnitTcpServer.gd b/addons/gdUnit4/src/network/GdUnitTcpServer.gd
index e27fe18b..05ccf3d0 100644
--- a/addons/gdUnit4/src/network/GdUnitTcpServer.gd
+++ b/addons/gdUnit4/src/network/GdUnitTcpServer.gd
@@ -4,7 +4,8 @@ extends Node
signal client_connected(client_id :int)
signal client_disconnected(client_id :int)
-signal rpc_data(rpc_data :RPC)
+@warning_ignore("unused_signal")
+signal rpc_data(rpc_data: RPC)
var _server :TCPServer
@@ -17,6 +18,7 @@ class TcpConnection extends Node:
var _readBuffer :String = ""
+ @warning_ignore("unsafe_method_access")
func _init(p_server :Variant) -> void:
assert(p_server is TCPServer)
_stream = p_server.take_connection()
@@ -31,6 +33,7 @@ class TcpConnection extends Node:
func close() -> void:
if _stream != null:
+ @warning_ignore("unsafe_method_access")
_stream.disconnect_from_host()
_readBuffer = ""
_stream = null
@@ -46,15 +49,18 @@ class TcpConnection extends Node:
func rpc_send(p_rpc: RPC) -> void:
+ @warning_ignore("unsafe_method_access")
_stream.put_var(p_rpc.serialize(), true)
func _process(_delta: float) -> void:
+ @warning_ignore("unsafe_method_access")
if _stream == null or _stream.get_status() != StreamPeerTCP.STATUS_CONNECTED:
return
receive_packages()
+ @warning_ignore("unsafe_method_access")
func receive_packages() -> void:
var available_bytes :int = _stream.get_available_bytes()
if available_bytes > 0:
@@ -64,7 +70,7 @@ class TcpConnection extends Node:
push_error("Error getting data from stream: %s " % partial_data[0])
return
else:
- var received_data := partial_data[1] as PackedByteArray
+ var received_data: PackedByteArray = partial_data[1]
for package in _read_next_data_packages(received_data):
var rpc_ := RPC.deserialize(package)
if rpc_ is RPCClientDisconnect:
@@ -95,6 +101,7 @@ class TcpConnection extends Node:
pass
+@warning_ignore("return_value_discarded")
func _ready() -> void:
_server = TCPServer.new()
client_connected.connect(_on_client_connected)
@@ -130,6 +137,7 @@ func stop() -> void:
_server.stop()
for connection in get_children():
if connection is TcpConnection:
+ @warning_ignore("unsafe_method_access")
connection.close()
remove_child(connection)
_server = null
@@ -151,6 +159,7 @@ func _on_client_connected(client_id: int) -> void:
console("Client connected %d" % client_id)
+@warning_ignore("unsafe_method_access")
func _on_client_disconnected(client_id: int) -> void:
for connection in get_children():
if connection is TcpConnection and connection.id() == client_id:
diff --git a/addons/gdUnit4/src/network/rpc/RPC.gd b/addons/gdUnit4/src/network/rpc/RPC.gd
index 190b05ab..c3ddaaf1 100644
--- a/addons/gdUnit4/src/network/rpc/RPC.gd
+++ b/addons/gdUnit4/src/network/rpc/RPC.gd
@@ -13,7 +13,7 @@ static func deserialize(json_value :String) -> Object:
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 :Dictionary = json.get_data()
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/dtos/GdUnitResourceDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd
index 9152c8d2..9cfd1fe1 100644
--- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd
+++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitResourceDto.gd
@@ -8,6 +8,7 @@ var _path :String
func serialize(resource :Node) -> Dictionary:
var serialized := Dictionary()
serialized["name"] = resource.get_name()
+ @warning_ignore("unsafe_method_access")
serialized["resource_path"] = resource.ResourcePath()
return serialized
diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd
index 26f5dda5..cfb093e0 100644
--- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd
+++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestCaseDto.gd
@@ -2,15 +2,23 @@ class_name GdUnitTestCaseDto
extends GdUnitResourceDto
var _line_number :int = -1
+var _script_path: String
var _test_case_names :PackedStringArray = []
+@warning_ignore("unsafe_method_access")
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()
else:
serialized["line_number"] = test_case.get("LineNumber")
+ if test_case.has_method("script_path"):
+ serialized["script_path"] = test_case.script_path()
+ else:
+ # TODO 'script_path' needs to be implement in c# the the
+ # serialized["script_path"] = test_case.get("ScriptPath")
+ serialized["script_path"] = serialized["resource_path"]
if test_case.has_method("test_case_names"):
serialized["test_case_names"] = test_case.test_case_names()
elif test_case.has_method("TestCaseNames"):
@@ -18,9 +26,11 @@ func serialize(test_case :Node) -> Dictionary:
return serialized
-func deserialize(data :Dictionary) -> GdUnitResourceDto:
+func deserialize(data :Dictionary) -> GdUnitTestCaseDto:
+ @warning_ignore("return_value_discarded")
super.deserialize(data)
_line_number = data.get("line_number", -1)
+ _script_path = data.get("script_path", data.get("resource_path", ""))
_test_case_names = data.get("test_case_names", [])
return self
@@ -29,5 +39,9 @@ func line_number() -> int:
return _line_number
+func script_path() -> String:
+ return _script_path
+
+
func test_case_names() -> PackedStringArray:
return _test_case_names
diff --git a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd
index edbae381..c5d4501c 100644
--- a/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd
+++ b/addons/gdUnit4/src/network/rpc/dtos/GdUnitTestSuiteDto.gd
@@ -21,6 +21,7 @@ func serialize(test_suite :Node) -> Dictionary:
func deserialize(data :Dictionary) -> GdUnitResourceDto:
+ @warning_ignore("return_value_discarded")
super.deserialize(data)
var test_cases_ :Array = data.get("test_cases", [])
for test_case :Dictionary in test_cases_:
diff --git a/addons/gdUnit4/src/report/GdUnitByPathReport.gd b/addons/gdUnit4/src/report/GdUnitByPathReport.gd
index d08600e9..d857ad64 100644
--- a/addons/gdUnit4/src/report/GdUnitByPathReport.gd
+++ b/addons/gdUnit4/src/report/GdUnitByPathReport.gd
@@ -2,8 +2,8 @@ class_name GdUnitByPathReport
extends GdUnitReportSummary
-func _init(path :String, report_summaries :Array[GdUnitReportSummary]) -> void:
- _resource_path = path
+func _init(p_path :String, report_summaries :Array[GdUnitReportSummary]) -> void:
+ _resource_path = p_path
_reports = report_summaries
@@ -11,7 +11,7 @@ func _init(path :String, report_summaries :Array[GdUnitReportSummary]) -> void:
static func sort_reports_by_path(report_summaries :Array[GdUnitReportSummary]) -> Dictionary:
var by_path := Dictionary()
for report in report_summaries:
- var suite_path :String = report.path()
+ var suite_path :String = ProjectSettings.localize_path(report.path())
var suite_report :Array[GdUnitReportSummary] = by_path.get(suite_path, [] as Array[GdUnitReportSummary])
suite_report.append(report)
by_path[suite_path] = suite_report
@@ -27,6 +27,7 @@ func create_record(report_link :String) -> String:
func write(report_dir :String) -> String:
+ calculate_summary()
var template := GdUnitHtmlPatterns.load_template("res://addons/gdUnit4/src/report/template/folder_report.html")
var path_report := GdUnitHtmlPatterns.build(template, self, "")
path_report = apply_testsuite_reports(report_dir, path_report, _reports)
@@ -34,6 +35,7 @@ func write(report_dir :String) -> String:
var output_path := "%s/path/%s.html" % [report_dir, path().replace("/", ".")]
var dir := output_path.get_base_dir()
if not DirAccess.dir_exists_absolute(dir):
+ @warning_ignore("return_value_discarded")
DirAccess.make_dir_recursive_absolute(dir)
FileAccess.open(output_path, FileAccess.WRITE).store_string(path_report)
return output_path
@@ -41,9 +43,18 @@ func write(report_dir :String) -> String:
func apply_testsuite_reports(report_dir :String, template :String, test_suite_reports :Array[GdUnitReportSummary]) -> String:
var table_records := PackedStringArray()
- for report in test_suite_reports:
- if report is GdUnitTestSuiteReport:
- var test_suite_report := report as GdUnitTestSuiteReport
- var report_link := test_suite_report.output_path(report_dir).replace(report_dir, "..")
- table_records.append(test_suite_report.create_record(report_link))
+ for report:GdUnitTestSuiteReport in test_suite_reports:
+ var report_link := report.output_path(report_dir).replace(report_dir, "..")
+ @warning_ignore("return_value_discarded")
+ table_records.append(report.create_record(report_link))
return template.replace(GdUnitHtmlPatterns.TABLE_BY_TESTSUITES, "\n".join(table_records))
+
+
+func calculate_summary() -> void:
+ for report:GdUnitTestSuiteReport in get_reports():
+ _error_count += report.error_count()
+ _failure_count += report.failure_count()
+ _orphan_count += report.orphan_count()
+ _skipped_count += report.skipped_count()
+ _flaky_count += report.flaky_count()
+ _duration += report.duration()
diff --git a/addons/gdUnit4/src/report/GdUnitHtmlPatterns.gd b/addons/gdUnit4/src/report/GdUnitHtmlPatterns.gd
index 1d9cf860..722c528f 100644
--- a/addons/gdUnit4/src/report/GdUnitHtmlPatterns.gd
+++ b/addons/gdUnit4/src/report/GdUnitHtmlPatterns.gd
@@ -2,48 +2,77 @@ class_name GdUnitHtmlPatterns
extends RefCounted
const TABLE_RECORD_TESTSUITE = """
-
- ${testsuite_name} |
+
+ ${testsuite_name} |
+ ${report_state_label} |
${test_count} |
${skipped_count} |
+ ${flaky_count} |
${failure_count} |
${orphan_count} |
${duration} |
- ${success_percent} |
+
+
+ |
"""
const TABLE_RECORD_PATH = """
-
+
${path} |
+ ${report_state_label} |
${test_count} |
${skipped_count} |
+ ${flaky_count} |
${failure_count} |
${orphan_count} |
${duration} |
- ${success_percent} |
+
+
+ |
"""
const TABLE_REPORT_TESTSUITE = """
-
- TestSuite hooks |
+
+ TestSuite hooks |
n/a |
${orphan_count} |
${duration} |
- ${failure-report} |
+
+
+${failure-report}
+
+ |
"""
const TABLE_RECORD_TESTCASE = """
-
- ${testcase_name} |
+
+ ${testcase_name} |
+ ${report_state_label} |
${skipped_count} |
${orphan_count} |
${duration} |
- ${failure-report} |
+
+
+${failure-report}
+
+ |
"""
@@ -53,16 +82,25 @@ const TABLE_BY_TESTCASES = "${report_table_tests}"
# the report state success, error, warning
const REPORT_STATE = "${report_state}"
+const REPORT_STATE_LABEL = "${report_state_label}"
const PATH = "${path}"
+const RESOURCE_PATH = "${resource_path}"
const TESTSUITE_COUNT = "${suite_count}"
const TESTCASE_COUNT = "${test_count}"
const FAILURE_COUNT = "${failure_count}"
+const FLAKY_COUNT = "${flaky_count}"
const SKIPPED_COUNT = "${skipped_count}"
const ORPHAN_COUNT = "${orphan_count}"
const DURATION = "${duration}"
const FAILURE_REPORT = "${failure-report}"
const SUCCESS_PERCENT = "${success_percent}"
+const QUICK_STATE_PASSED = "${passed-percent}"
+const QUICK_STATE_FLAKY = "${flaky-percent}"
+const QUICK_STATE_ERROR = "${error-percent}"
+const QUICK_STATE_FAILED = "${failed-percent}"
+const QUICK_STATE_WARNING = "${warning-percent}"
+
const TESTSUITE_NAME = "${testsuite_name}"
const TESTCASE_NAME = "${testcase_name}"
const REPORT_LINK = "${report_link}"
@@ -74,21 +112,47 @@ static func current_date() -> String:
return Time.get_datetime_string_from_system(true, true)
-static func build(template :String, report :GdUnitReportSummary, report_link :String) -> String:
+static func build(template: String, report: GdUnitReportSummary, report_link: String) -> String:
return template\
- .replace(PATH, report.path())\
- .replace(TESTSUITE_NAME, report.name())\
+ .replace(PATH, get_report_path(report))\
+ .replace(BREADCRUMP_PATH_LINK, get_path_as_link(report))\
+ .replace(RESOURCE_PATH, report.resource_path())\
+ .replace(TESTSUITE_NAME, report.name_html_encoded())\
.replace(TESTSUITE_COUNT, str(report.suite_count()))\
.replace(TESTCASE_COUNT, str(report.test_count()))\
.replace(FAILURE_COUNT, str(report.error_count() + report.failure_count()))\
+ .replace(FLAKY_COUNT, str(report.flaky_count()))\
.replace(SKIPPED_COUNT, str(report.skipped_count()))\
.replace(ORPHAN_COUNT, str(report.orphan_count()))\
.replace(DURATION, LocalTime.elapsed(report.duration()))\
.replace(SUCCESS_PERCENT, report.calculate_succes_rate(report.test_count(), report.error_count(), report.failure_count()))\
- .replace(REPORT_STATE, report.report_state())\
+ .replace(REPORT_STATE, report.report_state().to_lower())\
+ .replace(REPORT_STATE_LABEL, report.report_state())\
+ .replace(QUICK_STATE_PASSED, calculate_percentage(report.test_count(), report.success_count()))\
+ .replace(QUICK_STATE_FLAKY, calculate_percentage(report.test_count(), report.flaky_count()))\
+ .replace(QUICK_STATE_ERROR, calculate_percentage(report.test_count(), report.error_count()))\
+ .replace(QUICK_STATE_FAILED, calculate_percentage(report.test_count(), report.failure_count()))\
+ .replace(QUICK_STATE_WARNING, calculate_percentage(report.test_count(), 0))\
.replace(REPORT_LINK, report_link)\
.replace(BUILD_DATE, current_date())
static func load_template(template_name :String) -> String:
return FileAccess.open(template_name, FileAccess.READ).get_as_text()
+
+
+static func get_path_as_link(report: GdUnitReportSummary) -> String:
+ return "../path/%s.html" % report.path().replace("/", ".")
+
+
+static func get_report_path(report: GdUnitReportSummary) -> String:
+ var path := report.path()
+ if path.is_empty():
+ return "/"
+ return path
+
+
+static func calculate_percentage(p_test_count: int, count: int) -> String:
+ if count <= 0:
+ return "0%"
+ return "%d" % (( 0 if count < 0 else count) * 100.0 / p_test_count) + "%"
diff --git a/addons/gdUnit4/src/report/GdUnitHtmlReport.gd b/addons/gdUnit4/src/report/GdUnitHtmlReport.gd
index fda9833e..94bee48f 100644
--- a/addons/gdUnit4/src/report/GdUnitHtmlReport.gd
+++ b/addons/gdUnit4/src/report/GdUnitHtmlReport.gd
@@ -7,50 +7,97 @@ var _report_path :String
var _iteration :int
-func _init(report_path :String) -> void:
- _iteration = GdUnitFileAccess.find_last_path_index(report_path, REPORT_DIR_PREFIX) + 1
+func _init(report_path :String, max_reports: int) -> void:
+ if max_reports > 1:
+ _iteration = GdUnitFileAccess.find_last_path_index(report_path, REPORT_DIR_PREFIX) + 1
+ else:
+ _iteration = 1
_report_path = "%s/%s%d" % [report_path, REPORT_DIR_PREFIX, _iteration]
+ @warning_ignore("return_value_discarded")
DirAccess.make_dir_recursive_absolute(_report_path)
-func add_testsuite_report(suite_report :GdUnitTestSuiteReport) -> void:
- _reports.append(suite_report)
+func add_testsuite_report(p_resource_path: String, p_suite_name: String, p_test_count: int) -> void:
+ _reports.append(GdUnitTestSuiteReport.new(p_resource_path, p_suite_name, p_test_count))
@warning_ignore("shadowed_variable")
-func add_testcase_report(resource_path :String, suite_report :GdUnitTestCaseReport) -> void:
- for report in _reports:
+func add_testcase(resource_path :String, suite_name :String, test_name: String) -> void:
+ for report:GdUnitTestSuiteReport in _reports:
if report.resource_path() == resource_path:
- report.add_report(suite_report)
-
-
-@warning_ignore("shadowed_variable")
-func update_test_suite_report(
- resource_path :String,
- duration :int,
- _is_error :bool,
- is_failed: bool,
- _is_warning :bool,
- _is_skipped :bool,
- skipped_count :int,
- failed_count :int,
- orphan_count :int,
- reports :Array = []) -> void:
-
- for report in _reports:
- if report.resource_path() == resource_path:
- report.set_duration(duration)
- report.set_failed(is_failed, failed_count)
- report.set_skipped(skipped_count)
- report.set_orphans(orphan_count)
- report.set_reports(reports)
-
-
-@warning_ignore("shadowed_variable")
-func update_testcase_report(resource_path :String, test_report :GdUnitTestCaseReport) -> void:
- for report in _reports:
- if report.resource_path() == resource_path:
- report.update(test_report)
+ var test_report := GdUnitTestCaseReport.new(resource_path, suite_name, test_name)
+ report.add_or_create_test_report(test_report)
+
+
+func add_testsuite_reports(
+ p_resource_path :String,
+ p_error_count :int,
+ p_failure_count :int,
+ p_orphan_count :int,
+ p_duration :int,
+ p_reports :Array = []) -> void:
+
+ for report:GdUnitTestSuiteReport in _reports:
+ if report.resource_path() == p_resource_path:
+ report.set_reports(p_reports)
+ update_summary_counters(p_error_count, p_failure_count, p_orphan_count, 0, 0, p_duration)
+
+
+func add_testcase_reports(
+ p_resource_path: String,
+ p_test_name: String,
+ p_reports: Array[GdUnitReport]) -> void:
+
+ for report:GdUnitTestSuiteReport in _reports:
+ if report.resource_path() == p_resource_path:
+ report.add_testcase_reports(p_test_name, p_reports)
+
+
+func update_testsuite_counters(
+ p_resource_path :String,
+ p_error_count: int,
+ p_failure_count: int,
+ p_orphan_count: int,
+ p_is_skipped: bool,
+ p_is_flaky: bool,
+ p_duration: int) -> void:
+
+ for report:GdUnitTestSuiteReport in _reports:
+ if report.resource_path() == p_resource_path:
+ report.update_testsuite_counters(p_error_count, p_failure_count, p_orphan_count, p_is_skipped, p_is_flaky, p_duration)
+ update_summary_counters(p_error_count, p_failure_count, p_orphan_count, p_is_skipped, p_is_flaky, 0)
+
+
+func set_testcase_counters(
+ p_resource_path: String,
+ p_test_name: String,
+ p_error_count: int,
+ p_failure_count: int,
+ p_orphan_count: int,
+ p_is_skipped: bool,
+ p_is_flaky: bool,
+ p_duration: int) -> void:
+
+ for report:GdUnitTestSuiteReport in _reports:
+ if report.resource_path() == p_resource_path:
+ report.set_testcase_counters(p_test_name, p_error_count, p_failure_count, p_orphan_count,
+ p_is_skipped, p_is_flaky, p_duration)
+
+
+func update_summary_counters(
+ p_error_count: int,
+ p_failure_count: int,
+ p_orphan_count: int,
+ p_is_skipped: bool,
+ p_is_flaky: bool,
+ p_duration: int) -> void:
+
+ _error_count += p_error_count
+ _failure_count += p_failure_count
+ _orphan_count += p_orphan_count
+ _skipped_count += p_is_skipped as int
+ _flaky_count += p_is_flaky as int
+ _duration += p_duration
func write() -> String:
@@ -61,6 +108,7 @@ func write() -> String:
# write report
var index_file := "%s/index.html" % _report_path
FileAccess.open(index_file, FileAccess.WRITE).store_string(to_write)
+ @warning_ignore("return_value_discarded")
GdUnitFileAccess.copy_directory("res://addons/gdUnit4/src/report/template/css/", _report_path + "/css")
return index_file
@@ -77,17 +125,20 @@ func apply_path_reports(report_dir :String, template :String, report_summaries :
paths.append_array(path_report_mapping.keys())
paths.sort()
for report_path in paths:
- var report := GdUnitByPathReport.new(report_path, path_report_mapping.get(report_path))
+ var reports: Array[GdUnitReportSummary] = path_report_mapping.get(report_path)
+ var report := GdUnitByPathReport.new(report_path, reports)
var report_link :String = report.write(report_dir).replace(report_dir, ".")
+ @warning_ignore("return_value_discarded")
table_records.append(report.create_record(report_link))
return template.replace(GdUnitHtmlPatterns.TABLE_BY_PATHS, "\n".join(table_records))
-func apply_testsuite_reports(report_dir :String, template :String, test_suite_reports :Array[GdUnitReportSummary]) -> String:
+func apply_testsuite_reports(report_dir: String, template: String, test_suite_reports: Array[GdUnitReportSummary]) -> String:
var table_records := PackedStringArray()
- for report in test_suite_reports:
+ for report: GdUnitTestSuiteReport in test_suite_reports:
var report_link :String = report.write(report_dir).replace(report_dir, ".")
- table_records.append(report.create_record(report_link))
+ @warning_ignore("return_value_discarded")
+ table_records.append(report.create_record(report_link) as String)
return template.replace(GdUnitHtmlPatterns.TABLE_BY_TESTSUITES, "\n".join(table_records))
diff --git a/addons/gdUnit4/src/report/GdUnitReportSummary.gd b/addons/gdUnit4/src/report/GdUnitReportSummary.gd
index 41d423e7..e702491e 100644
--- a/addons/gdUnit4/src/report/GdUnitReportSummary.gd
+++ b/addons/gdUnit4/src/report/GdUnitReportSummary.gd
@@ -15,11 +15,15 @@ var _failure_count := 0
var _error_count := 0
var _orphan_count := 0
var _skipped_count := 0
+var _flaky_count := 0
var _duration := 0
var _reports :Array[GdUnitReportSummary] = []
-
func name() -> String:
+ return _name
+
+
+func name_html_encoded() -> String:
return html_encode(_name)
@@ -54,42 +58,35 @@ func test_executed_count() -> int:
return test_count() - skipped_count()
+func success_count() -> int:
+ return test_count() - error_count() - failure_count() - flaky_count()
+
+
func error_count() -> int:
- var count := _error_count
- for report in _reports:
- count += report.error_count()
- return count
+ return _error_count
func failure_count() -> int:
- var count := _failure_count
- for report in _reports:
- count += report.failure_count()
- return count
+ return _failure_count
func skipped_count() -> int:
- var count := _skipped_count
- for report in _reports:
- count += report.skipped_count()
- return count
+ return _skipped_count
+
+
+func flaky_count() -> int:
+ return _flaky_count
func orphan_count() -> int:
- var count := _orphan_count
- for report in _reports:
- count += report.orphan_count()
- return count
+ return _orphan_count
func duration() -> int:
- var count := _duration
- for report in _reports:
- count += report.duration()
- return count
+ return _duration
-func reports() -> Array:
+func get_reports() -> Array:
return _reports
@@ -98,21 +95,23 @@ func add_report(report :GdUnitReportSummary) -> void:
func report_state() -> String:
- return calculate_state(error_count(), failure_count(), orphan_count())
+ return calculate_state(error_count(), failure_count(), orphan_count(), flaky_count())
func succes_rate() -> String:
return calculate_succes_rate(test_count(), error_count(), failure_count())
-func calculate_state(p_error_count :int, p_failure_count :int, p_orphan_count :int) -> String:
+func calculate_state(p_error_count :int, p_failure_count :int, p_orphan_count :int, p_flaky_count: int) -> String:
if p_error_count > 0:
- return "error"
+ return "ERROR"
if p_failure_count > 0:
- return "failure"
+ return "FAILED"
+ if p_flaky_count > 0:
+ return "FLAKY"
if p_orphan_count > 0:
- return "warning"
- return "success"
+ return "WARNING"
+ return "PASSED"
func calculate_succes_rate(p_test_count :int, p_error_count :int, p_failure_count :int) -> String:
@@ -128,16 +127,12 @@ func create_summary(_report_dir :String) -> String:
return ""
-func html_encode(value :String) -> String:
- for key in CHARACTERS_TO_ENCODE.keys() as Array[String]:
- value =value.replace(key, CHARACTERS_TO_ENCODE[key])
+func html_encode(value: String) -> String:
+ for key: String in CHARACTERS_TO_ENCODE.keys():
+ @warning_ignore("unsafe_cast")
+ value = value.replace(key, CHARACTERS_TO_ENCODE[key] as String)
return value
func convert_rtf_to_html(bbcode :String) -> String:
- var as_text: = GdUnitTools.richtext_normalize(bbcode)
- var converted := PackedStringArray()
- var lines := as_text.split("\n")
- for line in lines:
- converted.append("%s
" % line)
- return "\n".join(converted)
+ return GdUnitTools.richtext_normalize(bbcode)
diff --git a/addons/gdUnit4/src/report/GdUnitTestCaseReport.gd b/addons/gdUnit4/src/report/GdUnitTestCaseReport.gd
index deaf7908..5eec799e 100644
--- a/addons/gdUnit4/src/report/GdUnitTestCaseReport.gd
+++ b/addons/gdUnit4/src/report/GdUnitTestCaseReport.gd
@@ -6,26 +6,10 @@ var _failure_reports :Array[GdUnitReport]
@warning_ignore("shadowed_variable")
-func _init(
- p_resource_path :String,
- p_suite_name :String,
- test_name :String,
- is_error := false,
- _is_failed := false,
- failed_count :int = 0,
- orphan_count :int = 0,
- is_skipped := false,
- failure_reports :Array[GdUnitReport] = [],
- p_duration :int = 0) -> void:
+func _init(p_resource_path: String, p_suite_name: String, p_test_name: String) -> void:
_resource_path = p_resource_path
_suite_name = p_suite_name
- _name = test_name
- _error_count = is_error
- _failure_count = failed_count
- _orphan_count = orphan_count
- _skipped_count = is_skipped
- _failure_reports = failure_reports
- _duration = p_duration
+ _name = p_test_name
func suite_name() -> String:
@@ -34,14 +18,15 @@ func suite_name() -> String:
func failure_report() -> String:
var html_report := ""
- for report in _failure_reports:
+ for report in get_test_reports():
html_report += convert_rtf_to_html(str(report))
return html_report
func create_record(_report_dir :String) -> String:
return GdUnitHtmlPatterns.TABLE_RECORD_TESTCASE\
- .replace(GdUnitHtmlPatterns.REPORT_STATE, report_state())\
+ .replace(GdUnitHtmlPatterns.REPORT_STATE, report_state().to_lower())\
+ .replace(GdUnitHtmlPatterns.REPORT_STATE_LABEL, report_state())\
.replace(GdUnitHtmlPatterns.TESTCASE_NAME, name())\
.replace(GdUnitHtmlPatterns.SKIPPED_COUNT, str(skipped_count()))\
.replace(GdUnitHtmlPatterns.ORPHAN_COUNT, str(orphan_count()))\
@@ -49,10 +34,19 @@ func create_record(_report_dir :String) -> String:
.replace(GdUnitHtmlPatterns.FAILURE_REPORT, failure_report())
-func update(report :GdUnitTestCaseReport) -> void:
- _error_count += report.error_count()
- _failure_count += report.failure_count()
- _orphan_count += report.orphan_count()
- _skipped_count += report.skipped_count()
- _failure_reports += report._failure_reports
- _duration += report.duration()
+func add_testcase_reports(reports: Array[GdUnitReport]) -> void:
+ _failure_reports.append_array(reports)
+
+
+func set_testcase_counters(p_error_count: int, p_failure_count: int, p_orphan_count: int,
+ p_is_skipped: bool, p_is_flaky: bool, p_duration: int) -> void:
+ _error_count = p_error_count
+ _failure_count = p_failure_count
+ _orphan_count = p_orphan_count
+ _skipped_count = p_is_skipped
+ _flaky_count = p_is_flaky as int
+ _duration = p_duration
+
+
+func get_test_reports() -> Array[GdUnitReport]:
+ return _failure_reports
diff --git a/addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd b/addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd
index fa07c8de..4a62087b 100644
--- a/addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd
+++ b/addons/gdUnit4/src/report/GdUnitTestSuiteReport.gd
@@ -1,16 +1,15 @@
class_name GdUnitTestSuiteReport
extends GdUnitReportSummary
-var _time_stamp :int
-var _failure_reports :Array[GdUnitReport] = []
+var _time_stamp: int
+var _failure_reports: Array[GdUnitReport] = []
-@warning_ignore("shadowed_variable")
-func _init(resource_path :String, name :String, test_count :int) -> void:
- _resource_path = resource_path
- _name = name
+func _init(p_resource_path: String, p_name: String, p_test_count: int) -> void:
+ _resource_path = p_resource_path
+ _name = p_name
+ _test_count = p_test_count
_time_stamp = Time.get_unix_time_from_system() as int
- _test_count = test_count
func create_record(report_link :String) -> String:
@@ -21,10 +20,6 @@ func output_path(report_dir :String) -> String:
return "%s/test_suites/%s.%s.html" % [report_dir, path().replace("/", "."), name()]
-func path_as_link() -> String:
- return "../path/%s.html" % path().replace("/", ".")
-
-
func failure_report() -> String:
var html_report := ""
for report in _failure_reports:
@@ -34,7 +29,8 @@ func failure_report() -> String:
func test_suite_failure_report() -> String:
return GdUnitHtmlPatterns.TABLE_REPORT_TESTSUITE\
- .replace(GdUnitHtmlPatterns.REPORT_STATE, report_state())\
+ .replace(GdUnitHtmlPatterns.REPORT_STATE, report_state().to_lower())\
+ .replace(GdUnitHtmlPatterns.REPORT_STATE_LABEL, report_state())\
.replace(GdUnitHtmlPatterns.ORPHAN_COUNT, str(orphan_count()))\
.replace(GdUnitHtmlPatterns.DURATION, LocalTime.elapsed(_duration))\
.replace(GdUnitHtmlPatterns.FAILURE_REPORT, failure_report())
@@ -42,20 +38,22 @@ func test_suite_failure_report() -> String:
func write(report_dir :String) -> String:
var template := GdUnitHtmlPatterns.load_template("res://addons/gdUnit4/src/report/template/suite_report.html")
- template = GdUnitHtmlPatterns.build(template, self, "")\
- .replace(GdUnitHtmlPatterns.BREADCRUMP_PATH_LINK, path_as_link())
+ template = GdUnitHtmlPatterns.build(template, self, "")
var report_output_path := output_path(report_dir)
var test_report_table := PackedStringArray()
if not _failure_reports.is_empty():
+ @warning_ignore("return_value_discarded")
test_report_table.append(test_suite_failure_report())
- for test_report in _reports:
+ for test_report: GdUnitTestCaseReport in _reports:
+ @warning_ignore("return_value_discarded")
test_report_table.append(test_report.create_record(report_output_path))
template = template.replace(GdUnitHtmlPatterns.TABLE_BY_TESTCASES, "\n".join(test_report_table))
var dir := report_output_path.get_base_dir()
if not DirAccess.dir_exists_absolute(dir):
+ @warning_ignore("return_value_discarded")
DirAccess.make_dir_recursive_absolute(dir)
FileAccess.open(report_output_path, FileAccess.WRITE).store_string(template)
return report_output_path
@@ -74,23 +72,53 @@ func duration() -> int:
func set_skipped(skipped :int) -> void:
- _skipped_count = skipped
+ _skipped_count += skipped
func set_orphans(orphans :int) -> void:
_orphan_count = orphans
-func set_failed(failed :bool, count :int) -> void:
- if failed:
- _failure_count += count
+func set_failed(count :int) -> void:
+ _failure_count += count
func set_reports(failure_reports :Array[GdUnitReport]) -> void:
_failure_reports = failure_reports
-func update(test_report :GdUnitTestCaseReport) -> void:
- for report in _reports:
- if report.name() == test_report.name():
- report.update(test_report)
+func add_or_create_test_report(test_report: GdUnitTestCaseReport) -> void:
+ _reports.append(test_report)
+
+
+func update_testsuite_counters(p_error_count: int, p_failure_count: int, p_orphan_count: int,
+ p_is_skipped: bool, p_is_flaky: bool, p_duration: int) -> void:
+ _error_count += p_error_count
+ _failure_count += p_failure_count
+ _orphan_count += p_orphan_count
+ _skipped_count += p_is_skipped as int
+ _flaky_count += p_is_flaky as int
+ _duration += p_duration
+
+
+func set_testcase_counters(test_name: String, p_error_count: int, p_failure_count: int, p_orphan_count: int,
+ p_is_skipped: bool, p_is_flaky: bool, p_duration: int) -> void:
+ if _reports.is_empty():
+ return
+ var test_report:GdUnitTestCaseReport = _reports.filter(func (report: GdUnitTestCaseReport) -> bool:
+ return report.name() == test_name
+ ).back()
+ if test_report:
+ test_report.set_testcase_counters(p_error_count, p_failure_count, p_orphan_count, p_is_skipped, p_is_flaky, p_duration)
+
+
+func add_testcase_reports(test_name: String, reports: Array[GdUnitReport] ) -> void:
+ if reports.is_empty():
+ return
+ # we lookup to latest matching report because of flaky tests could be retry the tests
+ # and resultis in multipe report entries with the same name
+ var test_report:GdUnitTestCaseReport = _reports.filter(func (report: GdUnitTestCaseReport) -> bool:
+ return report.name() == test_name
+ ).back()
+ if test_report:
+ test_report.add_testcase_reports(reports)
diff --git a/addons/gdUnit4/src/report/JUnitXmlReport.gd b/addons/gdUnit4/src/report/JUnitXmlReport.gd
index a4c040dc..8921564b 100644
--- a/addons/gdUnit4/src/report/JUnitXmlReport.gd
+++ b/addons/gdUnit4/src/report/JUnitXmlReport.gd
@@ -14,6 +14,7 @@ const ATTR_MESSAGE := "message"
const ATTR_NAME := "name"
const ATTR_PACKAGE := "package"
const ATTR_SKIPPED := "skipped"
+const ATTR_FLAKY := "flaky"
const ATTR_TESTS := "tests"
const ATTR_TIME := "time"
const ATTR_TIMESTAMP := "timestamp"
@@ -46,6 +47,8 @@ func build_junit_report(report :GdUnitReportSummary) -> String:
.attribute(ATTR_NAME, "report_%s" % _iteration)\
.attribute(ATTR_TESTS, report.test_count())\
.attribute(ATTR_FAILURES, report.failure_count())\
+ .attribute(ATTR_SKIPPED, report.skipped_count())\
+ .attribute(ATTR_FLAKY, report.flaky_count())\
.attribute(ATTR_TIME, JUnitXmlReport.to_time(report.duration()))\
.add_childs(build_test_suites(report))
var as_string := test_suites.to_xml()
@@ -55,8 +58,8 @@ func build_junit_report(report :GdUnitReportSummary) -> String:
func build_test_suites(summary :GdUnitReportSummary) -> Array:
var test_suites :Array[XmlElement] = []
- for index in summary.reports().size():
- var suite_report :GdUnitTestSuiteReport = summary.reports()[index]
+ for index in summary.get_reports().size():
+ var suite_report :GdUnitTestSuiteReport = summary.get_reports()[index]
var iso8601_datetime := Time.get_datetime_string_from_unix_time(suite_report.time_stamp())
test_suites.append(XmlElement.new("testsuite")\
.attribute(ATTR_ID, index)\
@@ -68,6 +71,7 @@ func build_test_suites(summary :GdUnitReportSummary) -> Array:
.attribute(ATTR_FAILURES, suite_report.failure_count())\
.attribute(ATTR_ERRORS, suite_report.error_count())\
.attribute(ATTR_SKIPPED, suite_report.skipped_count())\
+ .attribute(ATTR_FLAKY, suite_report.flaky_count())\
.attribute(ATTR_TIME, JUnitXmlReport.to_time(suite_report.duration()))\
.add_childs(build_test_cases(suite_report)))
return test_suites
@@ -75,8 +79,8 @@ func build_test_suites(summary :GdUnitReportSummary) -> Array:
func build_test_cases(suite_report :GdUnitTestSuiteReport) -> Array:
var test_cases :Array[XmlElement] = []
- for index in suite_report.reports().size():
- var report :GdUnitTestCaseReport = suite_report.reports()[index]
+ for index in suite_report.get_reports().size():
+ var report :GdUnitTestCaseReport = suite_report.get_reports()[index]
test_cases.append( XmlElement.new("testcase")\
.attribute(ATTR_NAME, JUnitXmlReport.encode_xml(report.name()))\
.attribute(ATTR_CLASSNAME, report.suite_name())\
@@ -85,26 +89,24 @@ func build_test_cases(suite_report :GdUnitTestSuiteReport) -> Array:
return test_cases
-func build_reports(test_report :GdUnitTestCaseReport) -> Array:
+func build_reports(test_report: GdUnitTestCaseReport) -> Array:
var failure_reports :Array[XmlElement] = []
- if test_report.failure_count() or test_report.error_count():
- for failure in test_report._failure_reports:
- var report := failure as GdUnitReport
- if report.is_failure():
- failure_reports.append( XmlElement.new("failure")\
- .attribute(ATTR_MESSAGE, "FAILED: %s:%d" % [test_report._resource_path, report.line_number()])\
- .attribute(ATTR_TYPE, JUnitXmlReport.to_type(report.type()))\
- .text(convert_rtf_to_text(report.message())))
- elif report.is_error():
- failure_reports.append( XmlElement.new("error")\
- .attribute(ATTR_MESSAGE, "ERROR: %s:%d" % [test_report._resource_path, report.line_number()])\
- .attribute(ATTR_TYPE, JUnitXmlReport.to_type(report.type()))\
- .text(convert_rtf_to_text(report.message())))
- if test_report.skipped_count():
- for failure in test_report._failure_reports:
- var report := failure as GdUnitReport
- failure_reports.append( XmlElement.new("skipped")\
- .attribute(ATTR_MESSAGE, "SKIPPED: %s:%d" % [test_report._resource_path, report.line_number()]))
+
+ for report: GdUnitReport in test_report.get_test_reports():
+ if report.is_failure():
+ failure_reports.append(XmlElement.new("failure")\
+ .attribute(ATTR_MESSAGE, "FAILED: %s:%d" % [test_report._resource_path, report.line_number()])\
+ .attribute(ATTR_TYPE, JUnitXmlReport.to_type(report.type()))\
+ .text(convert_rtf_to_text(report.message())))
+ elif report.is_error():
+ failure_reports.append(XmlElement.new("error")\
+ .attribute(ATTR_MESSAGE, "ERROR: %s:%d" % [test_report._resource_path, report.line_number()])\
+ .attribute(ATTR_TYPE, JUnitXmlReport.to_type(report.type()))\
+ .text(convert_rtf_to_text(report.message())))
+ elif report.is_skipped():
+ failure_reports.append(XmlElement.new("skipped")\
+ .attribute(ATTR_MESSAGE, "SKIPPED: %s:%d" % [test_report._resource_path, report.line_number()])\
+ .text(convert_rtf_to_text(report.message())))
return failure_reports
diff --git a/addons/gdUnit4/src/report/XmlElement.gd b/addons/gdUnit4/src/report/XmlElement.gd
index b5d2ed38..86c74212 100644
--- a/addons/gdUnit4/src/report/XmlElement.gd
+++ b/addons/gdUnit4/src/report/XmlElement.gd
@@ -39,6 +39,7 @@ func add_child(child :XmlElement) -> XmlElement:
func add_childs(childs :Array[XmlElement]) -> XmlElement:
for child in childs:
+ @warning_ignore("return_value_discarded")
add_child(child)
return self
diff --git a/addons/gdUnit4/src/report/template/css/breadcrumb.css b/addons/gdUnit4/src/report/template/css/breadcrumb.css
index 2dd65fee..17215ff2 100644
--- a/addons/gdUnit4/src/report/template/css/breadcrumb.css
+++ b/addons/gdUnit4/src/report/template/css/breadcrumb.css
@@ -1,15 +1,12 @@
-
.breadcrumb {
display: flex;
border-radius: 6px;
overflow: hidden;
height: 45px;
z-index: 1;
- background-color: #055d9c;
- font-weight: bold;
- font-size: 16px;
+ background-color: #9d73eb;
margin-top: 0px;
- margin-bottom: 20px;
+ margin-bottom: 10px;
box-shadow: 0 0 3px black;
}
@@ -25,11 +22,11 @@
}
.breadcrumb a:first-child {
- padding-left: 5.2px;
+ padding-left: 5.2px;
}
.breadcrumb a:last-child {
- padding-right: 5.2px;
+ padding-right: 5.2px;
}
.breadcrumb a:after {
@@ -40,28 +37,30 @@
height: 45px;
top: 0;
right: -20px;
- background-color: #055d9c;
+ background-color: #9d73eb;
border-top-right-radius: 5px;
transform: scale(0.707) rotate(45deg);
- box-shadow: 2px -2px rgba(0,0,0,0.25);
+ box-shadow: 2px -2px rgba(0, 0, 0, 0.25);
z-index: 1;
}
.breadcrumb a:last-child:after {
- content: none;
+ content: none;
}
-.breadcrumb a.active, .breadcrumb a:hover {
- background: #347bad;
+.breadcrumb a.active,
+.breadcrumb a:hover {
+ background: #b899f2;
color: white;
text-decoration: underline;
}
-.breadcrumb a.active:after, .breadcrumb a:hover:after {
- background: #347bad;
+.breadcrumb a.active:after,
+.breadcrumb a:hover:after {
+ background: #b899f2;
}
.breadcrumb span {
- margin:inherit;
+ margin: inherit;
z-index: 2;
}
diff --git a/addons/gdUnit4/src/report/template/css/icon.png b/addons/gdUnit4/src/report/template/css/icon.png
deleted file mode 100644
index eeac2924..00000000
Binary files a/addons/gdUnit4/src/report/template/css/icon.png and /dev/null differ
diff --git a/addons/gdUnit4/src/report/template/css/logo.png b/addons/gdUnit4/src/report/template/css/logo.png
new file mode 100644
index 00000000..12de79ff
Binary files /dev/null and b/addons/gdUnit4/src/report/template/css/logo.png differ
diff --git a/addons/gdUnit4/src/report/template/css/icon.png.import b/addons/gdUnit4/src/report/template/css/logo.png.import
similarity index 68%
rename from addons/gdUnit4/src/report/template/css/icon.png.import
rename to addons/gdUnit4/src/report/template/css/logo.png.import
index c1a20620..0bf55c36 100644
--- a/addons/gdUnit4/src/report/template/css/icon.png.import
+++ b/addons/gdUnit4/src/report/template/css/logo.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://c8duoevjrvx65"
-path="res://.godot/imported/icon.png-62697e17645466394a12e1821970bc28.ctex"
+uid="uid://2n0cbwv2t23g"
+path="res://.godot/imported/logo.png-37f10c549c1299049c5a6012ef6f9868.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://addons/gdUnit4/src/report/template/css/icon.png"
-dest_files=["res://.godot/imported/icon.png-62697e17645466394a12e1821970bc28.ctex"]
+source_file="res://addons/gdUnit4/src/report/template/css/logo.png"
+dest_files=["res://.godot/imported/logo.png-37f10c549c1299049c5a6012ef6f9868.ctex"]
[params]
diff --git a/addons/gdUnit4/src/report/template/css/style.css b/addons/gdUnit4/src/report/template/css/style.css
deleted file mode 100644
index 747d57cb..00000000
--- a/addons/gdUnit4/src/report/template/css/style.css
+++ /dev/null
@@ -1,312 +0,0 @@
-
-body {
- margin: 0;
- padding: 0;
- font-family: sans-serif;
- font-size: 12pt;
- background: #dbf1ff;
-}
-
-footer {
- position: fixed;
- left: 0;
- bottom: 0;
- width: 100%;
- height: 30px;
- font-size: 14px;
- white-space: nowrap;
- background-image: linear-gradient(to bottom right, #347bad, #055d9c);
-}
-
-footer a {
- padding-left: 5px;
-}
-
-footer p {
- padding-left: 10px;
-}
-
-.header {
- padding-top: 5px;
- padding-bottom: 5px;
- width: 100%;
- height: 100px;
- text-align: left;
- background-image: linear-gradient(to bottom right, #347bad, #055d9c);
-}
-
-.header h1 {
- font-size: 200%;
- display: flex;
- line-height: 64px;
- text-align: center;
- text-shadow: 2px 2px 5px black;
-}
-
-.header h1 .color1 {
- color: #9887c4;
-}
-
-.header h1 .color2 {
- color: #7a57d6;
-}
-
-.header h1 .color3 {
- color: #2fa5f5;
- padding-left: 10px;
-}
-
-.header img {
- padding-left: 50px;
- width: 64px;
- height: 64px;
- padding-right: 10px;
-}
-
-.content {
- padding-left: 50px;
- padding-right: 50px;
- padding-top: 30px;
- padding-bottom: 30px;
-}
-
-.content h1 {
- font-size: 130%;
- text-align: left;
- margin-bottom: 10px;
- margin: 0;
- padding: 0;
-}
-
-.grid-container {
- display: grid;
- grid-template-columns: 420px 400px auto;
- margin-bottom: 20px;
-}
-
-.grid-item {
- border-radius: 15px;
- border: 2px solid #9887c4;
- padding: 10px;
- margin: 5px;
- box-shadow: 0 0 10px #9887c4;
-}
-
-.summary table {
- border-collapse: collapse;
- border-collapse: inherit;
- border-spacing: 10px 10px;
-}
-
-.summary cell {
- width: 110px;
- font-size: 110%;
- text-align: left;
-}
-
-.summary .counter, .percent {
- width: 300px;
- font-size: 120%;
- font-weight: bold;
- text-align: right;
- box-shadow: 0 0 3px black;
-}
-
-.report {
- margin-top: 10px;
- margin-bottom: 10px;
-}
-
-/* Style tab links */
-input, section {
- clear: both;
- padding-top: 0px;
- padding-bottom: 0px;
- display: none;
- background-color: transparent;
- box-shadow: 0 0 5px black;
-}
-
-label {
- width: auto !important;
- min-width: 200px;
- height: 20px;
-
- font-weight: bold;
- font-size: 16px;
- color: white;
-
- margin: auto;
- display: flex;
- float: left;
- justify-content: space-between;
-
- padding-top: 18px;
- padding-left: 10px;
- padding-right: 10px;
- padding-bottom: 9px;
-
- border-top: 1px solid #DDD;
- border-right: 1px solid #DDD;
- border-left: 1px solid #DDD;
-
- -moz-border-radius: 7px;
- border-radius: 8px 4px 0px 0px;
- text-decoration: none;
- background-color: #055d9c
-}
-
-label:hover {
- cursor: pointer;
- text-decoration: underline;
-}
-
-#tab1:checked ~ #content1, #tab2:checked ~ #content2, #tab3:checked ~ #content3 {
- display: block;
-}
-
-input:checked + label {
- height: 14px;
- border-top: 8px solid #9887c4;
- border-right: 1px solid #9887c4;
- border-left: 1px solid #9887c4;
- border-bottom-color: transparent;
- background-color: #347bad;
- box-shadow: -2px -2px 2px #9887c4;
-}
-
-div.selected {
- display: block;
-}
-
-div.deselected {
- display: none;
-}
-
-.tab-report-grid {
- display: grid;
- grid-template-columns: 60% 40%;
- height: 400px;
- margin-bottom: 20px;
-}
-
-.grid-item {
- overflow: auto;
-}
-
-div.tab table {
- border-collapse: collapse;
-}
-
-div.tab th, div.tab table {
- border-bottom: solid 1px #055d9c;
-}
-
-div.tab th {
- text-align: left;
- white-space: nowrap;
- padding-left: 6em;
- padding-right: 10px;
-}
-
-div.tab th:first-child {
- padding-left: 5px;
-}
-
-div.tab td {
- white-space: nowrap;
- padding-left: 6em;
- padding-right: 10px;
- padding-top: 5px;
- padding-bottom: 5px;
-}
-
-div.tab td:first-child {
- padding-left: 5px;
- text-align: left;
-}
-
-div.tab td.numeric, div.tab {
- text-align: right;
-}
-
-div.tab td.report-column, th.report-column {
- display:none;
-}
-
-div.tab td.success, div.tab a.success {
- color: #03a606;
-}
-
-div.tab td.error, div.tab a.error {
- color: #bf3600;
-}
-
-div.tab td.failure, div.tab a.failure {
- color: #bf3600;
-}
-
-div.tab td.warning, div.tab a.warning {
- color: #cca704;
-}
-
-div.tab tr:hover {
- background-color: #d9e7fa;
- box-shadow: 0 0 5px black;
-}
-
-div.tab tr.selected {
- background-color: #347bad;
-}
-
-div.tab th {
- width: 100%;
- height: 40px;
- background-color: #347bad;
- color: white;
- font-weight: bold;
-}
-
-.success_percent {
- font-size: 750%;
-}
-
-.border_success {
- border: solid 3px #03a606;
- background-image: repeating-linear-gradient(45deg, white, white 20%, #03a606);
-}
-
-.border_error {
- border: solid 3px #bf3600;
- background-image: repeating-linear-gradient(45deg, #e86363, white 0%, #bf3600);
-}
-
-.border_failure {
- border: solid 3px #bf3600;
- background-image: repeating-linear-gradient(45deg, #e86363, white 0%, #bf3600);
-}
-
-div.logging {
- padding: 10px;
- height: 400px;
- margin: 0px;
- background-image: linear-gradient(to bottom right, #347bad, #055d9c);
- color: white;
-}
-
-div.logging p {
- padding: 10px;
- font: arial;
- background-color: #dbf1ff;
- height: 85%;
-}
-
-div.report-column {
- margin-top: 10px;
- width: 100%;
- height: 300px;
- font: arial;
- font-size: 16;
- text-align: left;
- background-color: #dbf1ff;
-}
diff --git a/addons/gdUnit4/src/report/template/css/styles.css b/addons/gdUnit4/src/report/template/css/styles.css
new file mode 100644
index 00000000..23eac48e
--- /dev/null
+++ b/addons/gdUnit4/src/report/template/css/styles.css
@@ -0,0 +1,468 @@
+html,
+body {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ padding: 0;
+ font-family: sans-serif;
+ background-color: white;
+ height: 100%;
+}
+
+main {
+ flex-grow: 1;
+ overflow: auto;
+ margin: 0 10em;
+}
+
+
+header {
+ color: white;
+ padding: 1px;
+ position: relative;
+ background-image: linear-gradient(to bottom right, #8058e3, #9d73eb);
+}
+
+.logo {
+ position: fixed;
+ top: 20px;
+ left: 20px;
+ display: flex;
+ align-items: center;
+ z-index: 1000;
+ filter: grayscale(1);
+ mix-blend-mode: plus-lighter;
+}
+
+.logo img {
+ width: 64px;
+ height: 64px;
+}
+
+.logo span {
+ font-size: 1.2em;
+ color: lightslategray;
+}
+
+.report-container {
+ margin: 0 15em;
+ text-align: center;
+ margin-top: 60px;
+ flex-grow: 0;
+}
+
+h1 {
+ margin: 0 0 20px 0;
+ font-size: 2.5em;
+ font-weight: normal;
+}
+
+.summary {
+ display: inline-flex;
+ justify-content: center;
+ flex-wrap: nowrap;
+ margin-bottom: 20px;
+ align-items: baseline;
+ max-width: 960px;
+}
+
+.summary-item {
+ flex: 1;
+ min-width: 80px;
+}
+
+.label {
+ font-size: 1em;
+ flex-wrap: nowrap;
+}
+
+.value {
+ font-size: 0.9em;
+ display: block;
+ padding-top: 10px;
+ color: lightgray;
+}
+
+.success-rate {
+ padding-left: 40px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+.check-icon {
+ background-color: #34c538;
+ color: white;
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.4em;
+}
+
+.rate-text {
+ text-align: center;
+ flex-wrap: nowrap;
+}
+
+.percentage {
+ font-size: 1.2em;
+ font-weight: bold;
+}
+
+
+nav {
+ padding: 20px 0px;
+ font-family: monospace;
+}
+
+nav ul {
+ list-style-type: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ justify-content: flex-start;
+ border-bottom: 1px solid lightgray;
+}
+
+nav li {
+ cursor: pointer;
+ padding: 5px 20px;
+ font-size: 1.1em;
+ color: lightslategray;
+}
+
+nav li.active {
+ color: darkslategray;
+ border-bottom: 1px solid darkslategray;
+ font-weight: bold;
+}
+
+div#content {
+ height: calc(100vh - 400px);
+}
+
+
+table {
+ width: 100%;
+ height: 100%;
+ border-collapse: collapse;
+ overflow: hidden;
+}
+
+thead th {
+ position: sticky;
+ top: 0;
+ background-color: white;
+ z-index: 1;
+ border-bottom: 2px solid #ddd;
+}
+
+tbody {
+ display: block;
+ /* Limit the height of the table body */
+ max-height: calc(100vh - 400px);
+ /* Enable scrolling on the table body */
+ overflow-y: auto;
+}
+
+thead,
+tbody tr {
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+}
+
+tbody td {
+ overflow: hidden;
+}
+
+/* Ensure scrollbar visibility */
+tbody::-webkit-scrollbar {
+ height: 4px;
+ width: 14px;
+}
+
+tbody::-webkit-scrollbar-thumb {
+ background-color: #aaa6a6;
+ border-radius: 4px;
+}
+
+tbody::-webkit-scrollbar-track {
+ background-color: #f1f1f1;
+}
+
+th,
+td {
+ font-size: .9em;
+ padding: 5px 0px;
+ border-bottom: 1px solid #eee;
+ color: lightslategrey;
+ text-align: left;
+ text-wrap: nowrap;
+ /* Default max and min width for all columns */
+ max-width: 150px;
+ min-width: 80px;
+ width: 80px;
+}
+
+th {
+ font-size: 1em;
+ font-weight: normal;
+ padding-top: 20px;
+ color: gray;
+ text-wrap: nowrap;
+}
+
+.tab-report {
+ display: grid;
+ grid-template-columns: 100%;
+ margin-bottom: 20px;
+}
+
+.tab-report-grid {
+ display: grid;
+ grid-template-columns: 70% 30%;
+ margin-bottom: 20px;
+}
+
+
+/* Specific styling for the first column (Testcase) */
+th:first-child,
+td:first-child {
+ padding-left: 5px;
+ text-align: left;
+ /* Max width for the first column */
+ min-width: 249px;
+ width: 250px;
+ /* Enable scrollbar if content exceeds max-width */
+ white-space: nowrap;
+ overflow: auto;
+}
+
+/* Scrollbar styles for first column */
+td:first-child {
+ overflow-x: auto;
+ text-overflow: initial;
+}
+
+/* Scrollbar appearance */
+td:first-child::-webkit-scrollbar {
+ height: 6px;
+}
+
+td:first-child::-webkit-scrollbar-thumb {
+ background-color: #888;
+ border-radius: 10px;
+}
+
+td:first-child::-webkit-scrollbar-track {
+ background-color: #f1f1f1;
+}
+
+/* Max width for Result column */
+th:nth-child(2),
+td:nth-child(2) {
+ max-width: 140px;
+ min-width: 140px;
+ width: 140px;
+}
+
+/* Max width for Quick Results column */
+th:nth-child(9),
+td:nth-child(9) {
+ max-width: 140px;
+ min-width: 140px;
+ width: 140px;
+ padding-right: 10px;
+}
+
+/* Background color for alternating groups */
+.group-bg-1 {
+ background-color: #f1f1f1;
+}
+
+.group-bg-2 {
+ background-color: #e0e0e0;
+}
+
+.grid-item {
+ overflow: auto;
+ padding-left: 20px;
+ color: lightslategrey;
+ max-height: calc(100vh - 350px);
+}
+
+div.tab td.report-column,
+th.report-column {
+ display: none;
+}
+
+/* Result status styles */
+.status {
+ padding: 2px 40px;
+ border-radius: 6px;
+ color: black;
+ width: 40px;
+ display: flex;
+ align-content: center;
+ align-items: center;
+}
+
+.status-bar {
+ display: flex;
+ border-radius: 8px;
+ overflow: hidden;
+ height: 20px;
+ flex-wrap: nowrap;
+ justify-content: space-evenly;
+}
+
+.status-bar-column {
+ margin: -2px;
+ color: black;
+ display: flex;
+ align-content: center;
+ align-items: center;
+ transition: width 0.3s ease;
+}
+
+.status-passed {
+ background-color: #63bb38;
+}
+
+.status-error {
+ background-color: #fd1100;
+}
+
+.status-failed {
+ background-color: #ed594f;
+}
+
+.status-flaky {
+ background-color: #1d9a1f;
+}
+
+.status-warning {
+ background-color: #fdda3f;
+}
+
+div.tab tr:hover {
+ background-color: #d9e7fa;
+ box-shadow: 0 0 5px black;
+}
+
+div.tab tr.selected {
+ background-color: #d9e7fa;
+}
+
+div.report-column {
+ margin-top: 10px;
+ width: 100%;
+ text-align: left;
+}
+
+.logging-container {
+ width: 100%;
+ height: 100%;
+}
+
+div.godot-report-frame {
+ margin: 10px;
+ font-family: monospace;
+ height: 100%;
+ background-color: #eee;
+}
+
+div.include-footer {
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ display: flex;
+}
+
+footer {
+ position: fixed;
+ padding-left: 10em;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ white-space: nowrap;
+ color: lightgray;
+ font-size: 12px;
+ background-image: linear-gradient(to bottom right, #8058e3, #9d73eb);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+footer .status-legend {
+ display: flex;
+ gap: 15px;
+ width: 500px;
+}
+
+footer a {
+ color: lightgray;
+}
+
+footer a:hover {
+ color: whitesmoke;
+}
+
+footer a:visited {
+ color: whitesmoke;
+}
+
+.status-legend-item {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+}
+
+.status-box {
+ width: 15px;
+ height: 15px;
+ border-radius: 3px;
+ display: inline-block;
+}
+
+/* Normal link */
+a {
+ color: lightslategrey;
+}
+
+/* Link when hovered */
+a:hover {
+ color: #9d73eb;
+}
+
+/* Visited link */
+a:visited {
+ color: #8058e3;
+}
+
+/* Active link (while being clicked) */
+a:active {
+ color: #8058e3;
+ /* Custom color when link is clicked */
+}
+
+
+@media (max-width: 1024px) {
+ .summary {
+ flex-direction: column;
+ }
+
+ nav ul {
+ flex-wrap: wrap;
+ }
+
+ nav li {
+ margin-right: 10px;
+ margin-bottom: 5px;
+ }
+}
diff --git a/addons/gdUnit4/src/report/template/folder_report.html b/addons/gdUnit4/src/report/template/folder_report.html
index 6f217725..c6df9810 100644
--- a/addons/gdUnit4/src/report/template/folder_report.html
+++ b/addons/gdUnit4/src/report/template/folder_report.html
@@ -1,99 +1,119 @@
+
-
-
- Testsuite Results
-
-
+
+
+
+
+ GdUnit4 Testsuite
+
+
-
-
-
-
-
-
-
- TestSuites |
- ${suite_count} |
-
-
- Tests |
- ${test_count} |
-
-
- Skipped |
- ${skipped_count} |
-
-
- Failures |
- ${failure_count} |
-
-
- Orphans |
- ${orphan_count} |
-
-
- Duration |
- ${duration} |
-
-
-
-
-
Success Rate
-
-
- ${success_percent} |
-
-
+