summaryrefslogtreecommitdiff
path: root/addons/gdUnit4/src/core/command/GdUnitCommandTestSession.gd
diff options
context:
space:
mode:
authorradiohotline <radoihotline@disroot.rg>2026-08-25 16:33:56 +0300
committerradiohotline <radoihotline@disroot.rg>2026-08-25 16:33:56 +0300
commita89e744ed0e8c759caf041a580112365bbfe4303 (patch)
treedd61e320904176995727e86a857d00c90d4933ba /addons/gdUnit4/src/core/command/GdUnitCommandTestSession.gd
parent6d53d8cac78250dc888aa49d5f9d4175c6652feb (diff)
escoria updateHEADmain
Diffstat (limited to 'addons/gdUnit4/src/core/command/GdUnitCommandTestSession.gd')
-rw-r--r--addons/gdUnit4/src/core/command/GdUnitCommandTestSession.gd124
1 files changed, 124 insertions, 0 deletions
diff --git a/addons/gdUnit4/src/core/command/GdUnitCommandTestSession.gd b/addons/gdUnit4/src/core/command/GdUnitCommandTestSession.gd
new file mode 100644
index 0000000..53d74ed
--- /dev/null
+++ b/addons/gdUnit4/src/core/command/GdUnitCommandTestSession.gd
@@ -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()