class_name GutUtils # ------------------------------------------------------------------------------ # Description # ----------- # This class is a PSUEDO SINGLETON. You should not make instances of it but use # the get_instance static method. # ------------------------------------------------------------------------------ # NOTE: I think this can become completely static now that we have static # variables. A lot would have to change though. But it would be good # to do. # ------------------------------------------------------------------------------ const GUT_METADATA = "__gutdbl" # Note, these cannot change since places are checking for TYPE_INT to determine # how to process parameters. enum DOUBLE_STRATEGY { INCLUDE_NATIVE, SCRIPT_ONLY, } enum DIFF { DEEP, SIMPLE } const TEST_STATUSES = { NO_ASSERTS = "no asserts", SKIPPED = "skipped", NOT_RUN = "not run", PENDING = "pending", # These two got the "ed" b/c pass is a reserved word and I could not # think of better words. FAILED = "fail", PASSED = "pass" } # This is a holdover from when GUT was making a psuedo autoload. It would add # an instance of this class to the tree with a name and retrieve it when # get_instance was called. We now have static variables so this var is now # used instead of a node. static var _the_instance = null # ------------------------------------------------------------------------------ # Gets the root node without having to be in the tree and pushing out an error # if we don't have a main loop ready to go yet. # ------------------------------------------------------------------------------ static func get_root_node(): var main_loop = Engine.get_main_loop() if main_loop != null: return main_loop.root else: push_error("No Main Loop Yet") return null # ------------------------------------------------------------------------------ # Get the ONE instance of utils # Since we can't have static variables we have to store the instance in the # tree. This means you have to wait a bit for the main loop to be up and # running. # ------------------------------------------------------------------------------ static func get_instance(): if _the_instance == null: _the_instance = GutUtils.new() return _the_instance # ------------------------------------------------------------------------------ # Gets the value from an enum. If passed an int it will return it if the enum # contains it. If passed a string it will convert it to upper case and replace # spaces with underscores. If the enum contains the key, it will return the # value for they key. When keys or ints are not found, the default is returned. # ------------------------------------------------------------------------------ static func get_enum_value(thing, e, default = null): var to_return = default if typeof(thing) == TYPE_STRING: var converted = thing.to_upper().replace(" ", "_") if e.keys().has(converted): to_return = e[converted] else: if e.values().has(thing): to_return = thing return to_return # ------------------------------------------------------------------------------ # return if_null if value is null otherwise return value # ------------------------------------------------------------------------------ static func nvl(value, if_null): if value == null: return if_null else: return value # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ static func pretty_print(dict): print(JSON.stringify(dict, " ")) # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ static func print_properties(props, thing, print_all_meta = false): for i in range(props.size()): var prop_name = props[i].name var prop_value = thing.get(props[i].name) var print_value = str(prop_value) if print_value.length() > 100: print_value = print_value.substr(0, 97) + "..." elif print_value == "": print_value = "EMPTY" print(prop_name, " = ", print_value) if print_all_meta: print(" ", props[i]) # ------------------------------------------------------------------------------ # Gets the value of the node_property 'script' from a PackedScene's root node. # This does not assume the location of the root node in the PackedScene's node # list. This also does not assume the index of the 'script' node property in # a nodes's property list. # ------------------------------------------------------------------------------ static func get_scene_script_object(scene): var state = scene.get_state() var to_return = null var root_node_path = NodePath(".") var node_idx = 0 while node_idx < state.get_node_count() and to_return == null: if state.get_node_path(node_idx) == root_node_path: for i in range(state.get_node_property_count(node_idx)): if state.get_node_property_name(node_idx, i) == "script": to_return = state.get_node_property_value(node_idx, i) node_idx += 1 return to_return # ############################################################################## # Start Class # ############################################################################## var Logger = load("res://addons/gut/logger.gd") # everything should use get_logger var _lgr = null var json = JSON.new() var _test_mode = false var AutoFree = load("res://addons/gut/autofree.gd") var Awaiter = load("res://addons/gut/awaiter.gd") var Comparator = load("res://addons/gut/comparator.gd") var CompareResult = load("res://addons/gut/compare_result.gd") var DiffTool = load("res://addons/gut/diff_tool.gd") var Doubler = load("res://addons/gut/doubler.gd") var Gut = load("res://addons/gut/gut.gd") var GutConfig = load("res://addons/gut/gut_config.gd") var HookScript = load("res://addons/gut/hook_script.gd") var InnerClassRegistry = load("res://addons/gut/inner_class_registry.gd") var InputFactory = load("res://addons/gut/input_factory.gd") var InputSender = load("res://addons/gut/input_sender.gd") var JunitXmlExport = load("res://addons/gut/junit_xml_export.gd") var MethodMaker = load("res://addons/gut/method_maker.gd") var OneToMany = load("res://addons/gut/one_to_many.gd") var OrphanCounter = load("res://addons/gut/orphan_counter.gd") var ParameterFactory = load("res://addons/gut/parameter_factory.gd") var ParameterHandler = load("res://addons/gut/parameter_handler.gd") var Printers = load("res://addons/gut/printers.gd") var ResultExporter = load("res://addons/gut/result_exporter.gd") var ScriptCollector = load("res://addons/gut/script_parser.gd") var Spy = load("res://addons/gut/spy.gd") var Strutils = load("res://addons/gut/strutils.gd") var Stubber = load("res://addons/gut/stubber.gd") var StubParams = load("res://addons/gut/stub_params.gd") var Summary = load("res://addons/gut/summary.gd") var Test = load("res://addons/gut/test.gd") var TestCollector = load("res://addons/gut/test_collector.gd") var ThingCounter = load("res://addons/gut/thing_counter.gd") var CollectedTest = load("res://addons/gut/collected_test.gd") var CollectedScript = load("res://addons/gut/collected_test.gd") var GutScene = load("res://addons/gut/GutScene.tscn") # Source of truth for the GUT version var version = "9.1.1" # The required Godot version as an array. var req_godot = [4, 1, 0] # ------------------------------------------------------------------------------ # Blurb of text with GUT and Godot versions. # ------------------------------------------------------------------------------ func get_version_text(): var v_info = Engine.get_version_info() var gut_version_info = str("GUT version: ", version) var godot_version_info = str( "Godot version: ", v_info.major, ".", v_info.minor, ".", v_info.patch ) return godot_version_info + "\n" + gut_version_info # ------------------------------------------------------------------------------ # Returns a nice string for erroring out when we have a bad Godot version. # ------------------------------------------------------------------------------ func get_bad_version_text(): var ver = ".".join(PackedStringArray(req_godot)) var info = Engine.get_version_info() var gd_version = str(info.major, ".", info.minor, ".", info.patch) return ( "GUT " + version + " requires Godot " + ver + " or greater. Godot version is " + gd_version ) # ------------------------------------------------------------------------------ # Checks the Godot version against req_godot array. # ------------------------------------------------------------------------------ func is_version_ok(engine_info = Engine.get_version_info(), required = req_godot): var is_ok = null var engine_array = [engine_info.major, engine_info.minor, engine_info.patch] var idx = 0 while is_ok == null and idx < engine_array.size(): if engine_array[idx] > required[idx]: is_ok = true elif engine_array[idx] < required[idx]: is_ok = false idx += 1 # still null means each index was the same. return nvl(is_ok, true) func godot_version(engine_info = Engine.get_version_info()): return str(engine_info.major, ".", engine_info.minor, ".", engine_info.patch) func is_godot_version(expected, engine_info = Engine.get_version_info()): var engine_array = [engine_info.major, engine_info.minor, engine_info.patch] var expected_array = expected.split(".") if expected_array.size() > engine_array.size(): return false var is_version = true var i = 0 while i < expected_array.size() and i < engine_array.size() and is_version: if expected_array[i] == str(engine_array[i]): i += 1 else: is_version = false return is_version func is_godot_version_gte(expected, engine_info = Engine.get_version_info()): return is_version_ok(engine_info, expected.split(".")) # ------------------------------------------------------------------------------ # Everything should get a logger through this. # # When running in test mode this will always return a new logger so that errors # are not caused by getting bad warn/error/etc counts. # ------------------------------------------------------------------------------ func get_logger(): if _test_mode: return Logger.new() else: if _lgr == null: _lgr = Logger.new() return _lgr # ------------------------------------------------------------------------------ # returns true if the object has been freed, false if not # # From what i've read, the weakref approach should work. It seems to work most # of the time but sometimes it does not catch it. The str comparison seems to # fill in the gaps. I've not seen any errors after adding that check. # ------------------------------------------------------------------------------ func is_freed(obj): var wr = weakref(obj) return !(wr.get_ref() and str(obj) != "") # ------------------------------------------------------------------------------ # Pretty self explanitory. # ------------------------------------------------------------------------------ func is_not_freed(obj): return !is_freed(obj) # ------------------------------------------------------------------------------ # Checks if the passed in object is a GUT Double or Partial Double. # ------------------------------------------------------------------------------ func is_double(obj): var to_return = false if typeof(obj) == TYPE_OBJECT and is_instance_valid(obj): to_return = obj.has_method("__gutdbl_check_method__") return to_return # ------------------------------------------------------------------------------ # Checks if the passed in is an instance of a class # ------------------------------------------------------------------------------ func is_instance(obj): return ( typeof(obj) == TYPE_OBJECT and !is_native_class(obj) and !obj.has_method("new") and !obj.has_method("instantiate") ) # ------------------------------------------------------------------------------ # Checks if the passed in is a GDScript # ------------------------------------------------------------------------------ func is_gdscript(obj): return typeof(obj) == TYPE_OBJECT and str(obj).begins_with("= 0: temp = decimal_value >> count if temp & 1: binary_string = binary_string + "1" else: binary_string = binary_string + "0" count -= 1 return binary_string func add_line_numbers(contents): if contents == null: return "" var to_return = "" var lines = contents.split("\n") var line_num = 1 for line in lines: var line_str = str(line_num).lpad(6, " ") to_return += str(line_str, " |", line, "\n") line_num += 1 return to_return func pp(dict, indent = ""): var text = json.stringify(dict, " ") print(text) var _created_script_count = 0 func create_script_from_source(source, override_path = null): _created_script_count += 1 var r_path = "" #str('workaround for godot issue #65263 (', _created_script_count, ')') if override_path != null: r_path = override_path var DynamicScript = GDScript.new() DynamicScript.source_code = source # The resource_path must be unique or Godot thinks it is trying # to load something it has already loaded and generates an error like # ERROR: Another resource is loaded from path 'workaround for godot issue #65263' (possible cyclic resource inclusion). DynamicScript.resource_path = r_path var result = DynamicScript.reload() return DynamicScript func get_display_size(): return Engine.get_main_loop().get_viewport().get_visible_rect() # ############################################################################## #(G)odot (U)nit (T)est class # # ############################################################################## # The MIT License (MIT) # ===================== # # Copyright (c) 2023 Tom "Butch" Wesley # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # ##############################################################################