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:
@@ -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
|
||||
+57
@@ -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
|
||||
+1
@@ -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
|
||||
Reference in New Issue
Block a user