making sure the issue comes from GDUnit addon folder
All checks were successful
Create tag and build when new code gets to main / Export (push) Successful in 7m6s

This commit is contained in:
2026-01-26 08:51:14 +01:00
parent 51907a1f01
commit 72bf3d4cc5
464 changed files with 6493 additions and 0 deletions

0
addons/gdUnit4/LICENSE Normal file
View File

View File

View File

View File

View File

View File

0
addons/gdUnit4/plugin.gd Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@@ -0,0 +1,691 @@
## The main class for all GdUnit test suites[br]
## This class is the main class to implement your unit tests[br]
## You have to extend and implement your test cases as described[br]
## e.g MyTests.gd [br]
## [codeblock]
## extends GdUnitTestSuite
## # testcase
## func test_case_a():
## assert_that("value").is_equal("value")
## [/codeblock]
## @tutorial: https://mikeschulze.github.io/gdUnit4/faq/test-suite/
@icon("res://addons/gdUnit4/src/ui/settings/logo.png")
class_name GdUnitTestSuite
extends Node
const NO_ARG :Variant = GdUnitConstants.NO_ARG
### internal runtime variables that must not be overwritten!!!
@warning_ignore("unused_private_class_variable")
var __is_skipped := false
@warning_ignore("unused_private_class_variable")
var __skip_reason :String = "Unknow."
var __active_test_case :String
var __awaiter := __gdunit_awaiter()
### We now load all used asserts and tool scripts into the cache according to the principle of "lazy loading"
### in order to noticeably reduce the loading time of the test suite.
# We go this hard way to increase the loading performance to avoid reparsing all the used scripts
# for more detailed info -> https://github.com/godotengine/godot/issues/67400
func __lazy_load(script_path :String) -> GDScript:
return GdUnitAssertions.__lazy_load(script_path)
func __gdunit_assert() -> GDScript:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd")
func __gdunit_tools() -> GDScript:
return __lazy_load("res://addons/gdUnit4/src/core/GdUnitTools.gd")
func __gdunit_file_access() -> GDScript:
return __lazy_load("res://addons/gdUnit4/src/core/GdUnitFileAccess.gd")
func __gdunit_awaiter() -> Object:
return __lazy_load("res://addons/gdUnit4/src/GdUnitAwaiter.gd").new()
func __gdunit_argument_matchers() -> GDScript:
return __lazy_load("res://addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd")
func __gdunit_object_interactions() -> GDScript:
return __lazy_load("res://addons/gdUnit4/src/doubler/GdUnitObjectInteractions.gd")
## This function is called before a test suite starts[br]
## You can overwrite to prepare test data or initalizize necessary variables
func before() -> void:
pass
## This function is called at least when a test suite is finished[br]
## You can overwrite to cleanup data created during test running
func after() -> void:
pass
## This function is called before a test case starts[br]
## You can overwrite to prepare test case specific data
func before_test() -> void:
pass
## This function is called after the test case is finished[br]
## You can overwrite to cleanup your test case specific data
func after_test() -> void:
pass
func is_failure(_expected_failure :String = NO_ARG) -> bool:
return Engine.get_meta("GD_TEST_FAILURE") if Engine.has_meta("GD_TEST_FAILURE") else false
func set_active_test_case(test_case :String) -> void:
__active_test_case = test_case
# === Tools ====================================================================
# Mapps Godot error number to a readable error message. See at ERROR
# https://docs.godotengine.org/de/stable/classes/class_@globalscope.html#enum-globalscope-error
func error_as_string(error_number :int) -> String:
return error_string(error_number)
## A litle helper to auto freeing your created objects after test execution
func auto_free(obj :Variant) -> Variant:
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
assert(execution_context != null, "INTERNAL ERROR: The current execution_context is null! Please report this as bug.")
return execution_context.register_auto_free(obj)
@warning_ignore("native_method_override")
func add_child(node :Node, force_readable_name := false, internal := Node.INTERNAL_MODE_DISABLED) -> void:
super.add_child(node, force_readable_name, internal)
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
if execution_context != null:
execution_context.orphan_monitor_start()
## Discard the error message triggered by a timeout (interruption).[br]
## By default, an interrupted test is reported as an error.[br]
## This function allows you to change the message to Success when an interrupted error is reported.
func discard_error_interupted_by_timeout() -> void:
@warning_ignore("unsafe_method_access")
__gdunit_tools().register_expect_interupted_by_timeout(self, __active_test_case)
## Creates a new directory under the temporary directory *user://tmp*[br]
## Useful for storing data during test execution. [br]
## The directory is automatically deleted after test suite execution
func create_temp_dir(relative_path :String) -> String:
@warning_ignore("unsafe_method_access")
return __gdunit_file_access().create_temp_dir(relative_path)
## Deletes the temporary base directory[br]
## Is called automatically after each execution of the test suite
func clean_temp_dir() -> void:
@warning_ignore("unsafe_method_access")
__gdunit_file_access().clear_tmp()
## Creates a new file under the temporary directory *user://tmp* + <relative_path>[br]
## with given name <file_name> and given file <mode> (default = File.WRITE)[br]
## If success the returned File is automatically closed after the execution of the test suite
func create_temp_file(relative_path :String, file_name :String, mode := FileAccess.WRITE) -> FileAccess:
@warning_ignore("unsafe_method_access")
return __gdunit_file_access().create_temp_file(relative_path, file_name, mode)
## Reads a resource by given path <resource_path> into a PackedStringArray.
func resource_as_array(resource_path :String) -> PackedStringArray:
@warning_ignore("unsafe_method_access")
return __gdunit_file_access().resource_as_array(resource_path)
## Reads a resource by given path <resource_path> and returned the content as String.
func resource_as_string(resource_path :String) -> String:
@warning_ignore("unsafe_method_access")
return __gdunit_file_access().resource_as_string(resource_path)
## Reads a resource by given path <resource_path> and return Variand translated by str_to_var
func resource_as_var(resource_path :String) -> Variant:
@warning_ignore("unsafe_method_access", "unsafe_cast")
return str_to_var(__gdunit_file_access().resource_as_string(resource_path) as String)
## Waits for given signal to be emitted by <source> until a specified timeout to fail[br]
## source: the object from which the signal is emitted[br]
## signal_name: signal name[br]
## args: the expected signal arguments as an array[br]
## timeout: the timeout in ms, default is set to 2000ms
func await_signal_on(source :Object, signal_name :String, args :Array = [], timeout :int = 2000) -> Variant:
@warning_ignore("unsafe_method_access")
return await __awaiter.await_signal_on(source, signal_name, args, timeout)
## Waits until the next idle frame
func await_idle_frame() -> void:
@warning_ignore("unsafe_method_access")
await __awaiter.await_idle_frame()
## Waits for a given amount of milliseconds[br]
## example:[br]
## [codeblock]
## # waits for 100ms
## await await_millis(myNode, 100).completed
## [/codeblock][br]
## use this waiter and not `await get_tree().create_timer().timeout to prevent errors when a test case is timed out
func await_millis(timeout :int) -> void:
@warning_ignore("unsafe_method_access")
await __awaiter.await_millis(timeout)
## 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]
## example:[br]
## [codeblock]
## # creates a runner by using a instanciated scene
## var scene = load("res://foo/my_scne.tscn").instantiate()
## var runner := scene_runner(scene)
##
## # or simply creates a runner by using the scene resource path
## var runner := scene_runner("res://foo/my_scne.tscn")
## [/codeblock]
func scene_runner(scene :Variant, verbose := false) -> GdUnitSceneRunner:
return auto_free(__lazy_load("res://addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd").new(scene, verbose))
# === Mocking & Spy ===========================================================
## do return a default value for primitive types or null
const RETURN_DEFAULTS = GdUnitMock.RETURN_DEFAULTS
## do call the real implementation
const CALL_REAL_FUNC = GdUnitMock.CALL_REAL_FUNC
## do return a default value for primitive types and a fully mocked value for Object types
## builds full deep mocked object
const RETURN_DEEP_STUB = GdUnitMock.RETURN_DEEP_STUB
## Creates a mock for given class name
func mock(clazz :Variant, mock_mode := RETURN_DEFAULTS) -> Variant:
@warning_ignore("unsafe_method_access")
return __lazy_load("res://addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd").build(clazz, mock_mode)
## Creates a spy checked given object instance
func spy(instance :Variant) -> Variant:
@warning_ignore("unsafe_method_access")
return __lazy_load("res://addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd").build(instance)
## Configures a return value for the specified function and used arguments.[br]
## [b]Example:
## [codeblock]
## # overrides the return value of myMock.is_selected() to false
## do_return(false).on(myMock).is_selected()
## [/codeblock]
func do_return(value :Variant) -> GdUnitMock:
return GdUnitMock.new(value)
## Verifies certain behavior happened at least once or exact number of times
func verify(obj :Variant, times := 1) -> Variant:
@warning_ignore("unsafe_method_access")
return __gdunit_object_interactions().verify(obj, times)
## Verifies no interactions is happen checked this mock or spy
func verify_no_interactions(obj :Variant) -> GdUnitAssert:
@warning_ignore("unsafe_method_access")
return __gdunit_object_interactions().verify_no_interactions(obj)
## Verifies the given mock or spy has any unverified interaction.
func verify_no_more_interactions(obj :Variant) -> GdUnitAssert:
@warning_ignore("unsafe_method_access")
return __gdunit_object_interactions().verify_no_more_interactions(obj)
## Resets the saved function call counters checked a mock or spy
func reset(obj :Variant) -> void:
@warning_ignore("unsafe_method_access")
__gdunit_object_interactions().reset(obj)
## Starts monitoring the specified source to collect all transmitted signals.[br]
## The collected signals can then be checked with 'assert_signal'.[br]
## By default, the specified source is automatically released when the test ends.
## You can control this behavior by setting auto_free to false if you do not want the source to be automatically freed.[br]
## Usage:
## [codeblock]
## var emitter := monitor_signals(MyEmitter.new())
## # call the function to send the signal
## emitter.do_it()
## # verify the signial is emitted
## await assert_signal(emitter).is_emitted('my_signal')
## [/codeblock]
func monitor_signals(source :Object, _auto_free := true) -> Object:
@warning_ignore("unsafe_method_access")
__lazy_load("res://addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd")\
.get_current_context()\
.get_signal_collector()\
.register_emitter(source, true) # force recreate to start with a fresh monitoring
return auto_free(source) if _auto_free else source
# === Argument matchers ========================================================
## Argument matcher to match any argument
func any() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().any()
## Argument matcher to match any boolean value
func any_bool() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_BOOL)
## Argument matcher to match any integer value
func any_int() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_INT)
## Argument matcher to match any float value
func any_float() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_FLOAT)
## Argument matcher to match any String value
func any_string() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_STRING)
## Argument matcher to match any Color value
func any_color() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_COLOR)
## Argument matcher to match any Vector typed value
func any_vector() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_types([
TYPE_VECTOR2,
TYPE_VECTOR2I,
TYPE_VECTOR3,
TYPE_VECTOR3I,
TYPE_VECTOR4,
TYPE_VECTOR4I,
])
## Argument matcher to match any Vector2 value
func any_vector2() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR2)
## Argument matcher to match any Vector2i value
func any_vector2i() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR2I)
## Argument matcher to match any Vector3 value
func any_vector3() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR3)
## Argument matcher to match any Vector3i value
func any_vector3i() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR3I)
## Argument matcher to match any Vector4 value
func any_vector4() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR4)
## Argument matcher to match any Vector4i value
func any_vector4i() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_VECTOR4I)
## Argument matcher to match any Rect2 value
func any_rect2() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_RECT2)
## Argument matcher to match any Plane value
func any_plane() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PLANE)
## Argument matcher to match any Quaternion value
func any_quat() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_QUATERNION)
## Argument matcher to match any AABB value
func any_aabb() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_AABB)
## Argument matcher to match any Basis value
func any_basis() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_BASIS)
## Argument matcher to match any Transform2D value
func any_transform_2d() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM2D)
## Argument matcher to match any Transform3D value
func any_transform_3d() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM3D)
## Argument matcher to match any NodePath value
func any_node_path() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_NODE_PATH)
## Argument matcher to match any RID value
func any_rid() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_RID)
## Argument matcher to match any Object value
func any_object() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_OBJECT)
## Argument matcher to match any Dictionary value
func any_dictionary() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_DICTIONARY)
## Argument matcher to match any Array value
func any_array() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_ARRAY)
## Argument matcher to match any PackedByteArray value
func any_packed_byte_array() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_BYTE_ARRAY)
## Argument matcher to match any PackedInt32Array value
func any_packed_int32_array() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT32_ARRAY)
## Argument matcher to match any PackedInt64Array value
func any_packed_int64_array() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT64_ARRAY)
## Argument matcher to match any PackedFloat32Array value
func any_packed_float32_array() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT32_ARRAY)
## Argument matcher to match any PackedFloat64Array value
func any_packed_float64_array() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT64_ARRAY)
## Argument matcher to match any PackedStringArray value
func any_packed_string_array() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_STRING_ARRAY)
## Argument matcher to match any PackedVector2Array value
func any_packed_vector2_array() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR2_ARRAY)
## Argument matcher to match any PackedVector3Array value
func any_packed_vector3_array() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR3_ARRAY)
## Argument matcher to match any PackedColorArray value
func any_packed_color_array() -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().by_type(TYPE_PACKED_COLOR_ARRAY)
## Argument matcher to match any instance of given class
func any_class(clazz :Object) -> GdUnitArgumentMatcher:
@warning_ignore("unsafe_method_access")
return __gdunit_argument_matchers().any_class(clazz)
# === value extract utils ======================================================
## Builds an extractor by given function name and optional arguments
func extr(func_name :String, args := Array()) -> GdUnitValueExtractor:
return __lazy_load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd").new(func_name, args)
## Constructs a tuple by given arguments
func tuple(arg0 :Variant,
arg1 :Variant=NO_ARG,
arg2 :Variant=NO_ARG,
arg3 :Variant=NO_ARG,
arg4 :Variant=NO_ARG,
arg5 :Variant=NO_ARG,
arg6 :Variant=NO_ARG,
arg7 :Variant=NO_ARG,
arg8 :Variant=NO_ARG,
arg9 :Variant=NO_ARG) -> GdUnitTuple:
return GdUnitTuple.new(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
# === Asserts ==================================================================
## The common assertion tool to verify values.
## It checks the given value by type to fit to the best assert
func assert_that(current :Variant) -> GdUnitAssert:
match typeof(current):
TYPE_BOOL:
return assert_bool(current)
TYPE_INT:
return assert_int(current)
TYPE_FLOAT:
return assert_float(current)
TYPE_STRING:
return assert_str(current)
TYPE_VECTOR2, TYPE_VECTOR2I, TYPE_VECTOR3, TYPE_VECTOR3I, TYPE_VECTOR4, TYPE_VECTOR4I:
return assert_vector(current, false)
TYPE_DICTIONARY:
return assert_dict(current)
TYPE_ARRAY, TYPE_PACKED_BYTE_ARRAY, TYPE_PACKED_INT32_ARRAY, TYPE_PACKED_INT64_ARRAY,\
TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_FLOAT64_ARRAY, TYPE_PACKED_STRING_ARRAY,\
TYPE_PACKED_VECTOR2_ARRAY, TYPE_PACKED_VECTOR3_ARRAY, TYPE_PACKED_COLOR_ARRAY:
return assert_array(current, false)
TYPE_OBJECT, TYPE_NIL:
return assert_object(current)
_:
return __gdunit_assert().new(current)
## An assertion tool to verify boolean values.
func assert_bool(current :Variant) -> GdUnitBoolAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd").new(current)
## An assertion tool to verify String values.
func assert_str(current :Variant) -> GdUnitStringAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd").new(current)
## An assertion tool to verify integer values.
func assert_int(current :Variant) -> GdUnitIntAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd").new(current)
## An assertion tool to verify float values.
func assert_float(current :Variant) -> GdUnitFloatAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd").new(current)
## An assertion tool to verify Vector values.[br]
## This assertion supports all vector types.[br]
## Usage:
## [codeblock]
## assert_vector(Vector2(1.2, 1.000001)).is_equal(Vector2(1.2, 1.000001))
## [/codeblock]
func assert_vector(current :Variant, type_check := true) -> GdUnitVectorAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd").new(current, type_check)
## An assertion tool to verify arrays.
func assert_array(current :Variant, type_check := true) -> GdUnitArrayAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd").new(current, type_check)
## An assertion tool to verify dictionaries.
func assert_dict(current :Variant) -> GdUnitDictionaryAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd").new(current)
## An assertion tool to verify FileAccess.
func assert_file(current :Variant) -> GdUnitFileAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd").new(current)
## An assertion tool to verify Objects.
func assert_object(current :Variant) -> GdUnitObjectAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd").new(current)
func assert_result(current :Variant) -> GdUnitResultAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd").new(current)
## An assertion tool that waits until a certain time for an expected function return value
func assert_func(instance :Object, func_name :String, args := Array()) -> GdUnitFuncAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd").new(instance, func_name, args)
## An assertion tool to verify for emitted signals until a certain time.
func assert_signal(instance :Object) -> GdUnitSignalAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd").new(instance)
## An assertion tool to test for failing assertions.[br]
## This assert is only designed for internal use to verify failing asserts working as expected.[br]
## Usage:
## [codeblock]
## assert_failure(func(): assert_bool(true).is_not_equal(true)) \
## .has_message("Expecting:\n 'true'\n not equal to\n 'true'")
## [/codeblock]
func assert_failure(assertion :Callable) -> GdUnitFailureAssert:
@warning_ignore("unsafe_method_access")
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute(assertion)
## An assertion tool to test for failing assertions.[br]
## This assert is only designed for internal use to verify failing asserts working as expected.[br]
## Usage:
## [codeblock]
## await assert_failure_await(func(): assert_bool(true).is_not_equal(true)) \
## .has_message("Expecting:\n 'true'\n not equal to\n 'true'")
## [/codeblock]
func assert_failure_await(assertion :Callable) -> GdUnitFailureAssert:
@warning_ignore("unsafe_method_access")
return await __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute_and_await(assertion)
## An assertion tool to verify Godot errors.[br]
## You can use to verify certain Godot errors like failing assertions, push_error, push_warn.[br]
## Usage:
## [codeblock]
## # tests no error occurred during execution of the code
## await assert_error(func (): return 0 )\
## .is_success()
##
## # tests a push_error('test error') occured during execution of the code
## await assert_error(func (): push_error('test error') )\
## .is_push_error('test error')
## [/codeblock]
func assert_error(current :Callable) -> GdUnitGodotErrorAssert:
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd").new(current)
## Explicitly fails the current test indicating that the feature is not yet implemented.[br]
## This function is useful during development when you want to write test cases before implementing the actual functionality.[br]
## It provides a clear indication that the test failure is expected because the feature is still under development.[br]
## Usage:
## [codeblock]
## # Test for a feature that will be implemented later
## func test_advanced_ai_behavior():
## assert_not_yet_implemented()
##
## [/codeblock]
func assert_not_yet_implemented() -> void:
@warning_ignore("unsafe_method_access")
__gdunit_assert().new(null).do_fail()
## Explicitly fails the current test with a custom error message.[br]
## This function reports an error but does not terminate test execution automatically.[br]
## You must use 'return' after calling fail() to stop the test since GDScript has no exception support.[br]
## Useful for complex conditional testing scenarios where standard assertions are insufficient.[br]
## Usage:
## [codeblock]
## # Fail test when conditions are not met
## if !custom_check(player):
## fail("Player should be alive but has %d health" % player.health)
## return
##
## # Continue with test if conditions pass
## assert_that(player.health).is_greater(0)
## [/codeblock]
func fail(message: String) -> void:
@warning_ignore("unsafe_method_access")
__gdunit_assert().new(null).report_error(message)
# --- internal stuff do not override!!!
func ResourcePath() -> String:
return get_script().resource_path

View File

View File

View File

View File

@@ -0,0 +1,436 @@
class_name GdUnitArrayAssertImpl
extends GdUnitArrayAssert
var _base: GdUnitAssertImpl
var _current_value_provider: ValueProvider
var _type_check: bool
func _init(current: Variant, type_check := true) -> void:
_type_check = type_check
_current_value_provider = DefaultValueProvider.new(current)
_base = GdUnitAssertImpl.new(current)
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
if not _validate_value_type(current):
@warning_ignore("return_value_discarded")
report_error("GdUnitArrayAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
func _notification(event: int) -> void:
if event == NOTIFICATION_PREDELETE:
if _base != null:
_base.notification(event)
_base = null
func report_success() -> GdUnitArrayAssert:
@warning_ignore("return_value_discarded")
_base.report_success()
return self
func report_error(error: String) -> GdUnitArrayAssert:
@warning_ignore("return_value_discarded")
_base.report_error(error)
return self
func failure_message() -> String:
return _base.failure_message()
func override_failure_message(message: String) -> GdUnitArrayAssert:
@warning_ignore("return_value_discarded")
_base.override_failure_message(message)
return self
func append_failure_message(message: String) -> GdUnitArrayAssert:
@warning_ignore("return_value_discarded")
_base.append_failure_message(message)
return self
func _validate_value_type(value: Variant) -> bool:
return value == null or GdArrayTools.is_array_type(value)
func get_current_value() -> Variant:
return _current_value_provider.get_value()
func max_length(left: Variant, right: Variant) -> int:
var ls := str(left).length()
var rs := str(right).length()
return rs if ls < rs else ls
# gdlint: disable=function-name
func _toPackedStringArray(value: Variant) -> PackedStringArray:
if GdArrayTools.is_array_type(value):
@warning_ignore("unsafe_cast")
return PackedStringArray(value as Array)
return PackedStringArray([str(value)])
func _array_equals_div(current: Variant, expected: Variant, case_sensitive: bool = false) -> Array[Array]:
var current_value := _toPackedStringArray(current)
var expected_value := _toPackedStringArray(expected)
var index_report := Array()
for index in current_value.size():
var c := current_value[index]
if index < expected_value.size():
var e := expected_value[index]
if not GdObjects.equals(c, e, case_sensitive):
var length := max_length(c, e)
current_value[index] = GdAssertMessages.format_invalid(c.lpad(length))
expected_value[index] = e.lpad(length)
index_report.push_back({"index": index, "current": c, "expected": e})
else:
current_value[index] = GdAssertMessages.format_invalid(c)
index_report.push_back({"index": index, "current": c, "expected": "<N/A>"})
for index in range(current_value.size(), expected_value.size()):
var value := expected_value[index]
expected_value[index] = GdAssertMessages.format_invalid(value)
index_report.push_back({"index": index, "current": "<N/A>", "expected": value})
return [current_value, expected_value, index_report]
func _array_div(compare_mode: GdObjects.COMPARE_MODE, left: Array[Variant], right: Array[Variant], _same_order := false) -> Array[Variant]:
var not_expect := left.duplicate(true)
var not_found := right.duplicate(true)
for index_c in left.size():
var c: Variant = left[index_c]
for index_e in right.size():
var e: Variant = right[index_e]
if GdObjects.equals(c, e, false, compare_mode):
GdArrayTools.erase_value(not_expect, e)
GdArrayTools.erase_value(not_found, c)
break
return [not_expect, not_found]
func _contains(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
var by_reference := compare_mode == GdObjects.COMPARE_MODE.OBJECT_REFERENCE
var current_value: Variant = get_current_value()
var expected_value: Variant = _extract_variadic_value(expected)
if not _validate_value_type(expected_value):
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
if current_value == null:
return report_error(GdAssertMessages.error_arr_contains(current_value, expected_value, [], expected_value, by_reference))
@warning_ignore("unsafe_cast")
var diffs := _array_div(compare_mode, current_value as Array[Variant], expected_value as Array[Variant])
#var not_expect := diffs[0] as Array
var not_found: Array = diffs[1]
if not not_found.is_empty():
return report_error(GdAssertMessages.error_arr_contains(current_value, expected_value, [], not_found, by_reference))
return report_success()
func _contains_exactly(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
var current_value: Variant = get_current_value()
var expected_value: Variant = _extract_variadic_value(expected)
if not _validate_value_type(expected_value):
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
if current_value == null:
return report_error(GdAssertMessages.error_arr_contains_exactly(null, expected_value, [], expected_value, compare_mode))
# has same content in same order
if _is_equal(current_value, expected_value, false, compare_mode):
return report_success()
# check has same elements but in different order
if _is_equals_sorted(current_value, expected_value, false, compare_mode):
return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected_value, [], [], compare_mode))
# find the difference
@warning_ignore("unsafe_cast")
var diffs := _array_div(compare_mode,
current_value as Array[Variant],
expected_value as Array[Variant],
GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
var not_expect: Array[Variant] = diffs[0]
var not_found: Array[Variant] = diffs[1]
return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected_value, not_expect, not_found, compare_mode))
func _contains_exactly_in_any_order(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
var current_value: Variant = get_current_value()
var expected_value: Variant = _extract_variadic_value(expected)
if not _validate_value_type(expected_value):
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
if current_value == null:
return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected_value, [],
expected_value, compare_mode))
# find the difference
@warning_ignore("unsafe_cast")
var diffs := _array_div(compare_mode, current_value as Array[Variant], expected_value as Array[Variant], false)
var not_expect: Array[Variant] = diffs[0]
var not_found: Array[Variant] = diffs[1]
if not_expect.is_empty() and not_found.is_empty():
return report_success()
return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected_value, not_expect,
not_found, compare_mode))
func _not_contains(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
var current_value: Variant = get_current_value()
var expected_value: Variant = _extract_variadic_value(expected)
if not _validate_value_type(expected_value):
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
if current_value == null:
return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected_value, [],
expected_value, compare_mode))
@warning_ignore("unsafe_cast")
var diffs := _array_div(compare_mode, current_value as Array[Variant], expected_value as Array[Variant])
var found: Array[Variant] = diffs[0]
@warning_ignore("unsafe_cast")
if found.size() == (current_value as Array).size():
return report_success()
@warning_ignore("unsafe_cast")
var diffs2 := _array_div(compare_mode, expected_value as Array[Variant], diffs[1] as Array[Variant])
return report_error(GdAssertMessages.error_arr_not_contains(current_value, expected_value, diffs2[0], compare_mode))
func is_null() -> GdUnitArrayAssert:
@warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitArrayAssert:
@warning_ignore("return_value_discarded")
_base.is_not_null()
return self
func is_equal(...expected: Array) -> GdUnitArrayAssert:
var current_value: Variant = get_current_value()
var expected_value: Variant= _extract_variadic_value(expected)
if not _validate_value_type(expected_value):
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
if current_value == null and expected_value != null:
return report_error(GdAssertMessages.error_equal(null, expected_value))
if not _is_equal(current_value, expected_value):
var diff := _array_equals_div(current_value, expected_value)
var expected_as_list := GdArrayTools.as_string(diff[0], false)
var current_as_list := GdArrayTools.as_string(diff[1], false)
var index_report: Array = diff[2]
return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report))
return report_success()
# Verifies that the current Array is equal to the given one, ignoring case considerations.
func is_equal_ignoring_case(...expected: Array) -> GdUnitArrayAssert:
var current_value: Variant = get_current_value()
var expected_value: Variant = _extract_variadic_value(expected)
if not _validate_value_type(expected_value):
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
if current_value == null and expected_value != null:
@warning_ignore("unsafe_cast")
return report_error(GdAssertMessages.error_equal(null, GdArrayTools.as_string(expected_value)))
if not _is_equal(current_value, expected_value, true):
@warning_ignore("unsafe_cast")
var diff := _array_equals_div(current_value, expected_value, true)
var expected_as_list := GdArrayTools.as_string(diff[0])
var current_as_list := GdArrayTools.as_string(diff[1])
var index_report: Array = diff[2]
return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report))
return report_success()
func is_not_equal(...expected: Array) -> GdUnitArrayAssert:
var current_value: Variant = get_current_value()
var expected_value: Variant = _extract_variadic_value(expected)
if not _validate_value_type(expected_value):
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
if _is_equal(current_value, expected_value):
return report_error(GdAssertMessages.error_not_equal(current_value, expected_value))
return report_success()
func is_not_equal_ignoring_case(...expected: Array) -> GdUnitArrayAssert:
var current_value: Variant = get_current_value()
var expected_value: Variant = _extract_variadic_value(expected)
if not _validate_value_type(expected_value):
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
if _is_equal(current_value, expected_value, true):
@warning_ignore("unsafe_cast")
var c := GdArrayTools.as_string(current_value as Array)
@warning_ignore("unsafe_cast")
var e := GdArrayTools.as_string(expected_value)
return report_error(GdAssertMessages.error_not_equal_case_insensetiv(c, e))
return report_success()
func is_empty() -> GdUnitArrayAssert:
var current_value: Variant = get_current_value()
@warning_ignore("unsafe_cast")
if current_value == null or (current_value as Array).size() > 0:
return report_error(GdAssertMessages.error_is_empty(current_value))
return report_success()
func is_not_empty() -> GdUnitArrayAssert:
var current_value: Variant = get_current_value()
@warning_ignore("unsafe_cast")
if current_value != null and (current_value as Array).size() == 0:
return report_error(GdAssertMessages.error_is_not_empty())
return report_success()
@warning_ignore("unused_parameter", "shadowed_global_identifier")
func is_same(expected: Variant) -> GdUnitArrayAssert:
if not _validate_value_type(expected):
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
var current: Variant = get_current_value()
if not is_same(current, expected):
@warning_ignore("return_value_discarded")
report_error(GdAssertMessages.error_is_same(current, expected))
return self
func is_not_same(expected: Variant) -> GdUnitArrayAssert:
if not _validate_value_type(expected):
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
var current: Variant = get_current_value()
if is_same(current, expected):
@warning_ignore("return_value_discarded")
report_error(GdAssertMessages.error_not_same(current, expected))
return self
func has_size(expected: int) -> GdUnitArrayAssert:
var current_value: Variant = get_current_value()
@warning_ignore("unsafe_cast")
if current_value == null or (current_value as Array).size() != expected:
return report_error(GdAssertMessages.error_has_size(current_value, expected))
return report_success()
func contains(...expected: Array) -> GdUnitArrayAssert:
return _contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
func contains_exactly(...expected: Array) -> GdUnitArrayAssert:
return _contains_exactly(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
func contains_exactly_in_any_order(...expected: Array) -> GdUnitArrayAssert:
return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
func contains_same(...expected: Array) -> GdUnitArrayAssert:
return _contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
func contains_same_exactly(...expected: Array) -> GdUnitArrayAssert:
return _contains_exactly(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
func contains_same_exactly_in_any_order(...expected: Array) -> GdUnitArrayAssert:
return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
func not_contains(...expected: Array) -> GdUnitArrayAssert:
return _not_contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
func not_contains_same(...expected: Array) -> GdUnitArrayAssert:
return _not_contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
func is_instanceof(expected: Variant) -> GdUnitAssert:
@warning_ignore("unsafe_method_access")
_base.is_instanceof(expected)
return self
func extract(func_name: String, ...func_args: Array) -> GdUnitArrayAssert:
var extracted_elements := Array()
var args: Array = _extract_variadic_value(func_args)
var extractor := GdUnitFuncValueExtractor.new(func_name, args)
var current: Variant = get_current_value()
if current == null:
_current_value_provider = DefaultValueProvider.new(null)
else:
for element: Variant in current:
extracted_elements.append(extractor.extract_value(element))
_current_value_provider = DefaultValueProvider.new(extracted_elements)
return self
func extractv(...extractors: Array) -> GdUnitArrayAssert:
var extracted_elements := Array()
var current: Variant = get_current_value()
if current == null:
_current_value_provider = DefaultValueProvider.new(null)
else:
for element: Variant in current:
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():
var extractor: GdUnitValueExtractor = extractors[index]
ev[index] = extractor.extract_value(element)
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]))
else:
extracted_elements.append(ev[0])
_current_value_provider = DefaultValueProvider.new(extracted_elements)
return self
## Small helper to support the old expected arguments as single array and variadic arguments
func _extract_variadic_value(values: Variant) -> Variant:
@warning_ignore("unsafe_method_access")
if values != null and values.size() == 1 and GdArrayTools.is_array_type(values[0]):
return values[0]
return values
@warning_ignore("incompatible_ternary")
func _is_equal(
left: Variant,
right: Variant,
case_sensitive := false,
compare_mode := GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
@warning_ignore("unsafe_cast")
return GdObjects.equals(
(left as Array) if GdArrayTools.is_array_type(left) else left,
(right as Array) if GdArrayTools.is_array_type(right) else right,
case_sensitive,
compare_mode
)
func _is_equals_sorted(
left: Variant,
right: Variant,
case_sensitive := false,
compare_mode := GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
@warning_ignore("unsafe_cast")
return GdObjects.equals_sorted(
left as Array,
right as Array,
case_sensitive,
compare_mode)

View File

@@ -0,0 +1,207 @@
extends GdUnitDictionaryAssert
var _base: GdUnitAssertImpl
func _init(current :Variant) -> void:
_base = GdUnitAssertImpl.new(current)
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
if not GdUnitAssertions.validate_value_type(current, TYPE_DICTIONARY):
@warning_ignore("return_value_discarded")
report_error("GdUnitDictionaryAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
func _notification(event :int) -> void:
if event == NOTIFICATION_PREDELETE:
if _base != null:
_base.notification(event)
_base = null
func report_success() -> GdUnitDictionaryAssert:
@warning_ignore("return_value_discarded")
_base.report_success()
return self
func report_error(error :String) -> GdUnitDictionaryAssert:
@warning_ignore("return_value_discarded")
_base.report_error(error)
return self
func failure_message() -> String:
return _base.failure_message()
func override_failure_message(message: String) -> GdUnitDictionaryAssert:
@warning_ignore("return_value_discarded")
_base.override_failure_message(message)
return self
func append_failure_message(message: String) -> GdUnitDictionaryAssert:
@warning_ignore("return_value_discarded")
_base.append_failure_message(message)
return self
func current_value() -> Variant:
return _base.current_value()
func is_null() -> GdUnitDictionaryAssert:
@warning_ignore("return_value_discarded")
_base.is_null()
return self
func is_not_null() -> GdUnitDictionaryAssert:
@warning_ignore("return_value_discarded")
_base.is_not_null()
return self
func is_equal(expected: Variant) -> GdUnitDictionaryAssert:
var current :Variant = current_value()
if current == null:
return report_error(GdAssertMessages.error_equal(null, GdAssertMessages.format_dict(expected)))
if not GdObjects.equals(current, expected):
var c := GdAssertMessages.format_dict(current)
var e := GdAssertMessages.format_dict(expected)
return report_error(GdAssertMessages.error_equal(c, e))
return report_success()
func is_not_equal(expected: Variant) -> GdUnitDictionaryAssert:
var current: Variant = current_value()
if GdObjects.equals(current, expected):
return report_error(GdAssertMessages.error_not_equal(current, expected))
return report_success()
@warning_ignore("unused_parameter", "shadowed_global_identifier")
func is_same(expected :Variant) -> GdUnitDictionaryAssert:
var current :Variant = current_value()
if current == null:
return report_error(GdAssertMessages.error_equal(null, GdAssertMessages.format_dict(expected)))
if not is_same(current, expected):
var c := GdAssertMessages.format_dict(current)
var e := GdAssertMessages.format_dict(expected)
return report_error(GdAssertMessages.error_is_same(c, e))
return report_success()
@warning_ignore("unused_parameter", "shadowed_global_identifier")
func is_not_same(expected :Variant) -> GdUnitDictionaryAssert:
var current :Variant = current_value()
if is_same(current, expected):
return report_error(GdAssertMessages.error_not_same(current, expected))
return report_success()
func is_empty() -> GdUnitDictionaryAssert:
var current :Variant = current_value()
@warning_ignore("unsafe_cast")
if current == null or not (current as Dictionary).is_empty():
return report_error(GdAssertMessages.error_is_empty(current))
return report_success()
func is_not_empty() -> GdUnitDictionaryAssert:
var current :Variant = current_value()
@warning_ignore("unsafe_cast")
if current == null or (current as Dictionary).is_empty():
return report_error(GdAssertMessages.error_is_not_empty())
return report_success()
func has_size(expected: int) -> GdUnitDictionaryAssert:
var current :Variant = current_value()
if current == null:
return report_error(GdAssertMessages.error_is_not_null())
@warning_ignore("unsafe_cast")
if (current as Dictionary).size() != expected:
return report_error(GdAssertMessages.error_has_size(current, expected))
return report_success()
func _contains_keys(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert:
var current :Variant = current_value()
var expected_value: Array = _extract_variadic_value(expected)
if current == null:
return report_error(GdAssertMessages.error_is_not_null())
# find expected keys
@warning_ignore("unsafe_cast")
var keys_not_found :Array = expected_value.filter(_filter_by_key.bind((current as Dictionary).keys(), compare_mode))
if not keys_not_found.is_empty():
@warning_ignore("unsafe_cast")
return report_error(GdAssertMessages.error_contains_keys((current as Dictionary).keys() as Array, expected_value,
keys_not_found, compare_mode))
return report_success()
func _contains_key_value(key :Variant, value :Variant, compare_mode :GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert:
var current :Variant = current_value()
var expected := [key]
if current == null:
return report_error(GdAssertMessages.error_is_not_null())
var dict_current: Dictionary = current
var keys_not_found :Array = expected.filter(_filter_by_key.bind(dict_current.keys(), compare_mode))
if not keys_not_found.is_empty():
return report_error(GdAssertMessages.error_contains_keys(dict_current.keys() as Array, expected, keys_not_found, compare_mode))
if not GdObjects.equals(dict_current[key], value, false, compare_mode):
return report_error(GdAssertMessages.error_contains_key_value(key, value, dict_current[key], compare_mode))
return report_success()
func _not_contains_keys(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitDictionaryAssert:
var current :Variant = current_value()
var expected_value: Array = _extract_variadic_value(expected)
if current == null:
return report_error(GdAssertMessages.error_is_not_null())
var dict_current: Dictionary = current
var keys_found :Array = dict_current.keys().filter(_filter_by_key.bind(expected_value, compare_mode, true))
if not keys_found.is_empty():
return report_error(GdAssertMessages.error_not_contains_keys(dict_current.keys() as Array, expected_value, keys_found, compare_mode))
return report_success()
func contains_keys(...expected: Array) -> GdUnitDictionaryAssert:
return _contains_keys(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
func contains_key_value(key :Variant, value :Variant) -> GdUnitDictionaryAssert:
return _contains_key_value(key, value, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
func not_contains_keys(...expected: Array) -> GdUnitDictionaryAssert:
return _not_contains_keys(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
func contains_same_keys(expected :Array) -> GdUnitDictionaryAssert:
return _contains_keys(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
func contains_same_key_value(key :Variant, value :Variant) -> GdUnitDictionaryAssert:
return _contains_key_value(key, value, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
func not_contains_same_keys(...expected: Array) -> GdUnitDictionaryAssert:
return _not_contains_keys(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
func _filter_by_key(element :Variant, values :Array, compare_mode :GdObjects.COMPARE_MODE, is_not :bool = false) -> bool:
for key :Variant in values:
if GdObjects.equals(key, element, false, compare_mode):
return is_not
return !is_not
## Small helper to support the old expected arguments as single array and variadic arguments
func _extract_variadic_value(values: Variant) -> Variant:
@warning_ignore("unsafe_method_access")
if values != null and values.size() == 1 and GdArrayTools.is_array_type(values[0]):
return values[0]
return values

View File

@@ -0,0 +1,177 @@
extends GdUnitFuncAssert
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
const DEFAULT_TIMEOUT := 2000
var _current_value_provider :ValueProvider
var _current_failure_message :String = ""
var _custom_failure_message :String = ""
var _additional_failure_message: String = ""
var _line_number := -1
var _timeout := DEFAULT_TIMEOUT
var _interrupted := false
var _sleep_timer :Timer = null
func _init(instance :Object, func_name :String, args := Array()) -> void:
_line_number = GdUnitAssertions.get_line_number()
GdAssertReports.reset_last_error_line_number()
# save the actual assert instance on the current thread context
GdUnitThreadManager.get_current_context().set_assert(self)
# verify at first the function name exists
if not instance.has_method(func_name):
@warning_ignore("return_value_discarded")
report_error("The function '%s' do not exists checked instance '%s'." % [func_name, instance])
_interrupted = true
else:
_current_value_provider = CallBackValueProvider.new(instance, func_name, args)
func _notification(what :int) -> void:
if what == NOTIFICATION_PREDELETE:
_interrupted = true
var main_node :Node = (Engine.get_main_loop() as SceneTree).root
if is_instance_valid(_current_value_provider):
_current_value_provider.dispose()
_current_value_provider = null
if is_instance_valid(_sleep_timer):
_sleep_timer.set_wait_time(0.0001)
_sleep_timer.stop()
main_node.remove_child(_sleep_timer)
_sleep_timer.free()
_sleep_timer = null
func report_success() -> GdUnitFuncAssert:
GdAssertReports.report_success()
return self
func report_error(failure :String) -> GdUnitFuncAssert:
_current_failure_message = GdAssertMessages.build_failure_message(failure, _additional_failure_message, _custom_failure_message)
GdAssertReports.report_error(_current_failure_message, _line_number)
return self
func failure_message() -> String:
return _current_failure_message
func override_failure_message(message: String) -> GdUnitFuncAssert:
_custom_failure_message = message
return self
func append_failure_message(message: String) -> GdUnitFuncAssert:
_additional_failure_message = message
return self
func wait_until(timeout := 2000) -> GdUnitFuncAssert:
if timeout <= 0:
push_warning("Invalid timeout param, alloed timeouts must be grater than 0. Use default timeout instead")
_timeout = DEFAULT_TIMEOUT
else:
_timeout = timeout
return self
func is_null() -> GdUnitFuncAssert:
await _validate_callback(cb_is_null)
return self
func is_not_null() -> GdUnitFuncAssert:
await _validate_callback(cb_is_not_null)
return self
func is_false() -> GdUnitFuncAssert:
await _validate_callback(cb_is_false)
return self
func is_true() -> GdUnitFuncAssert:
await _validate_callback(cb_is_true)
return self
func is_equal(expected: Variant) -> GdUnitFuncAssert:
await _validate_callback(cb_is_equal, expected)
return self
func is_not_equal(expected: Variant) -> GdUnitFuncAssert:
await _validate_callback(cb_is_not_equal, expected)
return self
# we need actually to define this Callable as functions otherwise we results into leaked scripts here
# this is actually a Godot bug and needs this kind of workaround
func cb_is_null(c :Variant, _e :Variant) -> bool: return c == null
func cb_is_not_null(c :Variant, _e :Variant) -> bool: return c != null
func cb_is_false(c :Variant, _e :Variant) -> bool: return c == false
func cb_is_true(c :Variant, _e :Variant) -> bool: return c == true
func cb_is_equal(c :Variant, e :Variant) -> bool: return GdObjects.equals(c,e)
func cb_is_not_equal(c :Variant, e :Variant) -> bool: return not GdObjects.equals(c, e)
func do_interrupt() -> void:
_interrupted = true
func _validate_callback(predicate :Callable, expected :Variant = null) -> void:
if _interrupted:
return
GdUnitMemoryObserver.guard_instance(self)
var time_scale := Engine.get_time_scale()
var timer := Timer.new()
timer.set_name("gdunit_funcassert_interrupt_timer_%d" % timer.get_instance_id())
var scene_tree := Engine.get_main_loop() as SceneTree
scene_tree.root.add_child(timer)
timer.add_to_group("GdUnitTimers")
@warning_ignore("return_value_discarded")
timer.timeout.connect(do_interrupt, CONNECT_DEFERRED)
timer.set_one_shot(true)
timer.start((_timeout/1000.0)*time_scale)
_sleep_timer = Timer.new()
_sleep_timer.set_name("gdunit_funcassert_sleep_timer_%d" % _sleep_timer.get_instance_id() )
scene_tree.root.add_child(_sleep_timer)
while true:
var current :Variant = await next_current_value()
# is interupted or predicate success
if _interrupted or predicate.call(current, expected):
break
if is_instance_valid(_sleep_timer):
_sleep_timer.start(0.05)
await _sleep_timer.timeout
_sleep_timer.stop()
await scene_tree.process_frame
if _interrupted:
# https://github.com/godotengine/godot/issues/73052
#var predicate_name = predicate.get_method()
var predicate_name :String = str(predicate).split('::')[1]
@warning_ignore("return_value_discarded")
report_error(GdAssertMessages.error_interrupted(
predicate_name.strip_edges().trim_prefix("cb_"),
expected,
LocalTime.elapsed(_timeout)
)
)
else:
@warning_ignore("return_value_discarded")
report_success()
_sleep_timer.free()
timer.free()
GdUnitMemoryObserver.unguard_instance(self)
func next_current_value() -> Variant:
@warning_ignore("redundant_await")
if is_instance_valid(_current_value_provider):
return await _current_value_provider.get_value()
return "invalid value"

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