From a89e744ed0e8c759caf041a580112365bbfe4303 Mon Sep 17 00:00:00 2001 From: radiohotline Date: Tue, 25 Aug 2026 16:33:56 +0300 Subject: escoria update --- addons/gdUnit4/src/core/GdUnitTools.gd | 144 +++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 addons/gdUnit4/src/core/GdUnitTools.gd (limited to 'addons/gdUnit4/src/core/GdUnitTools.gd') diff --git a/addons/gdUnit4/src/core/GdUnitTools.gd b/addons/gdUnit4/src/core/GdUnitTools.gd new file mode 100644 index 0000000..0742896 --- /dev/null +++ b/addons/gdUnit4/src/core/GdUnitTools.gd @@ -0,0 +1,144 @@ +extends RefCounted + + +static var _richtext_normalize: RegEx + + +static func normalize_text(text :String) -> String: + return text.replace("\r", ""); + + +static func richtext_normalize(input :String) -> String: + if _richtext_normalize == null: + _richtext_normalize = to_regex("\\[/?(b|color|bgcolor|right|table|cell).*?\\]") + return _richtext_normalize.sub(input, "", true).replace("\r", "") + + +static func to_regex(pattern :String) -> RegEx: + var regex := RegEx.new() + var err := regex.compile(pattern) + if err != OK: + push_error("Can't compiling regx '%s'.\n ERROR: %s" % [pattern, error_string(err)]) + return regex + + +static func prints_verbose(message :String) -> void: + if OS.is_stdout_verbose(): + prints(message) + + +static func free_instance(instance :Variant, use_call_deferred :bool = false, is_stdout_verbose := false) -> bool: + if instance is Array: + var as_array: Array = instance + for element: Variant in as_array: + @warning_ignore("return_value_discarded") + free_instance(element) + as_array.clear() + return true + # do not free an already freed instance + if not is_instance_valid(instance): + return false + # do not free a class refernece + @warning_ignore("unsafe_cast") + if typeof(instance) == TYPE_OBJECT and (instance as Object).is_class("GDScriptNativeClass"): + return false + if is_stdout_verbose: + print_verbose("GdUnit4:gc():free instance ", instance) + @warning_ignore("unsafe_cast") + release_double(instance as Object) + if instance is RefCounted: + @warning_ignore("unsafe_cast") + (instance as RefCounted).notification(Object.NOTIFICATION_PREDELETE) + # If scene runner freed we explicit await all inputs are processed + if instance is GdUnitSceneRunnerImpl: + @warning_ignore("unsafe_cast") + await (instance as GdUnitSceneRunnerImpl).await_input_processed() + return true + else: + if instance is Timer: + var timer: Timer = instance + timer.stop() + if use_call_deferred: + timer.call_deferred("free") + else: + timer.free() + await (Engine.get_main_loop() as SceneTree).process_frame + return true + + @warning_ignore("unsafe_cast") + if instance is Node and (instance as Node).get_parent() != null: + var node: Node = instance + if is_stdout_verbose: + print_verbose("GdUnit4:gc():remove node from parent ", node.get_parent(), node) + if use_call_deferred: + node.get_parent().remove_child.call_deferred(node) + #instance.call_deferred("set_owner", null) + else: + node.get_parent().remove_child(node) + if is_stdout_verbose: + print_verbose("GdUnit4:gc():freeing `free()` the instance ", instance) + if use_call_deferred: + @warning_ignore("unsafe_cast") + (instance as Object).call_deferred("free") + else: + @warning_ignore("unsafe_cast") + (instance as Object).free() + return !is_instance_valid(instance) + + +static func _release_connections(instance :Object) -> void: + if is_instance_valid(instance): + # disconnect from all connected signals to force freeing, otherwise it ends up in orphans + for connection in instance.get_incoming_connections(): + var signal_ :Signal = connection["signal"] + var callable_ :Callable = connection["callable"] + #prints(instance, connection) + #prints("signal", signal_.get_name(), signal_.get_object()) + #prints("callable", callable_.get_object()) + if instance.has_signal(signal_.get_name()) and instance.is_connected(signal_.get_name(), callable_): + #prints("disconnect signal", signal_.get_name(), callable_) + instance.disconnect(signal_.get_name(), callable_) + release_timers() + + +static func release_timers() -> void: + # we go the new way to hold all gdunit timers in group 'GdUnitTimers' + var scene_tree := Engine.get_main_loop() as SceneTree + if scene_tree.root == null: + return + for node :Node in scene_tree.root.get_children(): + if is_instance_valid(node) and node.is_in_group("GdUnitTimers"): + if is_instance_valid(node): + scene_tree.root.remove_child.call_deferred(node) + (node as Timer).stop() + node.queue_free() + + +# the finally cleaup unfreed resources and singletons +static func dispose_all(use_call_deferred :bool = false) -> void: + release_timers() + GdUnitSingleton.dispose(use_call_deferred) + GdUnitSignals.dispose() + + +# if instance an mock or spy we need manually freeing the self reference +static func release_double(instance :Object) -> void: + if instance.has_method("__release_double"): + instance.call("__release_double") + + + +static func find_test_case(test_suite: Node, test_case_name: String, index := -1) -> _TestCase: + for test_case: _TestCase in test_suite.get_children(): + if test_case.test_name() == test_case_name: + if index != -1: + if test_case._test_case.attribute_index != index: + continue + return test_case + return null + + +static func register_expect_interupted_by_timeout(test_suite: Node, test_case_name: String) -> void: + var test_case := find_test_case(test_suite, test_case_name) + if test_case: + test_case.expect_to_interupt() -- cgit v1.3