diff --git a/addons/gdUnit4/GdUnitRunner.cfg b/addons/gdUnit4/GdUnitRunner.cfg
deleted file mode 100644
index b91a3d7..0000000
--- a/addons/gdUnit4/GdUnitRunner.cfg
+++ /dev/null
@@ -1 +0,0 @@
-{"included":{"res://tests/scenes/game_tests.gd":["test_save_game"]},"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 c4ef66c..6e5d588 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 0919520..e540a5e 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"))
@@ -385,7 +397,7 @@ class CLIRunner:
var skipped := config.skipped()
if skipped.is_empty():
return
- _console.prints_warning("Found excluded test suite's configured at '%s'" % _runner_config_file)
+
for test_suite in test_suites:
# skipp c# testsuites for now
if test_suite.get_script() == null:
@@ -395,24 +407,26 @@ class CLIRunner:
# Dictionary[String, PackedStringArray]
func skip_suite(test_suite: Node, skipped: Dictionary) -> void:
- var skipped_suites :Array[String] = skipped.keys()
+ var skipped_suites :Array = skipped.keys()
var suite_name := test_suite.get_name()
var test_suite_path: String = (
test_suite.get_meta("ResourcePath") if test_suite.get_script() == null
else test_suite.get_script().resource_path
)
- for suite_to_skip in skipped_suites:
+ for suite_to_skip: String in skipped_suites:
# if suite skipped by path or name
if (
suite_to_skip == test_suite_path
or (suite_to_skip.is_valid_filename() and suite_to_skip == suite_name)
):
- var skipped_tests: Array[String] = skipped.get(suite_to_skip)
- var skip_reason := "Excluded by config '%s'" % _runner_config_file
+ var skipped_tests: PackedStringArray = skipped.get(suite_to_skip)
+ var skip_reason := "Excluded by configuration"
# 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)
+ _console.prints_warning("Mark the entire test suite '%s' as skipped!" % test_suite_path)
+ @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
)
@@ -615,6 +622,7 @@ func _initialize() -> void:
# do not use print statements on _finalize it results in random crashes
func _finalize() -> void:
+ queue_delete(_cli_runner)
if OS.is_stdout_verbose():
prints("Finallize ..")
prints("-Orphan nodes report-----------------------")
diff --git a/addons/gdUnit4/bin/GdUnitCopyLog.gd b/addons/gdUnit4/bin/GdUnitCopyLog.gd
index 2e037a6..084ac72 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]
+ return files_sorted.back()
-func _patch_report(report_path: String, godot_log: String) -> void:
- var index_file := FileAccess.open("%s/index.html" % report_path, FileAccess.READ_WRITE)
+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 log file '%s'. Error: %s"
+ % [log_file, error_string(FileAccess.get_open_error())]
+ )
+ var content := "
" + file.get_as_text()
+ # patch out console format codes
+ for color_index in range(0, 256):
+ var to_replace := "[38;5;%dm" % color_index
+ content = content.replace(to_replace, "")
+ content += "
"
+ content = content\
+ .replace("[0m", "")\
+ .replace(CmdConsole.CSI_BOLD, "")\
+ .replace(CmdConsole.CSI_ITALIC, "")\
+ .replace(CmdConsole.CSI_UNDERLINE, "")
+ 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"
+ % [get_log_report_html(), error_string(FileAccess.get_open_error())]
+ )
+ 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 _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
- # 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)
+ 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 _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)
- if file == null:
- return GdUnitResult.error(
- "Can't find file '%s'. Error: %s"
- % [from_file, error_string(FileAccess.get_open_error())]
- )
- var content := file.get_as_text()
- # patch out console format codes
- for color_index in range(0, 256):
- var to_replace := "[38;5;%dm" % color_index
- content = content.replace(to_replace, "")
- content = content\
- .replace("[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)
- if file == null:
- return GdUnitResult.error(
- "Can't open to write '%s'. Error: %s"
- % [to_file, error_string(FileAccess.get_open_error())]
- )
- file.store_string(content)
- return GdUnitResult.empty()
-
-
-func reports_available() -> bool:
- return DirAccess.dir_exists_absolute(_report_root_path)
+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/bin/ProjectScanner.gd b/addons/gdUnit4/bin/ProjectScanner.gd
deleted file mode 100644
index 93c40de..0000000
--- a/addons/gdUnit4/bin/ProjectScanner.gd
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/usr/bin/env -S godot -s
-@tool
-extends SceneTree
-
-const CmdConsole = preload("res://addons/gdUnit4/src/cmd/CmdConsole.gd")
-
-
-func _initialize() -> void:
- set_auto_accept_quit(false)
- var scanner := SourceScanner.new(self)
- root.add_child(scanner)
-
-
-# gdlint: disable=trailing-whitespace
-class SourceScanner extends Node:
-
- enum {
- INIT,
- STARTUP,
- SCAN,
- QUIT,
- DONE
- }
-
- var _state := INIT
- var _console := CmdConsole.new()
- var _elapsed_time := 0.0
- var _plugin: EditorPlugin
- var _fs: EditorFileSystem
- var _scene: SceneTree
-
-
- func _init(scene :SceneTree) -> void:
- _scene = scene
- _console.prints_color("""
- ========================================================================
- Running project scan:""".dedent(),
- Color.CORNFLOWER_BLUE
- )
- _state = INIT
-
-
- func _process(delta :float) -> void:
- _elapsed_time += delta
- set_process(false)
- await_inital_scan()
- await scan_project()
- set_process(true)
-
-
- # !! don't use any await in this phase otherwise the editor will be instable !!
- func await_inital_scan() -> void:
- if _state == INIT:
- _console.prints_color("Wait initial scanning ...", Color.DARK_GREEN)
- _plugin = EditorPlugin.new()
- _fs = _plugin.get_editor_interface().get_resource_filesystem()
- _plugin.get_editor_interface().set_plugin_enabled("gdUnit4", false)
- _state = STARTUP
-
- if _state == STARTUP:
- if _fs.is_scanning():
- _console.progressBar(_fs.get_scanning_progress() * 100 as int)
- # we wait 10s in addition to be on the save site the scanning is done
- if _elapsed_time > 10.0:
- _console.progressBar(100)
- _console.new_line()
- _console.prints_color("initial scanning ... done", Color.DARK_GREEN)
- _state = SCAN
-
-
- func scan_project() -> void:
- if _state != SCAN:
- return
- _console.prints_color("Scan project: ", Color.SANDY_BROWN)
- await get_tree().process_frame
- _fs.scan_sources()
- await get_tree().create_timer(5).timeout
- _console.prints_color("Scan: ", Color.SANDY_BROWN)
- _console.progressBar(0)
- await get_tree().process_frame
- _fs.scan()
- while _fs.is_scanning():
- await get_tree().process_frame
- _console.progressBar(_fs.get_scanning_progress() * 100 as int)
- await get_tree().create_timer(10).timeout
- _console.progressBar(100)
- _console.new_line()
- _plugin.free()
- _console.prints_color("""
- Scan project done.
- ========================================================================""".dedent(),
- Color.CORNFLOWER_BLUE
- )
- await get_tree().process_frame
- await get_tree().physics_frame
- queue_free()
- # force quit editor
- _state = DONE
- _scene.quit(0)
diff --git a/addons/gdUnit4/plugin.cfg b/addons/gdUnit4/plugin.cfg
index 11f9d21..2070c97 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.1"
+version="4.4.3"
script="plugin.gd"
diff --git a/addons/gdUnit4/plugin.gd b/addons/gdUnit4/plugin.gd
index c17fdc9..b7887c8 100644
--- a/addons/gdUnit4/plugin.gd
+++ b/addons/gdUnit4/plugin.gd
@@ -1,17 +1,20 @@
@tool
extends EditorPlugin
-const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
-const GdUnitTestDiscoverGuard := preload("res://addons/gdUnit4/src/core/discovery/GdUnitTestDiscoverGuard.gd")
+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 _server_node :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:
prints("GdUnit4 plugin requires a minimum of Godot 4.2.x Version!")
return
@@ -21,34 +24,39 @@ 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")
- _server_node = load("res://addons/gdUnit4/src/network/GdUnitServer.tscn").instantiate()
- Engine.get_main_loop().root.add_child.call_deferred(_server_node)
prints("Loading GdUnit4 Plugin success")
if GdUnitSettings.is_update_notification_enabled():
- var update_tool :Node = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate()
+ var update_tool: Node = load("res://addons/gdUnit4/src/update/GdUnitUpdateNotify.tscn").instantiate()
Engine.get_main_loop().root.add_child.call_deferred(update_tool)
if GdUnit4CSharpApiLoader.is_mono_supported():
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)
func _exit_tree() -> void:
+ if check_running_in_test_env():
+ return
if is_instance_valid(_gd_inspector):
remove_control_from_docks(_gd_inspector)
- GodotVersionFixures.free_fix(_gd_inspector)
+ _gd_inspector.free()
if is_instance_valid(_gd_console):
remove_control_from_bottom_panel(_gd_console)
_gd_console.free()
- if is_instance_valid(_server_node):
- Engine.get_main_loop().root.remove_child.call_deferred(_server_node)
- _server_node.queue_free()
- GdUnitTools.dispose_all.call_deferred()
+ GdUnitTools.dispose_all(true)
prints("Unload GdUnit4 Plugin success")
-func _on_resource_saved(resource :Resource) -> void:
+func check_running_in_test_env() -> bool:
+ var args := OS.get_cmdline_args()
+ args.append_array(OS.get_cmdline_user_args())
+ return DisplayServer.get_name() == "headless" or args.has("--selftest") or args.has("--add") or args.has("-a") or args.has("--quit-after") or args.has("--import")
+
+
+func _on_resource_saved(resource: Resource) -> void:
if resource is Script:
- _guard.discover(resource)
+ await _guard.discover(resource as Script)
diff --git a/addons/gdUnit4/src/GdUnitArrayAssert.gd b/addons/gdUnit4/src/GdUnitArrayAssert.gd
index 3fdaaca..cc2226d 100644
--- a/addons/gdUnit4/src/GdUnitArrayAssert.gd
+++ b/addons/gdUnit4/src/GdUnitArrayAssert.gd
@@ -158,3 +158,14 @@ func extractv(
extractor8 :GdUnitValueExtractor = null,
extractor9 :GdUnitValueExtractor = null) -> GdUnitArrayAssert:
return self
+
+
+
+@warning_ignore("unused_parameter")
+func override_failure_message(message :String) -> GdUnitArrayAssert:
+ return self
+
+
+@warning_ignore("unused_parameter")
+func append_failure_message(message :String) -> GdUnitArrayAssert:
+ return self
diff --git a/addons/gdUnit4/src/GdUnitAssert.gd b/addons/gdUnit4/src/GdUnitAssert.gd
index d2a5fd0..6b35475 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
@@ -39,3 +39,11 @@ func test_fail():
@warning_ignore("untyped_declaration")
func override_failure_message(message :String):
return self
+
+
+## Appends a custom message to the failure message.
+## This can be used to add additional infromations to the generated failure message.
+@warning_ignore("unused_parameter")
+@warning_ignore("untyped_declaration")
+func append_failure_message(message :String):
+ return self
diff --git a/addons/gdUnit4/src/GdUnitAwaiter.gd b/addons/gdUnit4/src/GdUnitAwaiter.gd
index fc2e487..51385e8 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