fixed camera and sword animation issue and upgraded to Godot 4.6
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Failing after 2m10s
Create tag and build when new code gets to main / Export (push) Has been skipped

This commit is contained in:
2026-01-27 17:47:19 +01:00
parent 056a68b0ad
commit caeae26a09
335 changed files with 3035 additions and 2221 deletions

View File

@@ -1 +1 @@
uid://bk60ywsj4ekp7
uid://etq75dot1c03

View File

@@ -1 +1 @@
uid://b5sli0lem5xca
uid://cx8bp6vsxyrts

View File

@@ -1 +1 @@
uid://b7ldhc4ryfh1v
uid://dk4tv55wf5mlm

View File

@@ -1 +1 @@
uid://bbaqjhpbxce3u
uid://ds3n71e8n4wce

View File

@@ -196,7 +196,7 @@ static func resource_as_string(resource_path :String) -> String:
if file == null:
push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_string(FileAccess.get_open_error())])
return ""
return file.get_as_text(true)
return file.get_as_text()
static func make_qualified_path(path :String) -> String:

View File

@@ -1 +1 @@
uid://dflqb5germp5n
uid://ddr26lg6f055n

View File

@@ -1 +1 @@
uid://cqndh0nuu8ltx
uid://c0ymnlx3jooyl

View File

@@ -1 +1 @@
uid://cnvq3nb61ei76
uid://b6k1ufvw6w2s5

View File

@@ -18,7 +18,10 @@ var _config := {
TESTS : Array([], TYPE_OBJECT, "RefCounted", GdUnitTestCase),
# the port of running test server for this session
SERVER_PORT : -1
SERVER_PORT : -1,
# Exit on first failure
EXIT_FAIL_FAST : false
}
@@ -40,6 +43,15 @@ func server_port() -> int:
return _config.get(SERVER_PORT, -1)
func do_fail_fast(is_fail_fast: bool) -> GdUnitRunnerConfig:
_config[EXIT_FAIL_FAST] = is_fail_fast
return self
func is_fail_fast() -> bool:
return _config.get(EXIT_FAIL_FAST, false)
func add_test_cases(tests: Array[GdUnitTestCase]) -> GdUnitRunnerConfig:
test_cases().append_array(tests)
return self
@@ -57,7 +69,8 @@ func save_config(path: String = CONFIG_FILE) -> GdUnitResult:
var to_save := {
VERSION : CONFIG_VERSION,
SERVER_PORT : _config.get(SERVER_PORT),
EXIT_FAIL_FAST : is_fail_fast(),
SERVER_PORT : server_port(),
TESTS : Array()
}

View File

@@ -1 +1 @@
uid://ltvpkh3ayklf
uid://dvrxtyni1604f

View File

@@ -72,12 +72,11 @@ func _init(p_scene: Variant, p_verbose: bool, p_hide_push_errors := false) -> vo
return
_scene_tree().root.add_child(_current_scene)
Engine.set_meta("GdUnitSceneRunner", self)
# 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:
_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()
@@ -103,6 +102,7 @@ func _notification(what: int) -> void:
_current_scene.free()
_is_disposed = true
_current_scene = null
Engine.remove_meta("GdUnitSceneRunner")
func _scene_tree() -> SceneTree:
@@ -145,6 +145,7 @@ func simulate_action_release(action: String, event_index := -1) -> GdUnitSceneRu
@warning_ignore("return_value_discarded")
func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
_push_warning_deprecated_arguments(shift_pressed, ctrl_pressed)
simulate_key_press(key_code, shift_pressed, ctrl_pressed)
await _scene_tree().process_frame
simulate_key_release(key_code, shift_pressed, ctrl_pressed)
@@ -152,30 +153,33 @@ func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed :=
func simulate_key_press(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
_push_warning_deprecated_arguments(shift_pressed, ctrl_pressed)
__print_current_focus()
var event := InputEventKey.new()
event.pressed = true
event.keycode = key_code as Key
event.physical_keycode = key_code as Key
event.unicode = key_code
event.alt_pressed = key_code == KEY_ALT
event.shift_pressed = shift_pressed or key_code == KEY_SHIFT
event.ctrl_pressed = ctrl_pressed or key_code == KEY_CTRL
event.set_alt_pressed(key_code == KEY_ALT)
event.set_shift_pressed(shift_pressed)
event.set_ctrl_pressed(ctrl_pressed)
event.get_modifiers_mask()
_apply_input_modifiers(event)
_key_on_press.append(key_code)
return _handle_input_event(event)
func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner:
_push_warning_deprecated_arguments(shift_pressed, ctrl_pressed)
__print_current_focus()
var event := InputEventKey.new()
event.pressed = false
event.keycode = key_code as Key
event.physical_keycode = key_code as Key
event.unicode = key_code
event.alt_pressed = key_code == KEY_ALT
event.shift_pressed = shift_pressed or key_code == KEY_SHIFT
event.ctrl_pressed = ctrl_pressed or key_code == KEY_CTRL
event.set_alt_pressed(key_code == KEY_ALT)
event.set_shift_pressed(shift_pressed)
event.set_ctrl_pressed(ctrl_pressed)
_apply_input_modifiers(event)
_key_on_press.erase(key_code)
return _handle_input_event(event)
@@ -485,6 +489,8 @@ func find_child(name: String, recursive: bool = true, owned: bool = false) -> No
func _scene_name() -> String:
if scene() == null:
return "unknown"
var scene_script :GDScript = scene().get_script()
var scene_name :String = scene().get_name()
if not scene_script:
@@ -515,6 +521,13 @@ func _apply_input_modifiers(event: InputEvent) -> void:
_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
if _last_input_event is InputEventKey and event is InputEventWithModifiers:
var last_input_event := _last_input_event as InputEventKey
var _event := event as InputEventWithModifiers
_event.shift_pressed = _event.shift_pressed or last_input_event.keycode == KEY_SHIFT
_event.alt_pressed = _event.alt_pressed or last_input_event.keycode == KEY_ALT
_event.ctrl_pressed = _event.ctrl_pressed or last_input_event.keycode == KEY_CTRL
_event.meta_pressed = _event.meta_pressed or last_input_event.keycode == KEY_META
# copy over current active mouse mask and combine with curren mask
@@ -620,3 +633,10 @@ func scene() -> Node:
if not _is_disposed:
push_error("The current scene instance is not valid anymore! check your test is valid. e.g. check for missing awaits.")
return null
func _push_warning_deprecated_arguments(shift_pressed: bool, ctrl_pressed: bool) -> void:
if shift_pressed:
push_warning("Deprecated! Don't use 'shift_pressed' it will be removed in v7.0, checkout the documentaion how to use key combinations.")
if ctrl_pressed:
push_warning("Deprecated! Don't use 'ctrl_pressed' it will be removed in v7.0, checkout the documentaion how to use key combinations.")

View File

@@ -1 +1 @@
uid://7a566a4kfreu
uid://bdhmqovuioydb

View File

@@ -21,6 +21,7 @@ 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"
const TEST_RERUN_UNTIL_FAILURE_RETRIES = GROUP_TEST + "/rerun_until_failure_retries"
# Report Setiings
@@ -62,6 +63,7 @@ const SHORTCUT_INSPECTOR_RERUN_TEST = GROUP_SHORTCUT_INSPECTOR + "/rerun_test"
const SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG = GROUP_SHORTCUT_INSPECTOR + "/rerun_test_debug"
const SHORTCUT_INSPECTOR_RUN_TEST_OVERALL = GROUP_SHORTCUT_INSPECTOR + "/run_test_overall"
const SHORTCUT_INSPECTOR_RUN_TEST_STOP = GROUP_SHORTCUT_INSPECTOR + "/run_test_stop"
const SHORTCUT_INSPECTOR_RERUN_TEST_UNTIL_FAILURE = GROUP_SHORTCUT_INSPECTOR + "/rerun_test_until_failure"
const GROUP_SHORTCUT_EDITOR = SHORTCUT_SETTINGS + "/editor"
const SHORTCUT_EDITOR_RUN_TEST = GROUP_SHORTCUT_EDITOR + "/run_test"
@@ -112,6 +114,7 @@ static func setup() -> void:
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")
create_property_if_need(TEST_RERUN_UNTIL_FAILURE_RETRIES, 10, "The number of reruns until the test fails.")
# 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")
@@ -148,6 +151,7 @@ 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 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_RERUN_TEST_UNTIL_FAILURE, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RERUN_TESTS_UNTIL_FAILURE), "Rerun tests until failure occurs")
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
@@ -155,8 +159,8 @@ static func create_shortcut_properties_if_need() -> void:
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), "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)")
create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTSUITE), "Run all test suites in the selected folder or file")
create_property_if_need(SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.RUN_TESTSUITE_DEBUG), "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:
@@ -306,6 +310,10 @@ static func get_flaky_max_retries() -> int:
return get_setting(TEST_FLAKY_MAX_RETRIES, 3)
static func get_rerun_max_retries() -> int:
return get_setting(TEST_RERUN_UNTIL_FAILURE_RETRIES, 10)
static func set_test_discover_enabled(enable :bool) -> void:
var property := get_property(TEST_DISCOVER_ENABLED)
property.set_value(enable)

View File

@@ -1 +1 @@
uid://coby4unvmd3eh
uid://djrx6fy3w3bb

View File

@@ -1 +1 @@
uid://ckx5jnr3ip6vp
uid://l1nsecnjoon6

View File

@@ -107,7 +107,7 @@ func is_signal_collecting(emitter: Object, signal_name: String) -> bool:
return _collected_signals.has(emitter) and (_collected_signals[emitter] as Dictionary).has(signal_name)
func match(emitter :Object, signal_name :String, args :Array) -> bool:
func match(emitter: Object, signal_name: String, args: Array) -> bool:
#prints("match", signal_name, _collected_signals[emitter][signal_name]);
if _collected_signals.is_empty() or not _collected_signals.has(emitter):
return false

View File

@@ -1 +1 @@
uid://cm0rbs8vhdhd1
uid://bnvdsssykfaeh

View File

@@ -33,9 +33,9 @@ signal gdunit_client_connected(client_id: int)
signal gdunit_client_disconnected(client_id: int)
## Emitted when a client terminates unexpectedly.
## Emitted when a the user stops (terminates) the current test session
@warning_ignore("unused_signal")
signal gdunit_client_terminated()
signal gdunit_test_session_terminate()
## Emitted when a test execution event occurs.[br]

View File

@@ -1 +1 @@
uid://kj16fg0hf6kn
uid://7fkqtqq0ib25

View File

@@ -1 +1 @@
uid://4sujouo3vf6d
uid://djyr7is32ffbs

View File

@@ -1 +1 @@
uid://ierjyaem56m3
uid://cg0fqsmpf8fdh

View File

@@ -1 +1 @@
uid://dthfh16tl5wqc
uid://cqe6i2xbwgneb

View File

@@ -70,8 +70,14 @@ func scan_directory(resource_path: String) -> Array[Script]:
func _scan_test_suites_scripts(dir: DirAccess, collected_suites: Array[Script]) -> Array[Script]:
# Skip excluded directories
if dir.file_exists(".gdignore"):
prints("Exclude directory %s, containing .gdignore file" % dir.get_current_dir())
return []
if exclude_scan_directories.has(dir.get_current_dir()):
return collected_suites
var err := dir.list_dir_begin()
if err != OK:
push_error("Error on scanning directory %s" % dir.get_current_dir(), error_string(err))

View File

@@ -1 +1 @@
uid://bju0nt1bgsc2s
uid://bymtxj63ek2kd

View File

@@ -1 +1 @@
uid://d05qgv6uu477i
uid://4tbcywx0qg1d

View File

@@ -1 +1 @@
uid://dehxycxsj68ev
uid://d0d4s6tkgoh3b

View File

@@ -1 +1 @@
uid://dmta1h7ndfnko
uid://dd7g37aslbmm1

View File

@@ -9,7 +9,8 @@ var _attribute: TestCaseAttribute
var _current_iteration: int = -1
var _expect_to_interupt := false
var _timer: Timer
var _interupted: bool = false
var _interupted := false
var _terminated := false
var _failed := false
var _parameter_set_resolver: GdUnitTestParameterSetResolver
var _is_disposed := false
@@ -123,7 +124,7 @@ func do_interrupt() -> void:
# We need to dispose manually the function state here
GdObjects.dispose_function_state(_func_state)
if not is_expect_interupted():
var execution_context:= GdUnitThreadManager.get_current_context().get_execution_context()
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")))
@@ -133,6 +134,16 @@ func do_interrupt() -> void:
completed.emit()
func do_terminate() -> void:
_terminated = true
# We need to dispose manually the function state here
GdObjects.dispose_function_state(_func_state)
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
execution_context.add_report(GdUnitReport.new()\
.create(GdUnitReport.TERMINATED, line_number(), GdAssertMessages.test_session_terminated()))
completed.emit()
func _set_failure_handler() -> void:
if not GdUnitSignals.instance().gdunit_set_test_failed.is_connected(_failure_received):
@warning_ignore("return_value_discarded")
@@ -172,6 +183,10 @@ func is_expect_interupted() -> bool:
return _expect_to_interupt
func is_terminated() -> bool:
return _terminated
func is_parameterized() -> bool:
return _parameter_set_resolver.is_parameterized()
@@ -192,11 +207,6 @@ func test_name() -> String:
return _test_case.test_name
@warning_ignore("native_method_override")
func get_name() -> StringName:
return _test_case.test_name
func line_number() -> int:
return _test_case.line_number

View File

@@ -1 +1 @@
uid://cb2lkpvh0liiv
uid://dvhr6i0bdk05n

View File

@@ -2,7 +2,7 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://csgvrbao53xmv"
uid="uid://c0lvcprd6501t"
path="res://.godot/imported/touch-button.png-2fff40c8520d8e97a57db1b2b043f641.ctex"
metadata={
"vram_texture": false

View File

@@ -1 +1 @@
uid://d2bres53mgxnw
uid://b3ilfkx1js423

View File

@@ -0,0 +1,64 @@
@abstract class_name GdUnitBaseCommand
extends Node
var id: String
var icon: Texture2D
var shortcut: Shortcut = null
var shortcut_type: GdUnitShortcut.ShortCut
func _init(p_id: String, p_shortcut: GdUnitShortcut.ShortCut = GdUnitShortcut.ShortCut.NONE) -> void:
id = p_id
shortcut_type = p_shortcut
_set_shortcut()
func _shortcut_input(event: InputEvent) -> void:
if is_running():
return
if shortcut and shortcut.matches_event(event):
execute()
get_viewport().set_input_as_handled()
func update_shortcut() -> void:
_set_shortcut()
func _set_shortcut() -> void:
if shortcut_type == GdUnitShortcut.ShortCut.NONE:
return
var property_name := GdUnitShortcut.as_property(shortcut_type)
var property := GdUnitSettings.get_property(property_name)
var keys := GdUnitShortcut.default_keys(shortcut_type)
if property != null:
keys = property.value()
var inputEvent := _create_shortcut_input_even(keys)
shortcut = Shortcut.new()
shortcut.set_events([inputEvent])
func _create_shortcut_input_even(key_codes: PackedInt32Array) -> InputEventKey:
var inputEvent := InputEventKey.new()
inputEvent.pressed = true
for key_code in key_codes:
match key_code:
KEY_ALT:
inputEvent.alt_pressed = true
KEY_SHIFT:
inputEvent.shift_pressed = true
KEY_CTRL:
inputEvent.ctrl_pressed = true
_:
inputEvent.keycode = key_code as Key
inputEvent.physical_keycode = key_code as Key
return inputEvent
@abstract func is_running() -> bool
@abstract func execute(...parameters: Array) -> void

View File

@@ -0,0 +1 @@
uid://bxuturao0ahb5

View File

@@ -1,41 +0,0 @@
class_name GdUnitCommand
extends RefCounted
func _init(p_name :String, p_is_enabled: Callable, p_runnable: Callable, p_shortcut :GdUnitShortcut.ShortCut = GdUnitShortcut.ShortCut.NONE) -> void:
assert(p_name != null, "(%s) missing parameter 'name'" % p_name)
assert(p_is_enabled != null, "(%s) missing parameter 'is_enabled'" % p_name)
assert(p_runnable != null, "(%s) missing parameter 'runnable'" % p_name)
assert(p_shortcut != null, "(%s) missing parameter 'shortcut'" % p_name)
self.name = p_name
self.is_enabled = p_is_enabled
self.shortcut = p_shortcut
self.runnable = p_runnable
var name: String:
set(value):
name = value
get:
return name
var shortcut: GdUnitShortcut.ShortCut:
set(value):
shortcut = value
get:
return shortcut
var is_enabled: Callable:
set(value):
is_enabled = value
get:
return is_enabled
var runnable: Callable:
set(value):
runnable = value
get:
return runnable

View File

@@ -1 +0,0 @@
uid://crmuuvbqy4shs

View File

@@ -0,0 +1,42 @@
@abstract class_name GdUnitCommandFileSystem
extends GdUnitBaseCommand
var _test_session_command: GdUnitCommandTestSession
func _init(p_id: String, p_shortcut: GdUnitShortcut.ShortCut, test_session_command: GdUnitCommandTestSession) -> void:
super(p_id, p_shortcut)
_test_session_command = test_session_command
func is_running() -> bool:
return _test_session_command.is_running()
func execute_tests(paths: PackedStringArray, with_debug: bool) -> void:
var suite_scaner := GdUnitTestSuiteScanner.new()
var scripts: Array[Script]
for resource_path in paths:
# directories and test-suites are valid to enable the menu
if DirAccess.dir_exists_absolute(resource_path):
scripts.append_array(suite_scaner.scan_directory(resource_path))
continue
var file_type := resource_path.get_extension()
if file_type == "gd" or file_type == "cs":
var script := GdUnitTestSuiteScanner.load_with_disabled_warnings(resource_path)
if GdUnitTestSuiteScanner.is_test_suite(script):
scripts.append(script)
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverStart.new())
var tests_to_execute: Array[GdUnitTestCase] = []
for script in scripts:
GdUnitTestDiscoverer.discover_tests(script, func(test_case: GdUnitTestCase) -> void:
tests_to_execute.append(test_case)
GdUnitTestDiscoverSink.discover(test_case)
)
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverEnd.new(0, 0))
GdUnitTestDiscoverer.console_log_discover_results(tests_to_execute)
_test_session_command.execute(tests_to_execute, with_debug)

View File

@@ -0,0 +1 @@
uid://bjfsokglrqosr

View File

@@ -0,0 +1,15 @@
class_name GdUnitCommandFileSystemDebugTests
extends GdUnitCommandFileSystem
const ID := "Debug FileSystem Tests"
func _init(test_session_command: GdUnitCommandTestSession) -> void:
super(ID, GdUnitShortcut.ShortCut.RUN_TESTSUITE_DEBUG, test_session_command)
icon = GdUnitUiTools.get_icon("PlayStart")
func execute(...parameters: Array) -> void:
var selected_paths: PackedStringArray = parameters[0]
execute_tests(selected_paths, true)

View File

@@ -0,0 +1 @@
uid://ceeexn8cdh4ul

View File

@@ -0,0 +1,15 @@
class_name GdUnitCommandFileSystemRunTests
extends GdUnitCommandFileSystem
const ID := "Run FileSystem Tests"
func _init(test_session_command: GdUnitCommandTestSession) -> void:
super(ID, GdUnitShortcut.ShortCut.RUN_TESTSUITE, test_session_command)
icon = GdUnitUiTools.get_icon("Play")
func execute(...parameters: Array) -> void:
var selected_paths: PackedStringArray = parameters[0]
execute_tests(selected_paths, false)

View File

@@ -0,0 +1 @@
uid://bv1bu2urk7rby

View File

@@ -1,62 +1,11 @@
class_name GdUnitCommandHandler
extends Object
signal gdunit_runner_start()
signal gdunit_runner_stop(client_id :int)
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
const CMD_RUN_OVERALL = "Debug Overall TestSuites"
const CMD_RUN_TESTCASE = "Run TestCases"
const CMD_RUN_TESTCASE_DEBUG = "Run TestCases (Debug)"
const CMD_RUN_TESTSUITE = "Run TestSuites"
const CMD_RUN_TESTSUITE_DEBUG = "Run TestSuites (Debug)"
const CMD_RERUN_TESTS = "ReRun Tests"
const CMD_RERUN_TESTS_DEBUG = "ReRun Tests (Debug)"
const CMD_STOP_TEST_RUN = "Stop Test Run"
const CMD_CREATE_TESTCASE = "Create TestCase"
const SETTINGS_SHORTCUT_MAPPING := {
"N/A" : GdUnitShortcut.ShortCut.NONE,
GdUnitSettings.SHORTCUT_INSPECTOR_RERUN_TEST : GdUnitShortcut.ShortCut.RERUN_TESTS,
GdUnitSettings.SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG : GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG,
GdUnitSettings.SHORTCUT_INSPECTOR_RUN_TEST_OVERALL : GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL,
GdUnitSettings.SHORTCUT_INSPECTOR_RUN_TEST_STOP : GdUnitShortcut.ShortCut.STOP_TEST_RUN,
GdUnitSettings.SHORTCUT_EDITOR_RUN_TEST : GdUnitShortcut.ShortCut.RUN_TESTCASE,
GdUnitSettings.SHORTCUT_EDITOR_RUN_TEST_DEBUG : GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG,
GdUnitSettings.SHORTCUT_EDITOR_CREATE_TEST : GdUnitShortcut.ShortCut.CREATE_TEST,
GdUnitSettings.SHORTCUT_FILESYSTEM_RUN_TEST : GdUnitShortcut.ShortCut.RUN_TESTSUITE,
GdUnitSettings.SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG : GdUnitShortcut.ShortCut.RUN_TESTSUITE_DEBUG
}
const CommandMapping := {
GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL: GdUnitCommandHandler.CMD_RUN_OVERALL,
GdUnitShortcut.ShortCut.RUN_TESTCASE: GdUnitCommandHandler.CMD_RUN_TESTCASE,
GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG: GdUnitCommandHandler.CMD_RUN_TESTCASE_DEBUG,
GdUnitShortcut.ShortCut.RUN_TESTSUITE: GdUnitCommandHandler.CMD_RUN_TESTSUITE,
GdUnitShortcut.ShortCut.RUN_TESTSUITE_DEBUG: GdUnitCommandHandler.CMD_RUN_TESTSUITE_DEBUG,
GdUnitShortcut.ShortCut.RERUN_TESTS: GdUnitCommandHandler.CMD_RERUN_TESTS,
GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG: GdUnitCommandHandler.CMD_RERUN_TESTS_DEBUG,
GdUnitShortcut.ShortCut.STOP_TEST_RUN: GdUnitCommandHandler.CMD_STOP_TEST_RUN,
GdUnitShortcut.ShortCut.CREATE_TEST: GdUnitCommandHandler.CMD_CREATE_TESTCASE,
}
# the current test runner config
var _runner_config := GdUnitRunnerConfig.new()
# holds the current connected gdUnit runner client id
var _client_id: int
# if no debug mode we have an process id
var _current_runner_process_id: int = 0
# hold is current an test running
var _is_running: bool = false
# holds if the current running tests started in debug mode
var _running_debug_mode: bool
var _commands := {}
var _shortcuts := {}
var _commnand_mappings: Dictionary[String, GdUnitBaseCommand]= {}
var test_session_command := GdUnitCommandTestSession.new()
static func instance() -> GdUnitCommandHandler:
return GdUnitSingleton.instance("GdUnitCommandHandler", func() -> GdUnitCommandHandler: return GdUnitCommandHandler.new())
@@ -64,28 +13,23 @@ static func instance() -> GdUnitCommandHandler:
@warning_ignore("return_value_discarded")
func _init() -> void:
assert_shortcut_mappings(SETTINGS_SHORTCUT_MAPPING)
GdUnitSignals.instance().gdunit_event.connect(_on_event)
GdUnitSignals.instance().gdunit_client_connected.connect(_on_client_connected)
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()
var is_running := func(_script :Script) -> bool: return _is_running
var is_not_running := func(_script :Script) -> bool: return !_is_running
register_command(GdUnitCommand.new(CMD_RUN_OVERALL, is_not_running, cmd_run_overall.bind(true), GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL))
register_command(GdUnitCommand.new(CMD_RUN_TESTCASE, is_not_running, cmd_editor_run_test.bind(false), GdUnitShortcut.ShortCut.RUN_TESTCASE))
register_command(GdUnitCommand.new(CMD_RUN_TESTCASE_DEBUG, is_not_running, cmd_editor_run_test.bind(true), GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG))
register_command(GdUnitCommand.new(CMD_RUN_TESTSUITE, is_not_running, cmd_run_test_suites.bind(false), GdUnitShortcut.ShortCut.RUN_TESTSUITE))
register_command(GdUnitCommand.new(CMD_RUN_TESTSUITE_DEBUG, is_not_running, cmd_run_test_suites.bind(true), GdUnitShortcut.ShortCut.RUN_TESTSUITE_DEBUG))
register_command(GdUnitCommand.new(CMD_RERUN_TESTS, is_not_running, cmd_run.bind(false), GdUnitShortcut.ShortCut.RERUN_TESTS))
register_command(GdUnitCommand.new(CMD_RERUN_TESTS_DEBUG, is_not_running, cmd_run.bind(true), GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG))
register_command(GdUnitCommand.new(CMD_CREATE_TESTCASE, is_not_running, cmd_create_test, GdUnitShortcut.ShortCut.CREATE_TEST))
register_command(GdUnitCommand.new(CMD_STOP_TEST_RUN, is_running, cmd_stop.bind(_client_id), GdUnitShortcut.ShortCut.STOP_TEST_RUN))
_register_command(test_session_command)
_register_command(GdUnitCommandStopTestSession.new(test_session_command))
_register_command(GdUnitCommandInspectorRunTests.new(test_session_command))
_register_command(GdUnitCommandInspectorDebugTests.new(test_session_command))
_register_command(GdUnitCommandInspectorRerunTestsUntilFailure.new(test_session_command))
_register_command(GdUnitCommandInspectorTreeCollapse.new())
_register_command(GdUnitCommandInspectorTreeExpand.new())
_register_command(GdUnitCommandScriptEditorRunTests.new(test_session_command))
_register_command(GdUnitCommandScriptEditorDebugTests.new(test_session_command))
_register_command(GdUnitCommandScriptEditorCreateTest.new())
_register_command(GdUnitCommandFileSystemRunTests.new(test_session_command))
_register_command(GdUnitCommandFileSystemDebugTests.new(test_session_command))
_register_command(GdUnitCommandRunTestsOverall.new(test_session_command))
# schedule discover tests if enabled and running inside the editor
if Engine.is_editor_hint() and GdUnitSettings.is_test_discover_enabled():
@@ -96,298 +40,79 @@ func _init() -> void:
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
_commands.clear()
_shortcuts.clear()
for command: GdUnitBaseCommand in _commnand_mappings.values():
if Engine.is_editor_hint():
EditorInterface.get_command_palette().remove_command("GdUnit4/"+command.id)
command.free()
_commnand_mappings.clear()
func _do_process() -> void:
check_test_run_stopped_manually()
# is checking if the user has press the editor stop scene
func check_test_run_stopped_manually() -> void:
if is_test_running_but_stop_pressed():
# Do stop test execution when the user has stoped the main scene manually
if test_session_command._is_debug and test_session_command.is_running() and not EditorInterface.is_playing_scene():
if GdUnitSettings.is_verbose_assert_warnings():
push_warning("Test Runner scene was stopped manually, force stopping the current test run!")
cmd_stop(_client_id)
print_debug("Test Runner scene was stopped manually, force stopping the current test run!")
command_execute(GdUnitCommandStopTestSession.ID)
func is_test_running_but_stop_pressed() -> bool:
return _running_debug_mode and _is_running and not EditorInterface.is_playing_scene()
func assert_shortcut_mappings(mappings: Dictionary) -> void:
for shortcut: int in GdUnitShortcut.ShortCut.values():
assert(mappings.values().has(shortcut), "missing settings mapping for shortcut '%s'!" % GdUnitShortcut.ShortCut.keys()[shortcut])
func init_shortcuts() -> void:
for shortcut: int in GdUnitShortcut.ShortCut.values():
if shortcut == GdUnitShortcut.ShortCut.NONE:
continue
var property_name: String = SETTINGS_SHORTCUT_MAPPING.find_key(shortcut)
var property := GdUnitSettings.get_property(property_name)
var keys := GdUnitShortcut.default_keys(shortcut)
if property != null:
keys = property.value()
var inputEvent := create_shortcut_input_even(keys)
register_shortcut(shortcut, inputEvent)
func create_shortcut_input_even(key_codes: PackedInt32Array) -> InputEventKey:
var inputEvent := InputEventKey.new()
inputEvent.pressed = true
for key_code in key_codes:
match key_code:
KEY_ALT:
inputEvent.alt_pressed = true
KEY_SHIFT:
inputEvent.shift_pressed = true
KEY_CTRL:
inputEvent.ctrl_pressed = true
_:
inputEvent.keycode = key_code as Key
inputEvent.physical_keycode = key_code as Key
return inputEvent
func register_shortcut(p_shortcut: GdUnitShortcut.ShortCut, p_input_event: InputEvent) -> void:
GdUnitTools.prints_verbose("register shortcut: '%s' to '%s'" % [GdUnitShortcut.ShortCut.keys()[p_shortcut], p_input_event.as_text()])
var shortcut := Shortcut.new()
shortcut.set_events([p_input_event])
var command_name := get_shortcut_command(p_shortcut)
_shortcuts[p_shortcut] = GdUnitShortcutAction.new(p_shortcut, shortcut, command_name)
func get_shortcut(shortcut_type: GdUnitShortcut.ShortCut) -> Shortcut:
return get_shortcut_action(shortcut_type).shortcut
func get_shortcut_action(shortcut_type: GdUnitShortcut.ShortCut) -> GdUnitShortcutAction:
return _shortcuts.get(shortcut_type)
func get_shortcut_command(p_shortcut: GdUnitShortcut.ShortCut) -> String:
return CommandMapping.get(p_shortcut, "unknown command")
func register_command(p_command: GdUnitCommand) -> void:
_commands[p_command.name] = p_command
func command(cmd_name: String) -> GdUnitCommand:
return _commands.get(cmd_name)
func cmd_run_test_suites(scripts: Array[Script], debug: bool, rerun := false) -> void:
# Update test discovery
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverStart.new())
var tests_to_execute: Array[GdUnitTestCase] = []
for script in scripts:
GdUnitTestDiscoverer.discover_tests(script, func(test_case: GdUnitTestCase) -> void:
tests_to_execute.append(test_case)
GdUnitTestDiscoverSink.discover(test_case)
)
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverEnd.new(0, 0))
GdUnitTestDiscoverer.console_log_discover_results(tests_to_execute)
# create new runner runner_config for fresh run otherwise use saved one
if not rerun:
var result := _runner_config.clear()\
.add_test_cases(tests_to_execute)\
.save_config()
if result.is_error():
push_error(result.error_message())
return
cmd_run(debug)
func cmd_run_test_case(script: Script, test_case: String, test_param_index: int, debug: bool, rerun := false) -> void:
# Update test discovery
var tests_to_execute: Array[GdUnitTestCase] = []
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverStart.new())
GdUnitTestDiscoverer.discover_tests(script, func(test: GdUnitTestCase) -> void:
# We filter for a single test
if test.test_name == test_case:
# We only add selected parameterized test to the execution list
if test_param_index == -1:
tests_to_execute.append(test)
elif test.attribute_index == test_param_index:
tests_to_execute.append(test)
GdUnitTestDiscoverSink.discover(test)
)
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverEnd.new(0, 0))
GdUnitTestDiscoverer.console_log_discover_results(tests_to_execute)
# create new runner config for fresh run otherwise use saved one
if not rerun:
var result := _runner_config.clear()\
.add_test_cases(tests_to_execute)\
.save_config()
if result.is_error():
push_error(result.error_message())
return
cmd_run(debug)
func cmd_run_tests(tests_to_execute: Array[GdUnitTestCase], debug: bool) -> void:
# Save tests to runner config before execute
var result := _runner_config.clear()\
.add_test_cases(tests_to_execute)\
.save_config()
if result.is_error():
push_error(result.error_message())
func command_icon(command_id: String) -> Texture2D:
if not _commnand_mappings.has(command_id):
push_error("GdUnitCommandHandler:command_icon(): No command id '%s' is registered." % command_id)
print_stack()
return
cmd_run(debug)
return _commnand_mappings[command_id].icon
func cmd_run_overall(debug: bool) -> void:
var tests_to_execute := await GdUnitTestDiscoverer.run()
var result := _runner_config.clear()\
.add_test_cases(tests_to_execute)\
.save_config()
if result.is_error():
push_error(result.error_message())
func command_shortcut(command_id: String) -> Shortcut:
if not _commnand_mappings.has(command_id):
push_error("GdUnitCommandHandler:command_shortcut(): No command id '%s' is registered." % command_id)
print_stack()
return
cmd_run(debug)
return _commnand_mappings[command_id].shortcut
func cmd_run(debug: bool) -> void:
# don't start is already running
if _is_running:
func command_execute(...parameters: Array) -> void:
if parameters.is_empty():
push_error("Invalid arguments used on CommandHandler:execute()! Expecting [<command_id, args...>]")
print_stack()
return
# save current selected excution 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())
var command_id: String = parameters.pop_front()
if not _commnand_mappings.has(command_id):
push_error("GdUnitCommandHandler:command_execute(): No command id '%s' is registered." % command_id)
print_stack()
return
# before start we have to save all changes
ScriptEditorControls.save_all_open_script()
gdunit_runner_start.emit()
_current_runner_process_id = -1
_running_debug_mode = debug
if debug:
run_debug_mode()
else:
run_release_mode()
await _commnand_mappings[command_id].callv("execute", parameters)
func cmd_stop(client_id: int) -> void:
# don't stop if is already stopped
if not _is_running:
func _register_command(command: GdUnitBaseCommand) -> void:
# first verify the command is not already registerd
if _commnand_mappings.has(command.id):
push_error("GdUnitCommandHandler:_register_command(): Command with id '%s' is already registerd!" % command.id)
return
_is_running = false
gdunit_runner_stop.emit(client_id)
if _running_debug_mode:
EditorInterface.stop_playing_scene()
elif _current_runner_process_id > 0:
if OS.is_process_running(_current_runner_process_id):
var result := OS.kill(_current_runner_process_id)
if result != OK:
push_error("ERROR checked stopping GdUnit Test Runner. error code: %s" % result)
_current_runner_process_id = -1
func cmd_editor_run_test(debug: bool) -> void:
if is_active_script_editor():
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:
var func_name := result.get_string(2).strip_edges()
if func_name.begins_with("test_"):
cmd_run_test_case(active_script(), func_name, -1, debug)
return
# otherwise run the full test suite
var selected_test_suites: Array[Script] = [active_script()]
cmd_run_test_suites(selected_test_suites, debug)
func cmd_create_test() -> void:
if not is_active_script_editor():
return
var cursor_line := active_base_editor().get_caret_line()
var result := GdUnitTestSuiteBuilder.create(active_script(), cursor_line)
if result.is_error():
# show error dialog
push_error("Failed to create test case: %s" % result.error_message())
return
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)
_commnand_mappings[command.id] = command
if Engine.is_editor_hint():
EditorInterface.get_base_control().add_child(command)
EditorInterface.get_command_palette().add_command(command.id, "GdUnit4/"+command.id, command.execute, command.shortcut.get_as_text() if command.shortcut else "None")
func cmd_discover_tests() -> void:
await GdUnitTestDiscoverer.run()
func run_debug_mode() -> void:
EditorInterface.play_custom_scene("res://addons/gdUnit4/src/core/runners/GdUnitTestRunner.tscn")
_is_running = true
func run_release_mode() -> void:
var arguments := Array()
if OS.is_stdout_verbose():
arguments.append("--verbose")
arguments.append("--no-window")
arguments.append("--path")
arguments.append(ProjectSettings.globalize_path("res://"))
arguments.append("res://addons/gdUnit4/src/core/runners/GdUnitTestRunner.tscn")
_current_runner_process_id = OS.create_process(OS.get_executable_path(), arguments, false);
_is_running = true
func is_active_script_editor() -> bool:
return EditorInterface.get_script_editor().get_current_editor() != null
func active_base_editor() -> TextEdit:
return EditorInterface.get_script_editor().get_current_editor().get_base_editor()
func active_script() -> Script:
return EditorInterface.get_script_editor().get_current_script()
################################################################################
# signals handles
################################################################################
func _on_event(event: GdUnitEvent) -> void:
if event.type() == GdUnitEvent.SESSION_CLOSE:
cmd_stop(_client_id)
func _on_stop_pressed() -> void:
cmd_stop(_client_id)
func _on_run_pressed(debug := false) -> void:
cmd_run(debug)
func _on_run_overall_pressed(_debug := false) -> void:
cmd_run_overall(true)
command_execute(GdUnitCommandStopTestSession.ID)
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 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()])
var action := get_shortcut_action(shortcut)
if action != null:
action.update_shortcut(input_event)
else:
register_shortcut(shortcut, input_event)
for command: GdUnitBaseCommand in _commnand_mappings.values():
command.update_shortcut()
if property.name() == GdUnitSettings.TEST_DISCOVER_ENABLED:
var timer :SceneTreeTimer = (Engine.get_main_loop() as SceneTree).create_timer(3)
@warning_ignore("return_value_discarded")
@@ -397,12 +122,5 @@ func _on_settings_changed(property: GdUnitProperty) -> void:
################################################################################
# Network stuff
################################################################################
func _on_client_connected(client_id: int) -> void:
_client_id = client_id
func _on_client_disconnected(client_id: int) -> void:
# only stops is not in debug mode running and the current client
if not _running_debug_mode and _client_id == client_id:
cmd_stop(client_id)
_client_id = -1
func _on_client_disconnected(_client_id: int) -> void:
command_execute(GdUnitCommandStopTestSession.ID)

View File

@@ -1 +1 @@
uid://dooc00u4rahqp
uid://c78scrs3jstsk

View File

@@ -0,0 +1,27 @@
class_name GdUnitCommandInspectorDebugTests
extends GdUnitBaseCommand
const InspectorTreeMainPanel := preload("res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd")
const ID := "Debug Inspector Tests"
var _test_session_command: GdUnitCommandTestSession
func _init(test_session_command: GdUnitCommandTestSession) -> void:
super(ID, GdUnitShortcut.ShortCut.RERUN_TESTS_DEBUG)
icon = GdUnitUiTools.get_icon("PlayStart")
_test_session_command = test_session_command
func is_running() -> bool:
return _test_session_command.is_running()
func execute(..._parameters: Array) -> void:
var base_control := EditorInterface.get_base_control()
var inspector: InspectorTreeMainPanel = base_control.get_meta("GdUnit4Inspector")
var selected_item := inspector._tree.get_selected()
var tests_to_execute := inspector.collect_test_cases(selected_item)
_test_session_command.execute(tests_to_execute, true)

View File

@@ -0,0 +1 @@
uid://cgge1kdbo3d3s

View File

@@ -0,0 +1,57 @@
class_name GdUnitCommandInspectorRerunTestsUntilFailure
extends GdUnitBaseCommand
signal session_closed()
const InspectorTreeMainPanel := preload("res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd")
const ID := "Rerun Inspector Tests Until Failure"
var _test_session_command: GdUnitCommandTestSession
var _current_execution_count := 0
func _init(test_session_command: GdUnitCommandTestSession) -> void:
super(ID, GdUnitShortcut.ShortCut.RERUN_TESTS_UNTIL_FAILURE)
icon = GdUnitUiTools.get_icon("Play")
_test_session_command = test_session_command
func is_running() -> bool:
return _test_session_command.is_running()
func execute(..._parameters: Array) -> void:
var base_control := EditorInterface.get_base_control()
var inspector: InspectorTreeMainPanel = base_control.get_meta("GdUnit4Inspector")
var selected_item := inspector._tree.get_selected()
var tests_to_execute := inspector.collect_test_cases(selected_item)
var rerun_until_failure_count := GdUnitSettings.get_rerun_max_retries()
var saved_settings := ProjectSettings.get_setting(GdUnitSettings.TEST_FLAKY_CHECK)
ProjectSettings.set_setting(GdUnitSettings.TEST_FLAKY_CHECK, false)
GdUnitSignals.instance().gdunit_event.connect(_on_test_event)
_current_execution_count = 0
_test_session_command._is_fail_fast = true
while _current_execution_count < rerun_until_failure_count:
_test_session_command.execute(tests_to_execute, true)
await session_closed
_test_session_command._is_fail_fast = false
ProjectSettings.set_setting(GdUnitSettings.TEST_FLAKY_CHECK, saved_settings)
GdUnitSignals.instance().gdunit_event.disconnect(_on_test_event)
func _on_test_event(event: GdUnitEvent) -> void:
if event.type() == GdUnitEvent.SESSION_START:
_current_execution_count += 1
GdUnitSignals.instance().gdunit_message.emit("[color=RED]Execution Mode: ReRun until failure! (iteration %d)[/color]" % _current_execution_count)
if event.type() == GdUnitEvent.SESSION_CLOSE:
session_closed.emit()
if event.type() == GdUnitEvent.TESTCASE_AFTER:
if not event.is_success():
GdUnitSignals.instance().gdunit_message.emit(" [color=RED](iteration: %d)[/color]" % _current_execution_count)
_current_execution_count = 9999

View File

@@ -0,0 +1 @@
uid://djooccc2lximb

View File

@@ -0,0 +1,26 @@
class_name GdUnitCommandInspectorRunTests
extends GdUnitBaseCommand
const InspectorTreeMainPanel := preload("res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd")
const ID := "Run Inspector Tests"
var _test_session_command: GdUnitCommandTestSession
func _init(test_session_command: GdUnitCommandTestSession) -> void:
super(ID, GdUnitShortcut.ShortCut.RERUN_TESTS)
icon = GdUnitUiTools.get_icon("Play")
_test_session_command = test_session_command
func is_running() -> bool:
return _test_session_command.is_running()
func execute(..._parameters: Array) -> void:
var base_control := EditorInterface.get_base_control()
var inspector: InspectorTreeMainPanel = base_control.get_meta("GdUnit4Inspector")
var selected_item := inspector._tree.get_selected()
var tests_to_execute := inspector.collect_test_cases(selected_item)
_test_session_command.execute(tests_to_execute, false)

View File

@@ -0,0 +1 @@
uid://4v3l7qmq7kug

View File

@@ -0,0 +1,26 @@
class_name GdUnitCommandInspectorTreeCollapse
extends GdUnitBaseCommand
const InspectorTreeMainPanel := preload("res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd")
const ID := "Inspector Tree Collapse"
func _init() -> void:
super(ID, GdUnitShortcut.ShortCut.NONE)
icon = GdUnitUiTools.get_icon("CollapseTree")
func is_running() -> bool:
return false
func execute(..._parameters: Array) -> void:
var inspector: InspectorTreeMainPanel = EditorInterface.get_base_control().get_meta("GdUnit4Inspector")
var selected_item := inspector._tree.get_selected()
if selected_item == null:
selected_item = inspector._tree.get_root()
else:
selected_item = selected_item.get_parent()
inspector.do_collapse_all(false, selected_item)
inspector.do_collapse_all(true, selected_item)

View File

@@ -0,0 +1 @@
uid://kwhwh58fwv7k

View File

@@ -0,0 +1,25 @@
class_name GdUnitCommandInspectorTreeExpand
extends GdUnitBaseCommand
const InspectorTreeMainPanel := preload("res://addons/gdUnit4/src/ui/parts/InspectorTreeMainPanel.gd")
const ID := "Inspector Tree Expand"
func _init() -> void:
super(ID, GdUnitShortcut.ShortCut.NONE)
icon = GdUnitUiTools.get_icon("ExpandTree")
func is_running() -> bool:
return false
func execute(..._parameters: Array) -> void:
var inspector: InspectorTreeMainPanel = EditorInterface.get_base_control().get_meta("GdUnit4Inspector")
var selected_item := inspector._tree.get_selected()
if selected_item == null:
selected_item = inspector._tree.get_root()
else:
selected_item = selected_item.get_parent()
inspector.do_collapse_all(false, selected_item)

View File

@@ -0,0 +1 @@
uid://bebld08cdue37

View File

@@ -0,0 +1,22 @@
class_name GdUnitCommandRunTestsOverall
extends GdUnitBaseCommand
const ID := "Run Tests Overall"
var _test_session_command: GdUnitCommandTestSession
func _init(test_session_command: GdUnitCommandTestSession) -> void:
super(ID, GdUnitShortcut.ShortCut.RUN_TESTS_OVERALL)
icon = GdUnitUiTools.get_run_overall_icon()
_test_session_command = test_session_command
func is_running() -> bool:
return _test_session_command.is_running()
func execute(..._parameters: Array) -> void:
var tests_to_execute := await GdUnitTestDiscoverer.run()
_test_session_command.execute(tests_to_execute, true)

View File

@@ -0,0 +1 @@
uid://cxw4tubqwvd0l

View File

@@ -0,0 +1,56 @@
@abstract class_name GdUnitCommandScriptEditor
extends GdUnitBaseCommand
var _test_session_command: GdUnitCommandTestSession
func _init(p_id: String, p_shortcut: GdUnitShortcut.ShortCut, test_session_command: GdUnitCommandTestSession) -> void:
super(p_id, p_shortcut)
_test_session_command = test_session_command
func is_running() -> bool:
return _test_session_command.is_running()
func execute_tests(with_debug: bool) -> void:
var selected_tests := PackedStringArray()
if _is_active_script_editor():
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:
var func_name := result.get_string(2).strip_edges()
if func_name.begins_with("test_"):
selected_tests.append(func_name)
var tests_to_execute := _collect_tests(_active_script(), selected_tests)
_test_session_command.execute(tests_to_execute, with_debug)
func _collect_tests(script: Script, tests: PackedStringArray) -> Array[GdUnitTestCase]:
# Update test discovery
var tests_to_execute: Array[GdUnitTestCase] = []
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverStart.new())
GdUnitTestDiscoverer.discover_tests(script, func(test: GdUnitTestCase) -> void:
if tests.is_empty() or tests.has(test.test_name):
tests_to_execute.append(test)
GdUnitTestDiscoverSink.discover(test)
)
GdUnitSignals.instance().gdunit_event.emit(GdUnitEventTestDiscoverEnd.new(0, 0))
GdUnitTestDiscoverer.console_log_discover_results(tests_to_execute)
return tests_to_execute
func _is_active_script_editor() -> bool:
return EditorInterface.get_script_editor().get_current_editor() != null
func _active_base_editor() -> TextEdit:
return EditorInterface.get_script_editor().get_current_editor().get_base_editor()
func _active_script() -> Script:
return EditorInterface.get_script_editor().get_current_script()

View File

@@ -0,0 +1 @@
uid://b8ed2knx2w43g

View File

@@ -0,0 +1,41 @@
class_name GdUnitCommandScriptEditorCreateTest
extends GdUnitBaseCommand
const ID := "Create Test"
func _init() -> void:
super(ID, GdUnitShortcut.ShortCut.CREATE_TEST)
icon = GdUnitUiTools.get_icon("New")
func is_running() -> bool:
return false
func execute(..._parameters: Array) -> void:
if not _is_active_script_editor():
return
var cursor_line := _active_base_editor().get_caret_line()
var result := GdUnitTestSuiteBuilder.create(_active_script(), cursor_line)
if result.is_error():
# show error dialog
push_error("Failed to create test case: %s" % result.error_message())
return
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 _is_active_script_editor() -> bool:
return EditorInterface.get_script_editor().get_current_editor() != null
func _active_base_editor() -> TextEdit:
return EditorInterface.get_script_editor().get_current_editor().get_base_editor()
func _active_script() -> Script:
return EditorInterface.get_script_editor().get_current_script()

View File

@@ -0,0 +1 @@
uid://be5yc60ppts7a

View File

@@ -0,0 +1,14 @@
class_name GdUnitCommandScriptEditorDebugTests
extends GdUnitCommandScriptEditor
const ID := "Debug ScriptEditor Tests"
func _init(test_session_command: GdUnitCommandTestSession) -> void:
super(ID, GdUnitShortcut.ShortCut.RUN_TESTCASE_DEBUG, test_session_command)
icon = GdUnitUiTools.get_icon("PlayStart")
func execute(..._parameters: Array) -> void:
execute_tests(true)

View File

@@ -0,0 +1 @@
uid://8ccganhkrt2o

View File

@@ -0,0 +1,14 @@
class_name GdUnitCommandScriptEditorRunTests
extends GdUnitCommandScriptEditor
const ID := "Run ScriptEditor Tests"
func _init(test_session_command: GdUnitCommandTestSession) -> void:
super(ID, GdUnitShortcut.ShortCut.RUN_TESTCASE, test_session_command)
icon = GdUnitUiTools.get_icon("Play")
func execute(..._parameters: Array) -> void:
execute_tests(false)

View File

@@ -0,0 +1 @@
uid://cnh8uhd8gnxxc

View File

@@ -0,0 +1,21 @@
class_name GdUnitCommandStopTestSession
extends GdUnitBaseCommand
const ID := "Stop Test Session"
var _test_session_command: GdUnitCommandTestSession
func _init(test_session_command: GdUnitCommandTestSession) -> void:
super(ID, GdUnitShortcut.ShortCut.STOP_TEST_RUN)
icon = GdUnitUiTools.get_icon("Stop")
_test_session_command = test_session_command
func is_running() -> bool:
return _test_session_command.is_running()
func execute(..._parameters: Array) -> void:
await _test_session_command.stop()

View File

@@ -0,0 +1 @@
uid://csffxdh5jm5rn

View File

@@ -0,0 +1,124 @@
class_name GdUnitCommandTestSession
extends GdUnitBaseCommand
const ID := "Start Test Session"
var _current_runner_process_id: int
var _is_running: bool
var _is_debug: bool
var _is_fail_fast: bool
func _init() -> void:
super(ID, GdUnitShortcut.ShortCut.NONE)
_is_running = false
_is_fail_fast = false
func is_running() -> bool:
return _is_running
func stop() -> void:
if not is_running():
return
_is_running = false
if _is_debug:
force_pause_scene()
GdUnitSignals.instance().gdunit_test_session_terminate.emit()
# Give the API time to commit terminate to the client
await get_tree().create_timer(.5).timeout
if _is_debug and EditorInterface.is_playing_scene():
EditorInterface.stop_playing_scene()
# We need finaly to send the test session close event because the current run is hard aborted.
GdUnitSignals.instance().gdunit_event.emit(GdUnitSessionClose.new())
elif OS.is_process_running(_current_runner_process_id):
var result := OS.kill(_current_runner_process_id)
if result != OK:
push_error("ERROR checked stopping GdUnit Test Runner. error code: %s" % result)
_current_runner_process_id = -1
# We need finaly to send the test session close event because the current run is hard aborted.
GdUnitSignals.instance().gdunit_event.emit(GdUnitSessionClose.new())
## Forces the running scene to unpause when the debugger hits a breakpoint.[br]
## [br]
## When the Godot debugger stops at a breakpoint during test execution, it blocks[br]
## the main thread. This prevents signals and TCP communications from being processed,[br]
## which can cause GdUnit4 tests to hang or fail to communicate properly with the[br]
## test runner. This function programmatically unpauses the scene to restore[br]
## main thread execution while maintaining debugger functionality. [br]
## [br]
## [b]Technical Background:[/b][br]
## - Debugger breakpoints freeze the main thread to allow inspection[br]
## - Frozen main thread blocks signal processing and network communications[br]
## - GdUnit4 requires active signal/TCP processing for test coordination[br]
## - This function finds and triggers the editor's pause button to resume execution[br]
## [br]
## [b]How It Works:[/b][br]
## 1. Locates the EditorRunBar in the Godot editor UI hierarchy[br]
## 2. Searches for the pause button by matching its icon[br]
## 3. Unpresses the button if it's currently pressed (paused state)[br]
## 4. Manually triggers the button's connected callbacks to resume execution[br]
func force_pause_scene() -> bool:
var nodes := EditorInterface.get_base_control().find_children("*", "EditorRunBar", true, false)
if nodes.size() != 1:
push_error("GdUnitCommandTestSession:force_pause_scene() Can't find Editor component 'EditorRunBar'")
return false
var editor_run_bar := nodes[0]
var containers := editor_run_bar.find_children("*", "HBoxContainer", true, false)
var pause_icon := GdUnitUiTools.get_icon("Pause")
for container in containers:
for child in container.get_children():
if child is Button:
var button: Button = child
if pause_icon == button.icon:
button.set_pressed(false)
var connected_signals := button.get_signal_connection_list("pressed")
if not connected_signals.is_empty():
for signal_ in connected_signals:
var cb: Callable = signal_["callable"]
cb.call()
return true
push_error("GdUnitCommandTestSession:force_pause_scene() Can't find Editor component 'EditorRunBar'")
return false
func execute(...parameters: Array) -> void:
var tests_to_execute: Array[GdUnitTestCase] = parameters[0]
_is_debug = parameters[1]
_prepare_test_session(tests_to_execute)
if _is_debug:
EditorInterface.play_custom_scene("res://addons/gdUnit4/src/core/runners/GdUnitTestRunner.tscn")
else:
var arguments := Array()
if OS.is_stdout_verbose():
arguments.append("--verbose")
arguments.append("--no-window")
arguments.append("--path")
arguments.append(ProjectSettings.globalize_path("res://"))
arguments.append("res://addons/gdUnit4/src/core/runners/GdUnitTestRunner.tscn")
_current_runner_process_id = OS.create_process(OS.get_executable_path(), arguments, false);
_is_running = true
func _prepare_test_session(tests_to_execute: Array[GdUnitTestCase]) -> void:
var server_port: int = Engine.get_meta("gdunit_server_port")
var result := GdUnitRunnerConfig.new() \
.set_server_port(server_port) \
.do_fail_fast(_is_fail_fast) \
.add_test_cases(tests_to_execute) \
.save_config()
if result.is_error():
push_error(result.error_message())
return
# before start we have to save all scrpt changes
ScriptEditorControls.save_all_open_script()

View File

@@ -0,0 +1 @@
uid://ch3v5vvl0bt8i

View File

@@ -11,6 +11,7 @@ enum ShortCut {
RUN_TESTSUITE_DEBUG,
RERUN_TESTS,
RERUN_TESTS_DEBUG,
RERUN_TESTS_UNTIL_FAILURE,
STOP_TEST_RUN,
CREATE_TEST,
}
@@ -19,30 +20,50 @@ const DEFAULTS_MACOS := {
ShortCut.NONE : [],
ShortCut.RUN_TESTCASE : [Key.KEY_META, Key.KEY_ALT, Key.KEY_F5],
ShortCut.RUN_TESTCASE_DEBUG : [Key.KEY_META, Key.KEY_ALT, Key.KEY_F6],
ShortCut.RUN_TESTSUITE : [Key.KEY_META, Key.KEY_ALT, Key.KEY_F5],
ShortCut.RUN_TESTSUITE_DEBUG : [Key.KEY_META, Key.KEY_ALT, Key.KEY_F6],
ShortCut.RUN_TESTSUITE : [Key.KEY_ALT, Key.KEY_META, Key.KEY_F5],
ShortCut.RUN_TESTSUITE_DEBUG : [Key.KEY_ALT, Key.KEY_META, Key.KEY_F6],
ShortCut.RUN_TESTS_OVERALL : [Key.KEY_ALT, Key.KEY_F7],
ShortCut.STOP_TEST_RUN : [Key.KEY_ALT, Key.KEY_F8],
ShortCut.RERUN_TESTS : [Key.KEY_ALT, Key.KEY_F5],
ShortCut.RERUN_TESTS_DEBUG : [Key.KEY_ALT, Key.KEY_F6],
ShortCut.RERUN_TESTS_UNTIL_FAILURE : [Key.KEY_ALT, Key.KEY_META, Key.KEY_F5],
ShortCut.CREATE_TEST : [Key.KEY_META, Key.KEY_ALT, Key.KEY_F10],
}
const DEFAULTS_WINDOWS := {
ShortCut.NONE : [],
ShortCut.RUN_TESTCASE : [Key.KEY_CTRL, Key.KEY_ALT, Key.KEY_F5],
ShortCut.RUN_TESTCASE_DEBUG : [Key.KEY_CTRL,Key.KEY_ALT, Key.KEY_F6],
ShortCut.RUN_TESTSUITE : [Key.KEY_CTRL, Key.KEY_ALT, Key.KEY_F5],
ShortCut.RUN_TESTSUITE_DEBUG : [Key.KEY_CTRL,Key.KEY_ALT, Key.KEY_F6],
ShortCut.RUN_TESTCASE_DEBUG : [Key.KEY_CTRL, Key.KEY_ALT, Key.KEY_F6],
ShortCut.RUN_TESTSUITE : [Key.KEY_ALT, Key.KEY_SHIFT, Key.KEY_F5],
ShortCut.RUN_TESTSUITE_DEBUG : [Key.KEY_ALT, Key.KEY_SHIFT, Key.KEY_F6],
ShortCut.RUN_TESTS_OVERALL : [Key.KEY_ALT, Key.KEY_F7],
ShortCut.STOP_TEST_RUN : [Key.KEY_ALT, Key.KEY_F8],
ShortCut.RERUN_TESTS : [Key.KEY_ALT, Key.KEY_F5],
ShortCut.RERUN_TESTS_DEBUG : [Key.KEY_ALT, Key.KEY_F6],
ShortCut.RERUN_TESTS_UNTIL_FAILURE : [Key.KEY_CTRL, Key.KEY_ALT, Key.KEY_F5],
ShortCut.CREATE_TEST : [Key.KEY_CTRL, Key.KEY_ALT, Key.KEY_F10],
}
static func default_keys(shortcut :ShortCut) -> PackedInt32Array:
const SETTINGS_MAPPING: Dictionary[ShortCut, String] = {
ShortCut.RUN_TESTCASE : GdUnitSettings.SHORTCUT_EDITOR_RUN_TEST,
ShortCut.RUN_TESTCASE_DEBUG : GdUnitSettings.SHORTCUT_EDITOR_RUN_TEST_DEBUG,
ShortCut.CREATE_TEST : GdUnitSettings.SHORTCUT_EDITOR_CREATE_TEST,
ShortCut.RERUN_TESTS : GdUnitSettings.SHORTCUT_INSPECTOR_RERUN_TEST,
ShortCut.RERUN_TESTS_DEBUG : GdUnitSettings.SHORTCUT_INSPECTOR_RERUN_TEST_DEBUG,
ShortCut.RERUN_TESTS_UNTIL_FAILURE : GdUnitSettings.SHORTCUT_INSPECTOR_RERUN_TEST_UNTIL_FAILURE,
ShortCut.STOP_TEST_RUN : GdUnitSettings.SHORTCUT_INSPECTOR_RUN_TEST_STOP,
ShortCut.RUN_TESTS_OVERALL : GdUnitSettings.SHORTCUT_INSPECTOR_RUN_TEST_OVERALL,
ShortCut.RUN_TESTSUITE : GdUnitSettings.SHORTCUT_FILESYSTEM_RUN_TEST,
ShortCut.RUN_TESTSUITE_DEBUG : GdUnitSettings.SHORTCUT_FILESYSTEM_RUN_TEST_DEBUG
}
static func as_property(sortcut: ShortCut) -> String:
return SETTINGS_MAPPING[sortcut]
static func default_keys(shortcut: ShortCut) -> PackedInt32Array:
match OS.get_name().to_lower():
'windows':
return DEFAULTS_WINDOWS[shortcut]

View File

@@ -1 +1 @@
uid://bsg0clvy7wf0m
uid://xmx5cn8rhpa

View File

@@ -1,40 +0,0 @@
class_name GdUnitShortcutAction
extends RefCounted
func _init(p_type :GdUnitShortcut.ShortCut, p_shortcut :Shortcut, p_command :String) -> void:
assert(p_type != null, "missing parameter 'type'")
assert(p_shortcut != null, "missing parameter 'shortcut'")
assert(p_command != null, "missing parameter 'command'")
self.type = p_type
self.shortcut = p_shortcut
self.command = p_command
var type: GdUnitShortcut.ShortCut:
set(value):
type = value
get:
return type
var shortcut: Shortcut:
set(value):
shortcut = value
get:
return shortcut
var command: String:
set(value):
command = value
get:
return command
func update_shortcut(input_event: InputEventKey) -> void:
shortcut.set_events([input_event])
func _to_string() -> String:
return "GdUnitShortcutAction: %s (%s) -> %s" % [GdUnitShortcut.ShortCut.keys()[type], shortcut.get_as_text(), command]

View File

@@ -1 +0,0 @@
uid://cmlh3hniafm5s

View File

@@ -1 +1 @@
uid://d4lobvde8tufj
uid://db25rfeu4v25p

View File

@@ -1 +1 @@
uid://i4kgxeu6rjiv
uid://dwqwv278b5sji

View File

@@ -1 +1 @@
uid://cojycdwxjbkf3
uid://v1jl5pcf7err

View File

@@ -1 +1 @@
uid://ct0kk6824vhxf
uid://d3defrdskwh0y

View File

@@ -1 +1 @@
uid://uakc3vyaaagr
uid://xdtvoikcir7n

View File

@@ -132,7 +132,7 @@ func is_success() -> bool:
func is_warning() -> bool:
return _statistics.get(WARNINGS, false)
return _statistics.get(WARNINGS, false) or orphan_nodes() > 0
func is_failed() -> bool:

View File

@@ -1 +1 @@
uid://c4wkq83n4a4bk
uid://cgwcvamqj1uyi

View File

@@ -1 +1 @@
uid://c8t36rmkcsvqm
uid://tu5npta2qjg3

View File

@@ -1 +1 @@
uid://cg768i3qgef2x
uid://dxelfts5smut4

View File

@@ -1 +1 @@
uid://bt4blgp4lw3p0
uid://clm4vcs4ix5yc

View File

@@ -1 +1 @@
uid://npuh47e34ud2
uid://dyf62y71o1l8m

View File

@@ -1 +1 @@
uid://eqiw85rg4fgn
uid://dwnfnd5ttpt66

View File

@@ -1 +1 @@
uid://hpagtimkbhev
uid://b7xtnak82y5mh

View File

@@ -22,7 +22,6 @@ 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()
var _orphans := -1
var error_monitor: GodotGdErrorMonitor = null:
@@ -53,7 +52,11 @@ func _init(name: StringName, parent_context: GdUnitExecutionContext = null) -> v
_parent_context = parent_context
_timer = LocalTime.now()
_orphan_monitor = GdUnitOrphanNodesMonitor.new(name)
_orphan_monitor.start()
if parent_context != null:
parent_context._orphan_monitor.add_child_monitor(_orphan_monitor)
orphan_monitor_start()
_memory_observer = GdUnitMemoryObserver.new()
_report_collector = GdUnitTestReportCollector.new()
if parent_context != null:
@@ -61,6 +64,8 @@ func _init(name: StringName, parent_context: GdUnitExecutionContext = null) -> v
func dispose() -> void:
if test_suite != null:
test_suite.free()
_timer = null
_orphan_monitor = null
_report_collector = null
@@ -77,6 +82,11 @@ func dispose_sub_contexts() -> void:
_sub_context.clear()
func terminate() -> void:
if test_case:
test_case.do_terminate()
static func of(pe: GdUnitExecutionContext) -> GdUnitExecutionContext:
var context := GdUnitExecutionContext.new(pe._test_case_name, pe)
context._test_case_name = pe._test_case_name
@@ -84,16 +94,9 @@ static func of(pe: GdUnitExecutionContext) -> GdUnitExecutionContext:
return context
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, p_test_case: _TestCase) -> GdUnitExecutionContext:
assert(p_test_case, "test_case is null")
var context := GdUnitExecutionContext.new(p_test_case.get_name(), pe)
var context := GdUnitExecutionContext.new(p_test_case.test_name(), pe)
context.test_case = p_test_case
return context
@@ -124,7 +127,7 @@ func error_monitor_start() -> void:
func error_monitor_stop() -> void:
await error_monitor.scan()
error_monitor.stop()
for error_report in error_monitor.to_reports():
if error_report.is_error():
_report_collector.push_back(error_report)
@@ -134,6 +137,10 @@ func orphan_monitor_start() -> void:
_orphan_monitor.start()
func orphan_monitor_collect() -> void:
_orphan_monitor.collect()
func orphan_monitor_stop() -> void:
_orphan_monitor.stop()
@@ -165,8 +172,8 @@ func calculate_statistics(reports_: Array[GdUnitReport]) -> Dictionary:
var error_count := GdUnitTestReportCollector.count_errors(reports_)
var warn_count := GdUnitTestReportCollector.count_warnings(reports_)
var skip_count := GdUnitTestReportCollector.count_skipped(reports_)
var orphan_count := GdUnitTestReportCollector.count_orphans(reports_)
var is_failed := !is_success()
var orphan_count := _count_orphans()
var elapsed_time := _timer.elapsed_since_ms()
var retries := 1 if _parent_context == null else _sub_context.size()
# Mark as flaky if it is successful, but errors were counted
@@ -213,22 +220,6 @@ func is_interupted() -> bool:
return false if test_case == null else test_case.is_interupted()
func _count_orphans() -> int:
if _orphans != -1:
return _orphans
var orphans := 0
for c in _sub_context:
if _orphan_monitor.orphan_nodes() != c._orphan_monitor.orphan_nodes():
orphans += c._count_orphans()
_orphans = _orphan_monitor.orphan_nodes()
if _orphan_monitor.orphan_nodes() != orphans:
_orphans -= orphans
return _orphans
func sum(accum: int, number: int) -> int:
return accum + number
@@ -251,19 +242,33 @@ func gc(gc_orphan_check: GC_ORPHANS_CHECK = GC_ORPHANS_CHECK.NONE) -> void:
await _memory_observer.gc()
orphan_monitor_stop()
var orphans := _count_orphans()
match(gc_orphan_check):
GC_ORPHANS_CHECK.SUITE_HOOK_AFTER:
if orphans > 0:
reports().push_front(GdUnitReport.new() \
.create(GdUnitReport.WARN, 1, GdAssertMessages.orphan_detected_on_suite_setup(orphans)))
_orphan_monitor.collect()
var orphan_infos := _orphan_monitor.detected_orphans()
if orphan_infos.is_empty():
return
reports().push_front(GdUnitReport.new() \
.create(GdUnitReport.ORPHAN, 1, GdAssertMessages.orphan_detected_on_suite_setup(orphan_infos))
.with_current_value(orphan_infos.size()))
GC_ORPHANS_CHECK.TEST_HOOK_AFTER:
if orphans > 0:
_orphan_monitor.collect()
var orphans := _orphan_monitor.detected_orphans()
if not orphans.is_empty():
reports().push_front(GdUnitReport.new()\
.create(GdUnitReport.WARN, 1, GdAssertMessages.orphan_detected_on_test_setup(orphans)))
.create(GdUnitReport.ORPHAN, 1, GdAssertMessages.orphan_detected_on_test_setup(orphans))
.with_current_value(orphans.size()))
GC_ORPHANS_CHECK.TEST_CASE:
if orphans > 0:
var orphans := _orphan_monitor.detected_orphans()
if orphans.is_empty():
var orphans_count := _orphan_monitor.orphans_count()
if orphans_count > 0:
reports().push_front(GdUnitReport.new() \
.create(GdUnitReport.ORPHAN, test_case.line_number(), GdAssertMessages.orphan_warning(orphans_count))
.with_current_value(orphans_count))
else:
reports().push_front(GdUnitReport.new()\
.create(GdUnitReport.WARN, test_case.line_number(), GdAssertMessages.orphan_detected_on_test(orphans)))
.create(GdUnitReport.ORPHAN, test_case.line_number(), GdAssertMessages.orphan_detected_on_test(orphans))
.with_current_value(orphans.size()))

View File

@@ -1 +1 @@
uid://dm5otinunwsc1
uid://cbumcrr7mlbl2

View File

@@ -1 +1 @@
uid://ibpnqu61f7yw
uid://bgatcioxigsa3

View File

@@ -22,6 +22,10 @@ static func __filter_is_skipped(report :GdUnitReport) -> bool:
return report.is_skipped()
static func __filter_is_orphan(report: GdUnitReport) -> bool:
return report.is_orphan()
static func count_failures(reports_: Array[GdUnitReport]) -> int:
return reports_.filter(__filter_is_failure).size()
@@ -38,6 +42,17 @@ static func count_skipped(reports_: Array[GdUnitReport]) -> int:
return reports_.filter(__filter_is_skipped).size()
static func count_orphans(reports_: Array[GdUnitReport]) -> int:
var orphan_reports := reports_.filter(__filter_is_orphan)
if orphan_reports.is_empty():
return 0
## Collect orphan count from the reports
var orphans := 0
for report: GdUnitReport in orphan_reports:
orphans += report._current_value
return orphans
func has_failures() -> bool:
return _reports.any(__filter_is_failure)

View File

@@ -1 +1 @@
uid://cl13ejhh26vv7
uid://ds05ko0pefbe

View File

@@ -7,37 +7,45 @@ class_name GdUnitTestSuiteExecutor
var _assertions := GdUnitAssertions.new()
var _executeStage := GdUnitTestSuiteExecutionStage.new()
var _debug_mode : bool
var _terminated := false
func _init(debug_mode :bool = false) -> void:
func _init(debug_mode: bool = false) -> void:
_executeStage.set_debug_mode(debug_mode)
_debug_mode = debug_mode
GdUnitSignals.instance().gdunit_test_session_terminate.connect(_on_testsession_terminated)
func execute(test_suite :GdUnitTestSuite) -> void:
var orphan_detection_enabled := GdUnitSettings.is_verbose_orphans()
if not orphan_detection_enabled:
prints("!!! Reporting orphan nodes is disabled. Please check GdUnit settings.")
(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))
func _on_testsession_terminated() -> void:
_terminated = true
GdUnitThreadManager.interrupt()
func run_and_wait(tests: Array[GdUnitTestCase]) -> void:
if !_debug_mode:
GdUnitSignals.instance().gdunit_event.emit(GdUnitInit.new())
var orphan_detection_enabled := GdUnitSettings.is_verbose_orphans()
if not orphan_detection_enabled:
prints("!!! Reporting orphan nodes is disabled. Please check GdUnit settings.")
# first we group all tests by resource path
var grouped_by_suites := GdArrayTools.group_by(tests, func(test: GdUnitTestCase) -> String:
return test.suite_resource_path
)
var scanner := GdUnitTestSuiteScanner.new()
for suite_path: String in grouped_by_suites.keys():
if _terminated:
break
@warning_ignore("unsafe_call_argument")
var suite_tests: Array[GdUnitTestCase] = Array(grouped_by_suites[suite_path], TYPE_OBJECT, "RefCounted", GdUnitTestCase)
var script := GdUnitTestSuiteScanner.load_with_disabled_warnings(suite_path)
if script.get_class() == "GDScript":
var context := GdUnitExecutionContext.new(suite_path)
var test_suite := scanner.load_suite(script as GDScript, suite_tests)
await execute(test_suite)
context.test_suite = test_suite
(Engine.get_main_loop() as SceneTree).root.add_child(test_suite)
await _executeStage.execute(context)
context.dispose()
else:
await GdUnit4CSharpApiLoader.execute(suite_tests)
if !_debug_mode:

View File

@@ -1 +1 @@
uid://hl8otc6pepsh
uid://b45ihvou3jfe2

View File

@@ -1 +1 @@
uid://ddknkun7aw51d
uid://nhg6vtb5bgn0

View File

@@ -10,10 +10,11 @@ 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.before_test()
context.error_monitor_start()

View File

@@ -1 +1 @@
uid://c8gq3sb8q6xih
uid://dyr1fyhd3oi26

View File

@@ -1 +1 @@
uid://brfrhige0dbmm
uid://kkmwmd1wsfgp

View File

@@ -1 +1 @@
uid://vs73mmj8rsbs
uid://csqrlovyndxqm

View File

@@ -4,7 +4,7 @@ class_name GdUnitTestSuiteBeforeStage
extends IGdUnitExecutionStage
func _execute(context :GdUnitExecutionContext) -> void:
func _execute(context: GdUnitExecutionContext) -> void:
var test_suite := context.test_suite
fire_event(GdUnitEvent.new()\

View File

@@ -1 +1 @@
uid://ce78xguk84kwb
uid://bs0orggd2ej7y

View File

@@ -28,9 +28,10 @@ func _execute(context :GdUnitExecutionContext) -> void:
if not is_instance_valid(test_case):
continue
context.test_suite.set_active_test_case(test_case.test_name())
await _stage_test.execute(GdUnitExecutionContext.of_test_case(context, test_case))
var test_case_context := GdUnitExecutionContext.of_test_case(context, test_case)
await _stage_test.execute(test_case_context)
# stop on first error or if fail fast is enabled
if _fail_fast and not context.is_success():
if test_case.is_terminated() or (_fail_fast and not test_case_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
@@ -39,9 +40,8 @@ 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() as SceneTree).process_frame
context.test_suite.free()
context.dispose()
# clones a test suite and moves the test cases to new instance

View File

@@ -1 +1 @@
uid://bfbyfr8ocwivm
uid://rteudd6jx3n1

Some files were not shown because too many files have changed in this diff Show More