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

@@ -40,11 +40,10 @@ jobs:
- name: Run tests - name: Run tests
uses: godot-gdunit-labs/gdUnit4-action@v1 uses: godot-gdunit-labs/gdUnit4-action@v1
with: with:
godot-version: '4.5.1' godot-version: '4.6.0'
godot-net: true godot-net: true
godot-force-mono: true godot-force-mono: true
dotnet-version: 'net9.0' dotnet-version: 'net9.0'
version: 'v6.0.3'
paths: | paths: |
res://tests/ res://tests/
timeout: 1 timeout: 1

View File

@@ -61,11 +61,10 @@ jobs:
- name: Run tests - name: Run tests
uses: godot-gdunit-labs/gdUnit4-action@v1 uses: godot-gdunit-labs/gdUnit4-action@v1
with: with:
godot-version: '4.5.1' godot-version: '4.6.0'
godot-net: true godot-net: true
godot-force-mono: true godot-force-mono: true
dotnet-version: 'net9.0' dotnet-version: 'net9.0'
version: 'v6.0.3'
paths: | paths: |
res://tests/ res://tests/
timeout: 1 timeout: 1
@@ -84,7 +83,7 @@ jobs:
- BumpTag - BumpTag
- Test # Wait for tests to finish - Test # Wait for tests to finish
container: container:
image: barichello/godot-ci:mono-4.5 image: barichello/godot-ci:mono-4.6
steps: steps:
- name: Install node, curl and zip - name: Install node, curl and zip
@@ -110,10 +109,6 @@ jobs:
run: | run: |
rm -rf ${{ gitea.workspace }}/addons/gdUnit4 rm -rf ${{ gitea.workspace }}/addons/gdUnit4
- name: Import resources and build solution
run: |
godot --headless --editor --build-solutions --quit --import --path $PWD
- name: Build Windows - name: Build Windows
run: | run: |
mkdir -v -p build/windows mkdir -v -p build/windows

View File

@@ -30,7 +30,7 @@ jobs:
if: ${{ contains(gitea.ref_name, 'release/') }} if: ${{ contains(gitea.ref_name, 'release/') }}
needs: ReleaseName needs: ReleaseName
container: container:
image: barichello/godot-ci:mono-4.5 image: barichello/godot-ci:mono-4.6
steps: steps:
- name: Install node, curl and zip - name: Install node, curl and zip

View File

@@ -1,4 +1,4 @@
<Project Sdk="Godot.NET.Sdk/4.5.0"> <Project Sdk="Godot.NET.Sdk/4.6.0">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading> <EnableDynamicLoading>true</EnableDynamicLoading>

142
Movement tests.csproj.old.1 Normal file
View File

@@ -0,0 +1,142 @@
<Project Sdk="Godot.NET.Sdk/4.5.0">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>Movementtests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Content Include=".runsettings" />
<Content Include="export_presets.cfg" />
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png" />
<Content Include="menus\assets\git_logo\Git-Logo-2Color.png.import" />
<Content Include="menus\assets\git_logo\LICENSE.txt" />
<Content Include="menus\assets\godot_engine_logo\LICENSE.txt" />
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png" />
<Content Include="menus\assets\godot_engine_logo\logo_vertical_color_dark.png.import" />
<Content Include="menus\assets\icon.png" />
<Content Include="menus\assets\icon.png.import" />
<Content Include="menus\ATTRIBUTION.md" />
<Content Include="menus\resources\themes\expedition.tres" />
<Content Include="menus\resources\themes\gravity.tres" />
<Content Include="menus\resources\themes\grow.tres" />
<Content Include="menus\resources\themes\lab.tres" />
<Content Include="menus\resources\themes\lore.tres" />
<Content Include="menus\resources\themes\steal_this_theme.tres" />
<Content Include="menus\scenes\credits\scrollable_credits.gd" />
<Content Include="menus\scenes\credits\scrollable_credits.gd.uid" />
<Content Include="menus\scenes\credits\scrollable_credits.tscn" />
<Content Include="menus\scenes\credits\scrolling_credits.gd" />
<Content Include="menus\scenes\credits\scrolling_credits.gd.uid" />
<Content Include="menus\scenes\credits\scrolling_credits.tscn" />
<Content Include="menus\scenes\end_credits\end_credits.gd" />
<Content Include="menus\scenes\end_credits\end_credits.gd.uid" />
<Content Include="menus\scenes\end_credits\end_credits.tscn" />
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd" />
<Content Include="menus\scenes\game_scene\configurable_sub_viewport.gd.uid" />
<Content Include="menus\scenes\game_scene\game_ui.tscn" />
<Content Include="menus\scenes\game_scene\input_display_label.gd" />
<Content Include="menus\scenes\game_scene\input_display_label.gd.uid" />
<Content Include="menus\scenes\game_scene\levels\level.gd" />
<Content Include="menus\scenes\game_scene\levels\level.gd.uid" />
<Content Include="menus\scenes\game_scene\levels\level_1.tscn" />
<Content Include="menus\scenes\game_scene\levels\level_2.tscn" />
<Content Include="menus\scenes\game_scene\levels\level_3.tscn" />
<Content Include="menus\scenes\game_scene\tutorials\tutorial_1.tscn" />
<Content Include="menus\scenes\game_scene\tutorials\tutorial_2.tscn" />
<Content Include="menus\scenes\game_scene\tutorials\tutorial_3.tscn" />
<Content Include="menus\scenes\game_scene\tutorial_manager.gd" />
<Content Include="menus\scenes\game_scene\tutorial_manager.gd.uid" />
<Content Include="menus\scenes\loading_screen\level_loading_screen.tscn" />
<Content Include="menus\scenes\loading_screen\loading_screen.gd" />
<Content Include="menus\scenes\loading_screen\loading_screen.gd.uid" />
<Content Include="menus\scenes\loading_screen\loading_screen.tscn" />
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd" />
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.gd.uid" />
<Content Include="menus\scenes\loading_screen\loading_screen_with_shader_caching.tscn" />
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd" />
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.gd.uid" />
<Content Include="menus\scenes\menus\level_select_menu\level_select_menu.tscn" />
<Content Include="menus\scenes\menus\main_menu\main_menu.gd" />
<Content Include="menus\scenes\menus\main_menu\main_menu.gd.uid" />
<Content Include="menus\scenes\menus\main_menu\main_menu.tscn" />
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd" />
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.gd.uid" />
<Content Include="menus\scenes\menus\main_menu\main_menu_with_animations.tscn" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_input_option_control.tscn" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\audio\audio_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\game\game_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd" />
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\game\reset_game_control\reset_game_control.tscn" />
<Content Include="menus\scenes\menus\options_menu\input\input_extras_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\input\input_options_menu_with_mouse_sensitivity.tscn" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\master_options_menu_with_tabs.tscn" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\mini_options_menu_with_reset.tscn" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.gd.uid" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu.tscn" />
<Content Include="menus\scenes\menus\options_menu\video\video_options_menu_with_extras.tscn" />
<Content Include="menus\scenes\opening\opening.gd" />
<Content Include="menus\scenes\opening\opening.gd.uid" />
<Content Include="menus\scenes\opening\opening.tscn" />
<Content Include="menus\scenes\opening\opening_with_logo.tscn" />
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\game_won_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\game_won_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\level_lost_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\level_won_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\level_won_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\mini_options_overlaid_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu.tscn" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\overlaid_menu_container.tscn" />
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd" />
<Content Include="menus\scenes\overlaid_menus\pause_menu.gd.uid" />
<Content Include="menus\scenes\overlaid_menus\pause_menu.tscn" />
<Content Include="menus\scripts\game_state.gd" />
<Content Include="menus\scripts\game_state.gd.uid" />
<Content Include="menus\scripts\level_list_and_state_manager.gd" />
<Content Include="menus\scripts\level_list_and_state_manager.gd.uid" />
<Content Include="menus\scripts\level_state.gd" />
<Content Include="menus\scripts\level_state.gd.uid" />
</ItemGroup>
<ItemGroup>
<Folder Include="addons\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="RustyOptions" Version="0.10.1" />
</ItemGroup>
<!-- gdUnit4 package dependencies -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0"/>
<PackageReference Include="gdUnit4.api" Version="5.1.0-rc3"/>
<PackageReference Include="gdUnit4.test.adapter" Version="3.0.0"/>
<PackageReference Include="gdUnit4.analyzers" Version="1.0.0">
<PrivateAssets>none</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -1 +1 @@
uid://do2c2faoehm61 uid://i04mdqgjjfsa

View File

@@ -1 +1 @@
uid://bretpek2ehht4 uid://lcya8t25hn0j

View File

@@ -3,5 +3,5 @@
name="gdUnit4" name="gdUnit4"
description="Unit Testing Framework for Godot Scripts" description="Unit Testing Framework for Godot Scripts"
author="Mike Schulze" author="Mike Schulze"
version="6.0.3" version="6.1.0"
script="plugin.gd" script="plugin.gd"

View File

@@ -1,22 +1,28 @@
@tool @tool
extends EditorPlugin extends EditorPlugin
# We need to define manually the slot id's, to be downwards compatible
const CONTEXT_SLOT_FILESYSTEM: int = 1 # EditorContextMenuPlugin.CONTEXT_SLOT_FILESYSTEM
const CONTEXT_SLOT_SCRIPT_EDITOR: int = 2 # EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR
var _gd_inspector: Control var _gd_inspector: Control
var _gd_console: Control var _gd_console: Control
var _gd_filesystem_context_menu: Variant var _filesystem_context_menu: EditorContextMenuPlugin
var _gd_scripteditor_context_menu: Variant var _editor_context_menu: EditorContextMenuPlugin
var _editor_code_context_menu: EditorContextMenuPlugin
func _enter_tree() -> void: func _enter_tree() -> void:
var inferred_declaration: int = ProjectSettings.get_setting("debug/gdscript/warnings/inferred_declaration") var inferred_declaration: int = ProjectSettings.get_setting("debug/gdscript/warnings/inferred_declaration")
var exclude_addons: bool = ProjectSettings.get_setting("debug/gdscript/warnings/exclude_addons")
if !exclude_addons and inferred_declaration != 0: var is_gdunit_excluded_warnings: bool = false
if Engine.get_version_info().hex >= 0x40600:
var dirctrory_rules: Dictionary = ProjectSettings.get_setting("debug/gdscript/warnings/directory_rules")
if dirctrory_rules.has("res://addons/gdUnit4") and dirctrory_rules["res://addons/gdUnit4"] == 0:
is_gdunit_excluded_warnings = true
else:
is_gdunit_excluded_warnings = ProjectSettings.get_setting("debug/gdscript/warnings/exclude_addons")
if !is_gdunit_excluded_warnings and inferred_declaration != 0:
printerr("GdUnit4: 'inferred_declaration' is set to Warning/Error!") printerr("GdUnit4: 'inferred_declaration' is set to Warning/Error!")
if Engine.get_version_info().hex >= 0x40600:
printerr("GdUnit4 is not 'inferred_declaration' save, you have to excluded the addon (debug/gdscript/warnings/directory_rules)")
else:
printerr("GdUnit4 is not 'inferred_declaration' save, you have to excluded addons (debug/gdscript/warnings/exclude_addons)") printerr("GdUnit4 is not 'inferred_declaration' save, you have to excluded addons (debug/gdscript/warnings/exclude_addons)")
printerr("Loading GdUnit4 Plugin failed.") printerr("Loading GdUnit4 Plugin failed.")
return return
@@ -73,36 +79,21 @@ func check_running_in_test_env() -> bool:
func _add_context_menus() -> void: func _add_context_menus() -> void:
if Engine.get_version_info().hex >= 0x40400: _filesystem_context_menu = preload("res://addons/gdUnit4/src/ui/menu/GdUnitEditorFileSystemContextMenuHandler.gd").new()
# With Godot 4.4 we have to use the 'add_context_menu_plugin' to register editor context menus _editor_context_menu = preload("res://addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd").new()
_gd_filesystem_context_menu = _preload_gdx_script("res://addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandlerV44.gdx") _editor_code_context_menu = preload("res://addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd").new()
call_deferred("add_context_menu_plugin", CONTEXT_SLOT_FILESYSTEM, _gd_filesystem_context_menu) add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_FILESYSTEM, _filesystem_context_menu)
# the CONTEXT_SLOT_SCRIPT_EDITOR is adding to the script panel instead of script editor see https://github.com/godotengine/godot/pull/100556 add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR, _editor_context_menu)
#_gd_scripteditor_context_menu = _preload("res://addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandlerV44.gdx") add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR_CODE, _editor_code_context_menu)
#call_deferred("add_context_menu_plugin", CONTEXT_SLOT_SCRIPT_EDITOR, _gd_scripteditor_context_menu)
# so we use the old hacky way to add the context menu
_gd_inspector.add_child(preload("res://addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd").new())
else:
# TODO Delete it if the minimum requirement for the plugin is set to Godot 4.4.
_gd_inspector.add_child(preload("res://addons/gdUnit4/src/ui/menu/EditorFileSystemContextMenuHandler.gd").new())
_gd_inspector.add_child(preload("res://addons/gdUnit4/src/ui/menu/ScriptEditorContextMenuHandler.gd").new())
func _remove_context_menus() -> void: func _remove_context_menus() -> void:
if is_instance_valid(_gd_filesystem_context_menu): if is_instance_valid(_filesystem_context_menu):
call_deferred("remove_context_menu_plugin", _gd_filesystem_context_menu) remove_context_menu_plugin(_filesystem_context_menu)
if is_instance_valid(_gd_scripteditor_context_menu): if is_instance_valid(_editor_context_menu):
call_deferred("remove_context_menu_plugin", _gd_scripteditor_context_menu) remove_context_menu_plugin(_editor_context_menu)
if is_instance_valid(_editor_code_context_menu):
remove_context_menu_plugin(_editor_code_context_menu)
func _preload_gdx_script(script_path: String) -> Variant:
var script: GDScript = GDScript.new()
script.source_code = GdUnitFileAccess.resource_as_string(script_path)
script.take_over_path(script_path)
var err :Error = script.reload()
if err != OK:
push_error("Can't create context menu %s, error: %s" % [script_path, error_string(err)])
return script.new()
func _on_resource_saved(resource: Resource) -> void: func _on_resource_saved(resource: Resource) -> void:

View File

@@ -1 +1 @@
uid://bc4fimf6ynr5d uid://8wxua8uw7x7k

View File

@@ -1 +1 @@
uid://buiskkw1yyuw3 uid://duj13ipuced2q

View File

@@ -1 +1 @@
uid://drfioswpw8u2u uid://wd2ydmpylh1e

View File

@@ -1 +1 @@
uid://byeulsiqvaugq uid://bwaeyokx1kgfd

View File

@@ -1 +1 @@
uid://bmy2nu4w22wia uid://qqwrtj2mj3xi

View File

@@ -1 +1 @@
uid://c1jp2le4lldby uid://fnv7o85xbiiq

View File

@@ -1 +1 @@
uid://bftfpffmfb1il uid://bav21rax06rdf

View File

@@ -1 +1 @@
uid://dkap7kpfh2bhg uid://b750alnjl31nv

View File

@@ -1 +1 @@
uid://8s1lymhdvlpu uid://cfqx4148ov21q

View File

@@ -1 +1 @@
uid://x54vf4fue301 uid://hqhd4a063x3n

View File

@@ -1 +1 @@
uid://vt1hx0i6pg4h uid://xsowfqnkhk7j

View File

@@ -1 +1 @@
uid://l487wamffax1 uid://crdh3ctgqxrqi

View File

@@ -1 +1 @@
uid://bvvptcdhi1g14 uid://bqguqoaa43uev

View File

@@ -1 +1 @@
uid://bwkv3a1hhdt88 uid://clve38xv30uem

View File

@@ -1 +1 @@
uid://ghuy35olsym1 uid://chqkkjclq101n

View File

@@ -1 +1 @@
uid://dmunl8xg53sym uid://b7mk5mihqqr65

View File

@@ -1 +1 @@
uid://b4n45twg8y2ar uid://cgee6csuvo1ye

View File

@@ -24,9 +24,8 @@ extends RefCounted
## Simulates that a key has been pressed.[br] ## Simulates that a key has been pressed.[br]
## @deprecated: the modifier [b]shift_pressed[/b] and [b]ctrl_pressed[/b] will be removed in v7.0
## [member key_code] : the key code e.g. [constant KEY_ENTER][br] ## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
## [member shift_pressed] : false by default set to true if simmulate shift is press[br]
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br]
## [codeblock] ## [codeblock]
## func test_key_presssed(): ## func test_key_presssed():
## var runner = scene_runner("res://scenes/simple_scene.tscn") ## var runner = scene_runner("res://scenes/simple_scene.tscn")
@@ -35,17 +34,43 @@ extends RefCounted
@abstract func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner @abstract func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner
## Simulates that a key is pressed.[br] ## Simulates that a key is pressing.[br]
## @deprecated: the modifier [b]shift_pressed[/b] and [b]ctrl_pressed[/b] will be removed in v7.0[br]See `test_key_shift_and_A_presssing` for example using key combinations
## [member key_code] : the key code e.g. [constant KEY_ENTER][br] ## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
## [member shift_pressed] : false by default set to true if simmulate shift is press[br] ## [codeblock]
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br] ## # Do simulate key pressing A
## func test_key_A_presssing():
## var runner = scene_runner("res://scenes/simple_scene.tscn")
## await runner.simulate_key_press(KEY_A)
##
##
## # Do simulate keycombination pressing shift+A
## func test_key_shift_and_A_presssing():
## var runner = scene_runner("res://scenes/simple_scene.tscn")
## runner.simulate_key_press(KEY_SHIFT)
## runner.simulate_key_press(KEY_A)
## await _runner.await_input_processed()
## [/codeblock]
@abstract func simulate_key_press(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner @abstract func simulate_key_press(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner
## Simulates that a key has been released.[br] ## Simulates that a key has been released.[br]
## [member key_code] : the key code e.g. [constant KEY_ENTER][br] ## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
## [member shift_pressed] : false by default set to true if simmulate shift is press[br] ## [codeblock]
## [member ctrl_pressed] : false by default set to true if simmulate control is press[br] ## # Do simulate releasing key A
## func test_key_A_releasing():
## var runner = scene_runner("res://scenes/simple_scene.tscn")
## await runner.simulate_key_release(KEY_A)
##
##
## # Do simulate keycombination pressing shift+A
## func test_key_shift_and_A_releasing(():
## var runner = scene_runner("res://scenes/simple_scene.tscn")
## runner.simulate_key_release(KEY_SHIFT)
## runner.simulate_key_release(KEY_A)
## await _runner.await_input_processed()
## [/codeblock]
## @deprecated: the modifier [b]shift_pressed[/b] and [b]ctrl_pressed[/b] will be removed in v7.0[br]See `test_key_shift_and_A_releasing` for example using key combinations
@abstract func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner @abstract func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner

View File

@@ -1 +1 @@
uid://dn20c5e8kb3q3 uid://cg867wakih43u

View File

@@ -27,20 +27,132 @@ extends GdUnitAssert
@abstract func append_failure_message(message: String) -> GdUnitSignalAssert @abstract func append_failure_message(message: String) -> GdUnitSignalAssert
## Verifies that given signal is emitted until waiting time ## Verifies that the specified signal is emitted with the expected arguments.[br]
@abstract func is_emitted(name: String, args := []) -> GdUnitSignalAssert ##
## This assertion waits for a signal to be emitted from the object under test and
## validates that it was emitted with the correct arguments. The function supports
## both typed signals (Signal type) and string-based signal names for flexibility
## in different testing scenarios.[br]
## [br]
## [b]Parameters:[/b][br]
## [param signal_name]: The signal to monitor. Can be either:[br]
## • A [Signal] reference (recommended for type safety)[br]
## • A [String] with the signal name
## [param signal_args]: Optional expected signal arguments.[br]
## When provided, verifies the signal was emitted with exactly these values.[br]
## [br]
## [b]Returns:[/b][br]
## [GdUnitSignalAssert] - Returns self for method chaining.[br]
## [br]
## [b]Examples:[/b]
## [codeblock]
## signal signal_a(value: int)
## signal signal_b(name: String, count: int)
##
## # Wait for signal emission without checking arguments
## # Using Signal reference (type-safe)
## await assert_signal(instance).is_emitted(signal_a)
## # Using string name (dynamic)
## await assert_signal(instance).is_emitted("signal_a")
##
## # Wait for signal emission with specific argument
## await assert_signal(instance).is_emitted(signal_a, 10)
##
## # Wait for signal with multiple arguments
## await assert_signal(instance).is_emitted(signal_b, "test", 42)
##
## # Wait max 500ms for signal with argument 10
## await assert_signal(instance).wait_until(500).is_emitted(signal_a, 10)
## [/codeblock]
## [br]
## [b]Note:[/b] This is an async operation - use [code]await[/code] when calling.[br]
## The assertion fails if the signal is not emitted within the timeout period.
@abstract func is_emitted(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert
## Verifies that given signal is NOT emitted until waiting time ## Verifies that the specified signal is NOT emitted with the expected arguments.[br]
@abstract func is_not_emitted(name: String, args := []) -> GdUnitSignalAssert ##
## This assertion waits for a specified time period and validates that a signal
## was not emitted with the given arguments. Useful for ensuring certain conditions
## don't trigger unwanted signals or for verifying signal filtering logic.[br]
## [br]
## [b]Parameters:[/b][br]
## [param signal_name]: The signal to monitor. Can be either:[br]
## • A [Signal] reference (recommended for type safety)[br]
## • A [String] with the signal name
## [param signal_args]: Optional expected signal arguments.[br]
## When provided, verifies the signal was not emitted with these specific values.[br]
## If omitted, verifies the signal was not emitted at all.[br]
## [br]
## [b]Returns:[/b][br]
## [GdUnitSignalAssert] - Returns self for method chaining.[br]
## [br]
## [b]Examples:[/b]
## [codeblock]
## signal signal_a(value: int)
## signal signal_b(name: String, count: int)
##
## # Verify signal is not emitted at all (without checking arguments)
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_a)
## await assert_signal(instance).wait_until(500).is_not_emitted("signal_a")
##
## # Verify signal is not emitted with specific argument
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_a, 10)
##
## # Verify signal is not emitted with multiple arguments
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_b, "test", 42)
##
## # Can be emitted with different arguments (this passes)
## instance.emit_signal("signal_a", 20) # Emits with 20, not 10
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_a, 10)
## [/codeblock]
## [br]
## [b]Note:[/b] This is an async operation - use [code]await[/code] when calling.[br]
## The assertion fails if the signal IS emitted with the specified arguments within the timeout period.
@abstract func is_not_emitted(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert
## Verifies the signal exists checked the emitter ## Verifies that the specified signal exists on the emitter object.[br]
@abstract func is_signal_exists(name: String) -> GdUnitSignalAssert ##
## This assertion checks if a signal is defined on the object under test,
## regardless of whether it has been emitted. Useful for validating that
## objects have the expected signals before testing their emission.[br]
## [br]
## [b]Parameters:[/b][br]
## [param signal_name]: The signal to check. Can be either:[br]
## • A [Signal] reference (recommended for type safety)[br]
## • A [String] with the signal name
## [br]
## [b]Returns:[/b][br]
## [GdUnitSignalAssert] - Returns self for method chaining.[br]
## [br]
## [b]Examples:[/b]
## [codeblock]
## signal my_signal(value: int)
## signal another_signal()
##
## # Verify signal exists using Signal reference
## assert_signal(instance).is_signal_exists(my_signal)
##
## # Verify signal exists using string name
## assert_signal(instance).is_signal_exists("my_signal")
##
## # Chain with other assertions
## assert_signal(instance) \
## .is_signal_exists(my_signal) \
## .is_emitted(my_signal, 42)
##
## [/codeblock]
## [br]
## [b]Note:[/b] This only checks signal definition, not emission.[br]
## The assertion fails if the signal is not defined on the object.
@abstract func is_signal_exists(signal_name: Variant) -> GdUnitSignalAssert
## Sets the assert signal timeout in ms, if the time over a failure is reported.[br] ## Sets the assert signal timeout in ms, if the time over a failure is reported.[br]
## e.g.[br] ## Example:
## [codeblock]
## do wait until 5s the instance has emitted the signal `signal_a`[br] ## do wait until 5s the instance has emitted the signal `signal_a`[br]
## [code]assert_signal(instance).wait_until(5000).is_emitted("signal_a")[/code] ## assert_signal(instance).wait_until(5000).is_emitted("signal_a")
## [/codeblock]
@abstract func wait_until(timeout: int) -> GdUnitSignalAssert @abstract func wait_until(timeout: int) -> GdUnitSignalAssert

View File

@@ -1 +1 @@
uid://572nse6u4l86 uid://ck4dbwhcpf144

View File

@@ -1 +1 @@
uid://ip241g801xri uid://b2itt4pwvweqo

View File

@@ -14,13 +14,12 @@
class_name GdUnitTestSuite class_name GdUnitTestSuite
extends Node extends Node
const NO_ARG :Variant = GdUnitConstants.NO_ARG
### internal runtime variables that must not be overwritten!!! ### internal runtime variables that must not be overwritten!!!
@warning_ignore("unused_private_class_variable") @warning_ignore("unused_private_class_variable")
var __is_skipped := false var __is_skipped := false
@warning_ignore("unused_private_class_variable") @warning_ignore("unused_private_class_variable")
var __skip_reason :String = "Unknow." var __skip_reason := "Unknow."
var __active_test_case: String var __active_test_case: String
var __awaiter := __gdunit_awaiter() var __awaiter := __gdunit_awaiter()
@@ -81,7 +80,7 @@ func after_test() -> void:
pass pass
func is_failure(_expected_failure :String = NO_ARG) -> bool: func is_failure() -> bool:
return Engine.get_meta("GD_TEST_FAILURE") if Engine.has_meta("GD_TEST_FAILURE") else false return Engine.get_meta("GD_TEST_FAILURE") if Engine.has_meta("GD_TEST_FAILURE") else false
@@ -189,6 +188,45 @@ func await_millis(timeout :int) -> void:
await __awaiter.await_millis(timeout) await __awaiter.await_millis(timeout)
## Collects detailed information about orphaned nodes for debugging purposes.[br]
##
## This function gathers comprehensive details about nodes that remain in memory
## after test execution (orphans). It provides debugging information to help
## identify the source of memory leaks in tests. Must be manually called in
## tests when orphan nodes are detected.[br]
## [br]
## [b]When to Use:[/b][br]
## - When GdUnit4 reports orphan nodes after test execution[br]
## - For debugging memory leaks in test scenarios[br]
## - To get detailed information about unreleased nodes[br]
## [br]
## [b]Usage Pattern:[/b][br]
## Add this call at the end of tests that are suspected to create orphans,
## or when the test runner reports orphan detection.[br]
## [br]
## [b]Examples:[/b]
## [codeblock]
## func test_scene_management():
## # Test code that might create orphan nodes
## var scene = preload("res://TestScene.tscn").instantiate()
## add_child(scene)
##
## # Do test operations
## scene.some_method()
##
## # Clean up (but might miss some nodes)
## scene.queue_free()
##
## # Collect orphan details if any are detected
## collect_orphan_node_details()
## [/codeblock]
## [br]
## [b]Note:[/b] This is a debugging utility function that should be removed
## or commented out once orphan issues are resolved.
func collect_orphan_node_details() -> void:
GdUnitThreadManager.get_current_context().get_execution_context().orphan_monitor_collect()
## Creates a new scene runner to allow simulate interactions checked a scene.[br] ## Creates a new scene runner to allow simulate interactions checked a scene.[br]
## The runner will manage the scene instance and release after the runner is released[br] ## The runner will manage the scene instance and release after the runner is released[br]
## example:[br] ## example:[br]
@@ -506,18 +544,35 @@ func extr(func_name :String, args := Array()) -> GdUnitValueExtractor:
return __lazy_load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd").new(func_name, args) return __lazy_load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd").new(func_name, args)
## Constructs a tuple by given arguments ## Creates a GdUnitTuple from the provided arguments for use in test assertions.
func tuple(arg0 :Variant, ## [br]
arg1 :Variant=NO_ARG, ## This is the primary helper function for creating tuples in GdUnit4 tests.
arg2 :Variant=NO_ARG, ## It provides a convenient way to group multiple expected values when using
arg3 :Variant=NO_ARG, ## [method extractv] assertions. The function enforces that tuples must contain
arg4 :Variant=NO_ARG, ## at least two values, as single-value extractions don't require tuple grouping.
arg5 :Variant=NO_ARG, ## [br]
arg6 :Variant=NO_ARG, ## [b]Parameters:[/b] [br]
arg7 :Variant=NO_ARG, ## - [code]...args[/code]: Variable number of arguments (minimum 2) to group into a tuple.
arg8 :Variant=NO_ARG, ## Each argument represents a value to be compared in assertions.
arg9 :Variant=NO_ARG) -> GdUnitTuple: ## [br]
return GdUnitTuple.new(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) ## [b]Returns:[/b] [br]
## A [GdUnitTuple] containing the provided values, or an empty tuple if fewer than
## 2 arguments are provided (with an error message).
## [br]
## [b]Error Handling:[/b] [br]
## [codeblock]
## # This will push an error and return empty tuple
## var invalid = tuple("single_value") # Error: requires at least 2 arguments
## [br]
## # Correct usage - minimum 2 arguments
## var valid = tuple("name", "value")
## var valid_multi = tuple(1, 2, 3, 4, 5) # Can have many values
## [/codeblock]
func tuple(...args: Array) -> GdUnitTuple:
if args.size() < 2:
push_error("Tuple requires at least two arguments.")
return GdUnitTuple.new()
return GdUnitTuple.new.callv(args)
# === Asserts ================================================================== # === Asserts ==================================================================

View File

@@ -1 +1 @@
uid://cgbfa4cflb5nl uid://bfroomur047su

View File

@@ -1,28 +1,86 @@
## A tuple implementation to hold two or many values ## A tuple implementation for GdUnit4 test assertions and value extraction.
## @tutorial(GdUnit4 Array Assertions): https://mikeschulze.github.io/gdUnit4/latest/testing/assert-array/#extractv
## @tutorial(GdUnit4 Testing Framework): https://mikeschulze.github.io/gdUnit4/
## [br]
## The GdUnitTuple class is a utility container designed specifically for the GdUnit4
## testing framework. It enables advanced assertion operations, particularly when
## extracting and comparing multiple values from complex test results.
## [br]
## [b]Primary Use Cases in Testing:[/b] [br]
## - Extracting multiple properties from test objects with [method extractv]## [br]
## - Grouping related assertion values for comparison## [br]
## - Returning multiple values from test helper methods## [br]
## - Organizing expected vs actual value pairs in assertions## [br]
## [br]
## [b]Example Usage in Tests:[/b]
## [codeblock]
## func test_player_stats_after_level_up():
## var player = Player.new()
## player.level_up()
##
## # Extract multiple properties using tuple
## assert_array([player]) \
## .extractv(extr("name"), extr("level"), extr("hp")) \
## .contains(tuple("Hero", 2, 150))
##
## func test_enemy_spawn_positions():
## var enemies: Array = spawn_enemies(3)
##
## # Verify multiple enemies have correct position data
## assert_array(enemies) \
## .extractv(extr("position.x"), extr("position.y")) \
## .contains_exactly([
## tuple(100, 200),
## tuple(150, 200),
## tuple(200, 200)
## ])
## [/codeblock]
## [br]
## [b]Integration with GdUnit4 Assertions:[/b] [br]
## Tuples work seamlessly with array assertion methods like: [br]
## - [code]contains()[/code] - Check if extracted values contain specific tuples [br]
## - [code]contains_exactly()[/code] - Verify exact tuple matches [br]
## - [code]is_equal()[/code] - Compare tuple equality [br]
## [br]
## [b]Note:[/b] This class is part of the GdUnit4 testing framework's internal
## utilities and is primarily intended for use within test assertions rather
## than production code.
class_name GdUnitTuple class_name GdUnitTuple
extends RefCounted extends RefCounted
const NO_ARG :Variant = GdUnitConstants.NO_ARG var _values: Array = []
var __values :Array = Array()
func _init(arg0:Variant, ## Initializes a new GdUnitTuple with test values.
arg1 :Variant=NO_ARG, ## [br]
arg2 :Variant=NO_ARG, ## Creates a tuple to hold multiple values extracted from test objects
arg3 :Variant=NO_ARG, ## or expected values for assertions. Commonly used with the [code]tuple()[/code]
arg4 :Variant=NO_ARG, ## helper function in GdUnit4 tests.
arg5 :Variant=NO_ARG, ## [br]
arg6 :Variant=NO_ARG, ## [b]Parameters:[/b]
arg7 :Variant=NO_ARG, ## - [code]...args[/code]: Variable number of values to store.
arg8 :Variant=NO_ARG, func _init(...args: Array) -> void:
arg9 :Variant=NO_ARG) -> void: _values = args
__values = GdArrayTools.filter_value([arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9], NO_ARG)
## Returns the tuple's values as an array for assertion comparisons.
## [br]
## Provides access to the stored test values. Used internally by GdUnit4's
## assertion system when comparing tuples in test validations.
## [br]
## [b]Returns:[/b]
## An [Array] containing all values stored in the tuple.
func values() -> Array: func values() -> Array:
return __values return _values
## Returns a string representation for test output and debugging.
## [br]
## Formats the tuple for display in test results, error messages, and debug logs.
## This method is automatically called by GdUnit4 when displaying assertion
## failures involving tuples.
## [br]
## [b]Returns:[/b]
## A [String] in the format "tuple([value1, value2, ...])"
func _to_string() -> String: func _to_string() -> String:
return "tuple(%s)" % str(__values) return "tuple(%s)" % str(_values)

View File

@@ -1 +1 @@
uid://mjqw2uww51fk uid://ckj5nn4gln5bw

View File

@@ -1 +1 @@
uid://2dylh01qtb66 uid://cyagv2phlu24d

View File

@@ -1 +1 @@
uid://bcx6bgypklb3e uid://hrk46nhl8icp

View File

@@ -1 +1 @@
uid://r43u2usutiss uid://bymqibokrfyuk

View File

@@ -1 +1 @@
uid://coauynw7rnsij uid://bxkxeyj2jbth2

View File

@@ -40,22 +40,40 @@ static func input_event_as_text(event :InputEvent) -> String:
var text := "" var text := ""
if event is InputEventKey: if event is InputEventKey:
var key_event := event as InputEventKey var key_event := event as InputEventKey
text += "InputEventKey : key='%s', pressed=%s, keycode=%d, physical_keycode=%s" % [ text += """
event.as_text(), key_event.pressed, key_event.keycode, key_event.physical_keycode] InputEventKey : keycode=%s (%s) pressed: %s
physical_keycode: %s
location: %s
echo: %s""" % [
key_event.keycode,
event.as_text_keycode(),
key_event.pressed,
key_event.physical_keycode,
key_event.location,
key_event.echo]
else: else:
text += event.as_text() text += event.as_text()
if event is InputEventMouse: if event is InputEventMouse:
var mouse_event := event as InputEventMouse var mouse_event := event as InputEventMouse
text += ", global_position %s" % mouse_event.global_position text += """
global_position: %s""" % mouse_event.global_position
if event is InputEventWithModifiers: if event is InputEventWithModifiers:
var mouse_event := event as InputEventWithModifiers var mouse_event := event as InputEventWithModifiers
text += ", shift=%s, alt=%s, control=%s, meta=%s, command=%s" % [ text += """
--------
mods: %s
shift: %s
alt: %s
control: %s
meta: %s
command: %s""" % [
mouse_event.get_modifiers_mask(),
mouse_event.shift_pressed, mouse_event.shift_pressed,
mouse_event.alt_pressed, mouse_event.alt_pressed,
mouse_event.ctrl_pressed, mouse_event.ctrl_pressed,
mouse_event.meta_pressed, mouse_event.meta_pressed,
mouse_event.command_or_control_autoremap] mouse_event.command_or_control_autoremap]
return text return text.dedent()
static func _colored_string_div(characters: String) -> String: static func _colored_string_div(characters: String) -> String:
@@ -119,6 +137,10 @@ static func _nerror(number :Variant) -> String:
return "[color=%s]%s[/color]" % [ERROR_COLOR, str(number)] return "[color=%s]%s[/color]" % [ERROR_COLOR, str(number)]
static func _colored(value: Variant, color: Color) -> String:
return "[color=%s]%s[/color]" % [color.to_html(), value]
static func _colored_value(value :Variant) -> String: static func _colored_value(value :Variant) -> String:
match typeof(value): match typeof(value):
TYPE_STRING, TYPE_STRING_NAME: TYPE_STRING, TYPE_STRING_NAME:
@@ -161,19 +183,53 @@ static func _index_report_as_table(index_reports :Array) -> String:
return table.replace("$cells", cells) return table.replace("$cells", cells)
static func orphan_detected_on_suite_setup(count :int) -> String:
return "%s\n Detected <%d> orphan nodes during test suite setup stage! [b]Check before() and after()![/b]" % [ static func orphan_warning(orphans_count: int) -> String:
_warning("WARNING:"), count] return """
%s: Found %s possible orphan nodes.
Add %s to the end of the test to collect details.""".dedent().trim_prefix("\n") % [
_warning("WARNING:"),
_nerror(orphans_count),
_colored_value("collect_orphan_node_details()")
]
static func orphan_detected_on_suite_setup(orphans: Array[GdUnitOrphanNodeInfo]) -> String:
return """
%s Detected %s orphan nodes!
[b]Verify your test suite setup.[/b]
%s""".dedent().trim_prefix("\n") % [
_warning("WARNING:"),
_nerror(orphans.size()),
_build_orphan_node_stacktrace(orphans)]
static func orphan_detected_on_test_setup(count :int) -> String: static func orphan_detected_on_test_setup(orphans: Array[GdUnitOrphanNodeInfo]) -> String:
return "%s\n Detected <%d> orphan nodes during test setup! [b]Check before_test() and after_test()![/b]" % [ return """
_warning("WARNING:"), count] %s Detected %s orphan nodes on test setup!
[b]Check before_test() and after_test()![/b]
%s""".dedent().trim_prefix("\n") % [
_warning("WARNING:"),
_nerror(orphans.size()),
_build_orphan_node_stacktrace(orphans)
]
static func orphan_detected_on_test(count :int) -> String: static func orphan_detected_on_test(orphans: Array[GdUnitOrphanNodeInfo]) -> String:
return "%s\n Detected <%d> orphan nodes during test execution!" % [ return """
_warning("WARNING:"), count] %s Detected %s orphan nodes!
%s""".dedent().trim_prefix("\n") % [
_warning("WARNING:"),
_nerror(orphans.size()),
_build_orphan_node_stacktrace(orphans)
]
static func _build_orphan_node_stacktrace(orphans: Array[GdUnitOrphanNodeInfo]) -> String:
var stack_trace := "\n"
for orphan in orphans:
stack_trace += orphan.as_trace(orphan, true) + "\n"
return stack_trace.indent(" ")
static func fuzzer_interuped(iterations: int, error: String) -> String: static func fuzzer_interuped(iterations: int, error: String) -> String:
@@ -188,6 +244,10 @@ static func test_timeout(timeout :int) -> String:
return "%s\n %s" % [_error("Timeout !"), _colored_value("Test timed out after %s" % LocalTime.elapsed(timeout))] return "%s\n %s" % [_error("Timeout !"), _colored_value("Test timed out after %s" % LocalTime.elapsed(timeout))]
static func test_session_terminated() -> String:
return "%s" % _error("Test Session Terminated")
# gdlint:disable = mixed-tabs-and-spaces # gdlint:disable = mixed-tabs-and-spaces
static func test_suite_skipped(hint :String, skip_count :int) -> String: static func test_suite_skipped(hint :String, skip_count :int) -> String:
return """ return """

View File

@@ -1 +1 @@
uid://vl7cfc01g5wl uid://c178wdncle4i5

View File

@@ -1 +1 @@
uid://brxvavm3ml0om uid://5fthlxduiurg

View File

@@ -75,7 +75,7 @@ func _toPackedStringArray(value: Variant) -> PackedStringArray:
return PackedStringArray([str(value)]) return PackedStringArray([str(value)])
func _array_equals_div(current: Variant, expected: Variant, case_sensitive: bool = false) -> Array[Array]: func _array_equals_div(current: Variant, expected: Variant, case_sensitive: bool = false) -> Array:
var current_value := _toPackedStringArray(current) var current_value := _toPackedStringArray(current)
var expected_value := _toPackedStringArray(expected) var expected_value := _toPackedStringArray(expected)
var index_report := Array() var index_report := Array()
@@ -374,24 +374,12 @@ func extractv(...extractors: Array) -> GdUnitArrayAssert:
_current_value_provider = DefaultValueProvider.new(null) _current_value_provider = DefaultValueProvider.new(null)
else: else:
for element: Variant in current: for element: Variant in current:
var ev: Array[Variant] = [ var ev: Array[Variant] = []
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG,
GdUnitTuple.NO_ARG
]
for index: int in extractors.size(): for index: int in extractors.size():
var extractor: GdUnitValueExtractor = extractors[index] var extractor: GdUnitValueExtractor = extractors[index]
ev[index] = extractor.extract_value(element) ev.append(extractor.extract_value(element))
if extractors.size() > 1: if extractors.size() > 1:
extracted_elements.append(GdUnitTuple.new(ev[0], ev[1], ev[2], ev[3], ev[4], ev[5], ev[6], ev[7], ev[8], ev[9])) extracted_elements.append(GdUnitTuple.new.callv(ev))
else: else:
extracted_elements.append(ev[0]) extracted_elements.append(ev[0])
_current_value_provider = DefaultValueProvider.new(extracted_elements) _current_value_provider = DefaultValueProvider.new(extracted_elements)

View File

@@ -1 +1 @@
uid://bx7cehfdh2x4w uid://381c6r7r5uhp

View File

@@ -1 +1 @@
uid://cq38mcld2thyl uid://8c05oepulju5

View File

@@ -1 +1 @@
uid://61d7pdgldg0r uid://crrhhudfmy3ay

View File

@@ -1 +1 @@
uid://cxndss6mdq7de uid://c310wp8sog1ti

View File

@@ -1 +1 @@
uid://dqrp7csbeyvon uid://diqntuiv0y7pg

View File

@@ -1 +1 @@
uid://cbrj7dsr235i0 uid://b8htynygoyy0f

View File

@@ -1 +1 @@
uid://2s6h0titid8y uid://dg0uusgasnr64

View File

@@ -1 +1 @@
uid://dvce6xeybbh1i uid://fyb32t5gwdxk

View File

@@ -1 +1 @@
uid://c2jdw0vv5nldq uid://bd15pn72awek1

View File

@@ -4,6 +4,7 @@ var _current_failure_message := ""
var _custom_failure_message := "" var _custom_failure_message := ""
var _additional_failure_message := "" var _additional_failure_message := ""
var _callable: Callable var _callable: Callable
var _logger := GodotGdErrorMonitor.new()
func _init(callable: Callable) -> void: func _init(callable: Callable) -> void:
@@ -14,17 +15,10 @@ func _init(callable: Callable) -> void:
func _execute() -> Array[ErrorLogEntry]: func _execute() -> Array[ErrorLogEntry]:
# execute the given code and monitor for runtime errors _logger.start()
if _callable == null or not _callable.is_valid():
@warning_ignore("return_value_discarded")
_report_error("Invalid Callable '%s'" % _callable)
else:
await _callable.call() await _callable.call()
return await _error_monitor().scan(true) _logger.stop()
return _logger.log_entries()
func _error_monitor() -> GodotGdErrorMonitor:
return GdUnitThreadManager.get_current_context().get_execution_context().error_monitor
func failure_message() -> String: func failure_message() -> String:
@@ -46,8 +40,6 @@ func _report_error(error_message: String, failure_line_number: int = -1) -> GdUn
func _has_log_entry(log_entries: Array[ErrorLogEntry], type: ErrorLogEntry.TYPE, error: Variant) -> bool: func _has_log_entry(log_entries: Array[ErrorLogEntry], type: ErrorLogEntry.TYPE, error: Variant) -> bool:
for entry in log_entries: for entry in log_entries:
if entry._type == type and GdObjects.equals(entry._message, error): if entry._type == type and GdObjects.equals(entry._message, error):
# Erase the log entry we already handled it by this assertion, otherwise it will report at twice
_error_monitor().erase_log_entry(entry)
return true return true
return false return false
@@ -55,12 +47,11 @@ func _has_log_entry(log_entries: Array[ErrorLogEntry], type: ErrorLogEntry.TYPE,
func _to_list(log_entries: Array[ErrorLogEntry]) -> String: func _to_list(log_entries: Array[ErrorLogEntry]) -> String:
if log_entries.is_empty(): if log_entries.is_empty():
return "no errors" return "no errors"
if log_entries.size() == 1:
return log_entries[0]._message var values := []
var value := ""
for entry in log_entries: for entry in log_entries:
value += "'%s'\n" % entry._message values.append(entry)
return value return "\n".join(values)
func is_null() -> GdUnitGodotErrorAssert: func is_null() -> GdUnitGodotErrorAssert:
@@ -90,6 +81,9 @@ func append_failure_message(message: String) -> GdUnitGodotErrorAssert:
func is_success() -> GdUnitGodotErrorAssert: func is_success() -> GdUnitGodotErrorAssert:
if not _validate_callable():
return self
var log_entries := await _execute() var log_entries := await _execute()
if log_entries.is_empty(): if log_entries.is_empty():
return _report_success() return _report_success()
@@ -100,6 +94,9 @@ func is_success() -> GdUnitGodotErrorAssert:
func is_runtime_error(expected_error: Variant) -> GdUnitGodotErrorAssert: func is_runtime_error(expected_error: Variant) -> GdUnitGodotErrorAssert:
if not _validate_callable():
return self
var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_error) var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_error)
if result.is_error(): if result.is_error():
return _report_error(result.error_message()) return _report_error(result.error_message())
@@ -108,12 +105,15 @@ func is_runtime_error(expected_error: Variant) -> GdUnitGodotErrorAssert:
return _report_success() return _report_success()
return _report_error(""" return _report_error("""
Expecting: a runtime error is triggered. Expecting: a runtime error is triggered.
message: '%s' expected: '%s'
found: %s current: '%s'
""".dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)]) """.dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)])
func is_push_warning(expected_warning: Variant) -> GdUnitGodotErrorAssert: func is_push_warning(expected_warning: Variant) -> GdUnitGodotErrorAssert:
if not _validate_callable():
return self
var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_warning) var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_warning)
if result.is_error(): if result.is_error():
return _report_error(result.error_message()) return _report_error(result.error_message())
@@ -122,12 +122,15 @@ func is_push_warning(expected_warning: Variant) -> GdUnitGodotErrorAssert:
return _report_success() return _report_success()
return _report_error(""" return _report_error("""
Expecting: push_warning() is called. Expecting: push_warning() is called.
message: '%s' expected: '%s'
found: %s current: '%s'
""".dedent().trim_prefix("\n") % [expected_warning, _to_list(log_entries)]) """.dedent().trim_prefix("\n") % [expected_warning, _to_list(log_entries)])
func is_push_error(expected_error: Variant) -> GdUnitGodotErrorAssert: func is_push_error(expected_error: Variant) -> GdUnitGodotErrorAssert:
if not _validate_callable():
return self
var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_error) var result := GdUnitArgumentMatchers.is_variant_string_matching(expected_error)
if result.is_error(): if result.is_error():
return _report_error(result.error_message()) return _report_error(result.error_message())
@@ -136,6 +139,14 @@ func is_push_error(expected_error: Variant) -> GdUnitGodotErrorAssert:
return _report_success() return _report_success()
return _report_error(""" return _report_error("""
Expecting: push_error() is called. Expecting: push_error() is called.
message: '%s' expected: '%s'
found: %s current: '%s'
""".dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)]) """.dedent().trim_prefix("\n") % [expected_error, _to_list(log_entries)])
func _validate_callable() -> bool:
if _callable == null or not _callable.is_valid():
@warning_ignore("return_value_discarded")
_report_error("Invalid Callable '%s'" % _callable)
return false
return true

View File

@@ -1 +1 @@
uid://cyi6ooahncq7q uid://d3st26kcndm8l

View File

@@ -1 +1 @@
uid://j4mpmwm2hw61 uid://bcuh7lnvrg7mb

View File

@@ -1 +1 @@
uid://bm6qm58a0dacq uid://ba515a8xk0ubo

View File

@@ -1 +1 @@
uid://b0dlq6jyjcvps uid://dwl5ooagjg2nc

View File

@@ -91,7 +91,11 @@ func is_not_equal(_expected: Variant) -> GdUnitSignalAssert:
# Verifies the signal exists checked the emitter # Verifies the signal exists checked the emitter
func is_signal_exists(signal_name :String) -> GdUnitSignalAssert: func is_signal_exists(signal_name: Variant) -> GdUnitSignalAssert:
if not (signal_name is String or signal_name is Signal):
return report_error("Invalid signal_name: expected String or Signal, but is '%s'" % type_string(typeof(signal_name)))
signal_name = (signal_name as Signal).get_name() if signal_name is Signal else signal_name
if not _emitter.has_signal(signal_name): if not _emitter.has_signal(signal_name):
@warning_ignore("return_value_discarded") @warning_ignore("return_value_discarded")
report_error("The signal '%s' not exists checked object '%s'." % [signal_name, _emitter.get_class()]) report_error("The signal '%s' not exists checked object '%s'." % [signal_name, _emitter.get_class()])
@@ -99,20 +103,36 @@ func is_signal_exists(signal_name :String) -> GdUnitSignalAssert:
# Verifies that given signal is emitted until waiting time # Verifies that given signal is emitted until waiting time
func is_emitted(name :String, args := []) -> GdUnitSignalAssert: func is_emitted(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert:
_line_number = GdUnitAssertions.get_line_number() _line_number = GdUnitAssertions.get_line_number()
return await _wail_until_signal(name, args, false) return await _wail_until_signal(
signal_name,
_wrap_arguments.callv(signal_args),
false)
# Verifies that given signal is NOT emitted until waiting time # Verifies that given signal is NOT emitted until waiting time
func is_not_emitted(name :String, args := []) -> GdUnitSignalAssert: func is_not_emitted(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert:
_line_number = GdUnitAssertions.get_line_number() _line_number = GdUnitAssertions.get_line_number()
return await _wail_until_signal(name, args, true) return await _wail_until_signal(
signal_name,
_wrap_arguments.callv(signal_args),
true)
func _wail_until_signal(signal_name :String, expected_args :Array, expect_not_emitted: bool) -> GdUnitSignalAssert: func _wrap_arguments(...args: Array) -> Array:
# Check using old syntax
if not args.is_empty() and args[0] is Array:
return args[0]
return args
func _wail_until_signal(signal_value: Variant, expected_args: Array, expect_not_emitted: bool) -> GdUnitSignalAssert:
if _emitter == null: if _emitter == null:
return report_error("Can't wait for signal checked a NULL object.") return report_error("Can't wait for signal checked a NULL object.")
if not (signal_value is String or signal_value is Signal):
return report_error("Invalid signal_name: expected String or Signal, but is '%s'" % type_string(typeof(signal_value)))
var signal_name := (signal_value as Signal).get_name() if signal_value is Signal else signal_value
# first verify the signal is defined # first verify the signal is defined
if not _emitter.has_signal(signal_name): if not _emitter.has_signal(signal_name):
return report_error("Can't wait for non-existion signal '%s' checked object '%s'." % [signal_name,_emitter.get_class()]) return report_error("Can't wait for non-existion signal '%s' checked object '%s'." % [signal_name,_emitter.get_class()])

View File

@@ -1 +1 @@
uid://dlh37yc086vr5 uid://c3ytdhnam8ba7

View File

@@ -1 +1 @@
uid://dxqvilchqqeta uid://cw5h1gecccd37

View File

@@ -1 +1 @@
uid://r4avfcakvscw uid://pyvt1oi43853

View File

@@ -1 +1 @@
uid://8y15b6ts3kss uid://beueufp3wgjw8

View File

@@ -1 +1 @@
uid://d4hd3vc50jltg uid://34eyejb3x15x

View File

@@ -1 +1 @@
uid://w4mr1j0k0l uid://crpirtbbekydy

View File

@@ -1 +1 @@
uid://ccm3ivfiaf3i7 uid://dbbsj4qqi8w3m

View File

@@ -1 +1 @@
uid://ccnb2ah35atho uid://cpokylwutgxw6

View File

@@ -1 +1 @@
uid://0p8udx4tdwol uid://swuykw4qh2xx

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: if file == null:
push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_string(FileAccess.get_open_error())]) push_error("ERROR: Can't read resource '%s'. %s" % [resource_path, error_string(FileAccess.get_open_error())])
return "" return ""
return file.get_as_text(true) return file.get_as_text()
static func make_qualified_path(path :String) -> String: 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), TESTS : Array([], TYPE_OBJECT, "RefCounted", GdUnitTestCase),
# the port of running test server for this session # 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) 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: func add_test_cases(tests: Array[GdUnitTestCase]) -> GdUnitRunnerConfig:
test_cases().append_array(tests) test_cases().append_array(tests)
return self return self
@@ -57,7 +69,8 @@ func save_config(path: String = CONFIG_FILE) -> GdUnitResult:
var to_save := { var to_save := {
VERSION : CONFIG_VERSION, VERSION : CONFIG_VERSION,
SERVER_PORT : _config.get(SERVER_PORT), EXIT_FAIL_FAST : is_fail_fast(),
SERVER_PORT : server_port(),
TESTS : Array() 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 return
_scene_tree().root.add_child(_current_scene) _scene_tree().root.add_child(_current_scene)
Engine.set_meta("GdUnitSceneRunner", self)
# do finally reset all open input events when the scene is removed # do finally reset all open input events when the scene is removed
@warning_ignore("return_value_discarded") @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: 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() _reset_input_to_default()
) )
_simulate_start_time = LocalTime.now() _simulate_start_time = LocalTime.now()
@@ -103,6 +102,7 @@ func _notification(what: int) -> void:
_current_scene.free() _current_scene.free()
_is_disposed = true _is_disposed = true
_current_scene = null _current_scene = null
Engine.remove_meta("GdUnitSceneRunner")
func _scene_tree() -> SceneTree: func _scene_tree() -> SceneTree:
@@ -145,6 +145,7 @@ func simulate_action_release(action: String, event_index := -1) -> GdUnitSceneRu
@warning_ignore("return_value_discarded") @warning_ignore("return_value_discarded")
func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner: 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) simulate_key_press(key_code, shift_pressed, ctrl_pressed)
await _scene_tree().process_frame await _scene_tree().process_frame
simulate_key_release(key_code, shift_pressed, ctrl_pressed) 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: 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() __print_current_focus()
var event := InputEventKey.new() var event := InputEventKey.new()
event.pressed = true event.pressed = true
event.keycode = key_code as Key event.keycode = key_code as Key
event.physical_keycode = key_code as Key event.physical_keycode = key_code as Key
event.unicode = key_code event.unicode = key_code
event.alt_pressed = key_code == KEY_ALT event.set_alt_pressed(key_code == KEY_ALT)
event.shift_pressed = shift_pressed or key_code == KEY_SHIFT event.set_shift_pressed(shift_pressed)
event.ctrl_pressed = ctrl_pressed or key_code == KEY_CTRL event.set_ctrl_pressed(ctrl_pressed)
event.get_modifiers_mask()
_apply_input_modifiers(event) _apply_input_modifiers(event)
_key_on_press.append(key_code) _key_on_press.append(key_code)
return _handle_input_event(event) return _handle_input_event(event)
func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner: 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() __print_current_focus()
var event := InputEventKey.new() var event := InputEventKey.new()
event.pressed = false event.pressed = false
event.keycode = key_code as Key event.keycode = key_code as Key
event.physical_keycode = key_code as Key event.physical_keycode = key_code as Key
event.unicode = key_code event.unicode = key_code
event.alt_pressed = key_code == KEY_ALT event.set_alt_pressed(key_code == KEY_ALT)
event.shift_pressed = shift_pressed or key_code == KEY_SHIFT event.set_shift_pressed(shift_pressed)
event.ctrl_pressed = ctrl_pressed or key_code == KEY_CTRL event.set_ctrl_pressed(ctrl_pressed)
_apply_input_modifiers(event) _apply_input_modifiers(event)
_key_on_press.erase(key_code) _key_on_press.erase(key_code)
return _handle_input_event(event) 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: func _scene_name() -> String:
if scene() == null:
return "unknown"
var scene_script :GDScript = scene().get_script() var scene_script :GDScript = scene().get_script()
var scene_name :String = scene().get_name() var scene_name :String = scene().get_name()
if not scene_script: 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 _event.ctrl_pressed = _event.ctrl_pressed or last_input_event.ctrl_pressed
# this line results into reset the control_pressed state!!! # 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 #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 # copy over current active mouse mask and combine with curren mask
@@ -620,3 +633,10 @@ func scene() -> Node:
if not _is_disposed: 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.") push_error("The current scene instance is not valid anymore! check your test is valid. e.g. check for missing awaits.")
return null 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_DISCOVER_ENABLED = GROUP_TEST + "/test_discovery"
const TEST_FLAKY_CHECK = GROUP_TEST + "/flaky_check_enable" const TEST_FLAKY_CHECK = GROUP_TEST + "/flaky_check_enable"
const TEST_FLAKY_MAX_RETRIES = GROUP_TEST + "/flaky_max_retries" const TEST_FLAKY_MAX_RETRIES = GROUP_TEST + "/flaky_max_retries"
const TEST_RERUN_UNTIL_FAILURE_RETRIES = GROUP_TEST + "/rerun_until_failure_retries"
# Report Setiings # 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_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_OVERALL = GROUP_SHORTCUT_INSPECTOR + "/run_test_overall"
const SHORTCUT_INSPECTOR_RUN_TEST_STOP = GROUP_SHORTCUT_INSPECTOR + "/run_test_stop" 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 GROUP_SHORTCUT_EDITOR = SHORTCUT_SETTINGS + "/editor"
const SHORTCUT_EDITOR_RUN_TEST = GROUP_SHORTCUT_EDITOR + "/run_test" 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_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_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_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 # report settings
create_property_if_need(REPORT_PUSH_ERRORS, false, "Report push_error() as failure") 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") 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 # 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, 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_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_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") create_property_if_need(SHORTCUT_INSPECTOR_RUN_TEST_STOP, GdUnitShortcut.default_keys(GdUnitShortcut.ShortCut.STOP_TEST_RUN), "Stop the current test execution")
# script editor # 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_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") 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 # 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, 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.NONE), "Run all test suites in the selected folder or file (Debug)") 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: 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) 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: static func set_test_discover_enabled(enable :bool) -> void:
var property := get_property(TEST_DISCOVER_ENABLED) var property := get_property(TEST_DISCOVER_ENABLED)
property.set_value(enable) property.set_value(enable)

View File

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

View File

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

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) 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") @warning_ignore("unused_signal")
signal gdunit_client_terminated() signal gdunit_test_session_terminate()
## Emitted when a test execution event occurs.[br] ## 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]: 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()): if exclude_scan_directories.has(dir.get_current_dir()):
return collected_suites return collected_suites
var err := dir.list_dir_begin() var err := dir.list_dir_begin()
if err != OK: if err != OK:
push_error("Error on scanning directory %s" % dir.get_current_dir(), error_string(err)) 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 _current_iteration: int = -1
var _expect_to_interupt := false var _expect_to_interupt := false
var _timer: Timer var _timer: Timer
var _interupted: bool = false var _interupted := false
var _terminated := false
var _failed := false var _failed := false
var _parameter_set_resolver: GdUnitTestParameterSetResolver var _parameter_set_resolver: GdUnitTestParameterSetResolver
var _is_disposed := false var _is_disposed := false
@@ -133,6 +134,16 @@ func do_interrupt() -> void:
completed.emit() 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: func _set_failure_handler() -> void:
if not GdUnitSignals.instance().gdunit_set_test_failed.is_connected(_failure_received): if not GdUnitSignals.instance().gdunit_set_test_failed.is_connected(_failure_received):
@warning_ignore("return_value_discarded") @warning_ignore("return_value_discarded")
@@ -172,6 +183,10 @@ func is_expect_interupted() -> bool:
return _expect_to_interupt return _expect_to_interupt
func is_terminated() -> bool:
return _terminated
func is_parameterized() -> bool: func is_parameterized() -> bool:
return _parameter_set_resolver.is_parameterized() return _parameter_set_resolver.is_parameterized()
@@ -192,11 +207,6 @@ func test_name() -> String:
return _test_case.test_name return _test_case.test_name
@warning_ignore("native_method_override")
func get_name() -> StringName:
return _test_case.test_name
func line_number() -> int: func line_number() -> int:
return _test_case.line_number return _test_case.line_number

View File

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

View File

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

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