chore(client): add gdUnit4 test framework addon

Vendor gdUnit4 for Godot 4 client testing (D-030).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 16:52:00 +01:00
co-authored by Claude Opus 4.6
parent 3a2fe4bd71
commit 61e0762a84
979 changed files with 55221 additions and 0 deletions
@@ -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
@@ -0,0 +1 @@
uid://dqgwcpqwfd327
@@ -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)
@@ -0,0 +1 @@
uid://b01ohem73ksbu
@@ -0,0 +1,17 @@
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:
if parameters.is_empty():
return
var selected_paths: PackedStringArray = parameters[0]
execute_tests(selected_paths, true)
@@ -0,0 +1 @@
uid://btp0ve48jv8sq
@@ -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)
@@ -0,0 +1 @@
uid://mf76re4jbx4r
@@ -0,0 +1,126 @@
class_name GdUnitCommandHandler
extends Object
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
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())
@warning_ignore("return_value_discarded")
func _init() -> void:
GdUnitSignals.instance().gdunit_event.connect(_on_event)
GdUnitSignals.instance().gdunit_client_disconnected.connect(_on_client_disconnected)
GdUnitSignals.instance().gdunit_settings_changed.connect(_on_settings_changed)
_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():
var timer :SceneTreeTimer = (Engine.get_main_loop() as SceneTree).create_timer(5)
@warning_ignore("return_value_discarded")
timer.timeout.connect(cmd_discover_tests)
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
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:
# 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():
print_debug("Test Runner scene was stopped manually, force stopping the current test run!")
command_execute(GdUnitCommandStopTestSession.ID)
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
return _commnand_mappings[command_id].icon
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
return _commnand_mappings[command_id].shortcut
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
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
await _commnand_mappings[command_id].callv("execute", parameters)
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
_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()
################################################################################
# signals handles
################################################################################
func _on_event(event: GdUnitEvent) -> void:
if event.type() == GdUnitEvent.SESSION_CLOSE:
command_execute(GdUnitCommandStopTestSession.ID)
func _on_settings_changed(property: GdUnitProperty) -> void:
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")
timer.timeout.connect(cmd_discover_tests)
################################################################################
# Network stuff
################################################################################
func _on_client_disconnected(_client_id: int) -> void:
command_execute(GdUnitCommandStopTestSession.ID)
@@ -0,0 +1 @@
uid://dvl8go6w4cc5a
@@ -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)
@@ -0,0 +1 @@
uid://c8d86bstesx4p
@@ -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: bool = 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
@@ -0,0 +1 @@
uid://d07gpq6ui186u
@@ -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)
@@ -0,0 +1 @@
uid://b8w0t52ri4juf
@@ -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)
@@ -0,0 +1 @@
uid://c83ss4xfgjla3
@@ -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)
@@ -0,0 +1 @@
uid://dlm3fpsg6nxbo
@@ -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)
@@ -0,0 +1 @@
uid://ymhoq2h1i4nd
@@ -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()
@@ -0,0 +1 @@
uid://by2yyuh1wytis
@@ -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()
@@ -0,0 +1 @@
uid://r17mdgq0teyv
@@ -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)
@@ -0,0 +1 @@
uid://cox6kko7o4jbq
@@ -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)
@@ -0,0 +1 @@
uid://b7i5o71aweaq8
@@ -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()
@@ -0,0 +1 @@
uid://igl6l837i488
@@ -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()
@@ -0,0 +1 @@
uid://bjvce3m7toyxj
@@ -0,0 +1,73 @@
class_name GdUnitShortcut
extends RefCounted
enum ShortCut {
NONE,
RUN_TESTS_OVERALL,
RUN_TESTCASE,
RUN_TESTCASE_DEBUG,
RUN_TESTSUITE,
RUN_TESTSUITE_DEBUG,
RERUN_TESTS,
RERUN_TESTS_DEBUG,
RERUN_TESTS_UNTIL_FAILURE,
STOP_TEST_RUN,
CREATE_TEST,
}
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_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_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],
}
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]
'macos':
return DEFAULTS_MACOS[shortcut]
_:
return DEFAULTS_WINDOWS[shortcut]
@@ -0,0 +1 @@
uid://crywx1snsjiti