From 05d29ccce1898ed89c0b650c77242c2fa2805128 Mon Sep 17 00:00:00 2001 From: Sophia Pearson Date: Fri, 20 May 2022 00:45:25 +0200 Subject: texty: initial commit --- addons/gut/test.gd | 1651 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1651 insertions(+) create mode 100644 addons/gut/test.gd (limited to 'addons/gut/test.gd') diff --git a/addons/gut/test.gd b/addons/gut/test.gd new file mode 100644 index 0000000..2a80a8d --- /dev/null +++ b/addons/gut/test.gd @@ -0,0 +1,1651 @@ +class_name GutTest +# ############################################################################## +#(G)odot (U)nit (T)est class +# +# ############################################################################## +# The MIT License (MIT) +# ===================== +# +# Copyright (c) 2020 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. +# +# ############################################################################## +# View readme for usage details. +# +# Version - see gut.gd +# ############################################################################## +# Class that all test scripts must extend. +# +# This provides all the asserts and other testing features. Test scripts are +# run by the Gut class in gut.gd +# ############################################################################## +extends Node + +# ------------------------------------------------------------------------------ +# Helper class to hold info for objects to double. This extracts info and has +# some convenience methods. This is key in being able to make the "smart double" +# method which makes doubling much easier for the user. +# ----------------------------------------------------------------------------- +class DoubleInfo: + var path + var subpath + var strategy + var make_partial + var extension + var _utils = load('res://addons/gut/utils.gd').get_instance() + var _is_native = false + var is_valid = false + + # Flexible init method. p2 can be subpath or stategy unless p3 is + # specified, then p2 must be subpath and p3 is strategy. + # + # Examples: + # (object_to_double) + # (object_to_double, subpath) + # (object_to_double, strategy) + # (object_to_double, subpath, strategy) + func _init(thing, p2=null, p3=null): + strategy = p2 + + # short-circuit and ensure that is_valid + # is not set to true. + if(_utils.is_instance(thing)): + return + + if(typeof(p2) == TYPE_STRING): + strategy = p3 + subpath = p2 + + if(typeof(thing) == TYPE_OBJECT): + if(_utils.is_native_class(thing)): + path = thing + _is_native = true + extension = 'native_class_not_used' + else: + path = thing.resource_path + else: + path = thing + + if(!_is_native): + extension = path.get_extension() + + is_valid = true + + func is_scene(): + return extension == 'tscn' + + func is_script(): + return extension == 'gd' + + func is_native(): + return _is_native + +# ------------------------------------------------------------------------------ +# Begin test.gd +# ------------------------------------------------------------------------------ +var _utils = load('res://addons/gut/utils.gd').get_instance() +var _compare = _utils.Comparator.new() + +# constant for signal when calling yield_for +const YIELD = 'timeout' + +# Need a reference to the instance that is running the tests. This +# is set by the gut class when it runs the tests. This gets you +# access to the asserts in the tests you write. +var gut = null + +var _disable_strict_datatype_checks = false +# Holds all the text for a test's fail/pass. This is used for testing purposes +# to see the text of a failed sub-test in test_test.gd +var _fail_pass_text = [] + +const EDITOR_PROPERTY = PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT +const VARIABLE_PROPERTY = PROPERTY_USAGE_SCRIPT_VARIABLE + +# Used with assert_setget +enum { + DEFAULT_SETTER_GETTER, + SETTER_ONLY, + GETTER_ONLY +} + +# Summary counts for the test. +var _summary = { + asserts = 0, + passed = 0, + failed = 0, + tests = 0, + pending = 0 +} + +# This is used to watch signals so we can make assertions about them. +var _signal_watcher = load('res://addons/gut/signal_watcher.gd').new() + +# Convenience copy of _utils.DOUBLE_STRATEGY +var DOUBLE_STRATEGY = null +var _lgr = _utils.get_logger() +var _strutils = _utils.Strutils.new() + +# syntax sugar +var ParameterFactory = _utils.ParameterFactory +var CompareResult = _utils.CompareResult +var InputFactory = _utils.InputFactory +var InputSender = _utils.InputSender + +func _init(): + DOUBLE_STRATEGY = _utils.DOUBLE_STRATEGY # yes, this is right + +func _str(thing): + return _strutils.type2str(thing) + +# ------------------------------------------------------------------------------ +# Fail an assertion. Causes test and script to fail as well. +# ------------------------------------------------------------------------------ +func _fail(text): + _summary.asserts += 1 + _summary.failed += 1 + _fail_pass_text.append('failed: ' + text) + if(gut): + _lgr.failed(text) + gut._fail(text) + +# ------------------------------------------------------------------------------ +# Pass an assertion. +# ------------------------------------------------------------------------------ +func _pass(text): + _summary.asserts += 1 + _summary.passed += 1 + _fail_pass_text.append('passed: ' + text) + if(gut): + _lgr.passed(text) + gut._pass(text) + +# ------------------------------------------------------------------------------ +# Checks if the datatypes passed in match. If they do not then this will cause +# a fail to occur. If they match then TRUE is returned, FALSE if not. This is +# used in all the assertions that compare values. +# ------------------------------------------------------------------------------ +func _do_datatypes_match__fail_if_not(got, expected, text): + var did_pass = true + + if(!_disable_strict_datatype_checks): + var got_type = typeof(got) + var expect_type = typeof(expected) + if(got_type != expect_type and got != null and expected != null): + # If we have a mismatch between float and int (types 2 and 3) then + # print out a warning but do not fail. + if([2, 3].has(got_type) and [2, 3].has(expect_type)): + _lgr.warn(str('Warn: Float/Int comparison. Got ', _strutils.types[got_type], + ' but expected ', _strutils.types[expect_type])) + else: + _fail('Cannot compare ' + _strutils.types[got_type] + '[' + _str(got) + '] to ' + \ + _strutils.types[expect_type] + '[' + _str(expected) + ']. ' + text) + did_pass = false + + return did_pass + +# ------------------------------------------------------------------------------ +# Create a string that lists all the methods that were called on an spied +# instance. +# ------------------------------------------------------------------------------ +func _get_desc_of_calls_to_instance(inst): + var BULLET = ' * ' + var calls = gut.get_spy().get_call_list_as_string(inst) + # indent all the calls + calls = BULLET + calls.replace("\n", "\n" + BULLET) + # remove trailing newline and bullet + calls = calls.substr(0, calls.length() - BULLET.length() - 1) + return "Calls made on " + str(inst) + "\n" + calls + +# ------------------------------------------------------------------------------ +# Signal assertion helper. Do not call directly, use _can_make_signal_assertions +# ------------------------------------------------------------------------------ +func _fail_if_does_not_have_signal(object, signal_name): + var did_fail = false + if(!_signal_watcher.does_object_have_signal(object, signal_name)): + _fail(str('Object ', object, ' does not have the signal [', signal_name, ']')) + did_fail = true + return did_fail + +# ------------------------------------------------------------------------------ +# Signal assertion helper. Do not call directly, use _can_make_signal_assertions +# ------------------------------------------------------------------------------ +func _fail_if_not_watching(object): + var did_fail = false + if(!_signal_watcher.is_watching_object(object)): + _fail(str('Cannot make signal assertions because the object ', object, \ + ' is not being watched. Call watch_signals(some_object) to be able to make assertions about signals.')) + did_fail = true + return did_fail + +# ------------------------------------------------------------------------------ +# Returns text that contains original text and a list of all the signals that +# were emitted for the passed in object. +# ------------------------------------------------------------------------------ +func _get_fail_msg_including_emitted_signals(text, object): + return str(text," (Signals emitted: ", _signal_watcher.get_signals_emitted(object), ")") + +# ------------------------------------------------------------------------------ +# This validates that parameters is an array and generates a specific error +# and a failure with a specific message +# ------------------------------------------------------------------------------ +func _fail_if_parameters_not_array(parameters): + var invalid = parameters != null and typeof(parameters) != TYPE_ARRAY + if(invalid): + _lgr.error('The "parameters" parameter must be an array of expected parameter values.') + _fail('Cannot compare paramter values because an array was not passed.') + return invalid + + +func _create_obj_from_type(type): + var obj = null + if type.is_class("PackedScene"): + obj = type.instance() + add_child(obj) + else: + obj = type.new() + return obj + + +# ####################### +# Virtual Methods +# ####################### + +# alias for prerun_setup +func before_all(): + pass + +# alias for setup +func before_each(): + pass + +# alias for postrun_teardown +func after_all(): + pass + +# alias for teardown +func after_each(): + pass + +# ####################### +# Public +# ####################### + +func get_logger(): + return _lgr + +func set_logger(logger): + _lgr = logger + + +# ####################### +# Asserts +# ####################### + +# ------------------------------------------------------------------------------ +# Asserts that the expected value equals the value got. +# ------------------------------------------------------------------------------ +func assert_eq(got, expected, text=""): + + if(_do_datatypes_match__fail_if_not(got, expected, text)): + var disp = "[" + _str(got) + "] expected to equal [" + _str(expected) + "]: " + text + var result = null + + if(typeof(got) == TYPE_ARRAY): + result = _compare.shallow(got, expected) + else: + result = _compare.simple(got, expected) + + if(typeof(got) in [TYPE_ARRAY, TYPE_DICTIONARY]): + disp = str(result.summary, ' ', text) + + if(result.are_equal): + _pass(disp) + else: + _fail(disp) + + +# ------------------------------------------------------------------------------ +# Asserts that the value got does not equal the "not expected" value. +# ------------------------------------------------------------------------------ +func assert_ne(got, not_expected, text=""): + if(_do_datatypes_match__fail_if_not(got, not_expected, text)): + var disp = "[" + _str(got) + "] expected to not equal [" + _str(not_expected) + "]: " + text + var result = null + + if(typeof(got) == TYPE_ARRAY): + result = _compare.shallow(got, not_expected) + else: + result = _compare.simple(got, not_expected) + + if(typeof(got) in [TYPE_ARRAY, TYPE_DICTIONARY]): + disp = str(result.summary, ' ', text) + + if(result.are_equal): + _fail(disp) + else: + _pass(disp) + + +# ------------------------------------------------------------------------------ +# Asserts that the expected value almost equals the value got. +# ------------------------------------------------------------------------------ +func assert_almost_eq(got, expected, error_interval, text=''): + var disp = "[" + _str(got) + "] expected to equal [" + _str(expected) + "] +/- [" + str(error_interval) + "]: " + text + if(_do_datatypes_match__fail_if_not(got, expected, text) and _do_datatypes_match__fail_if_not(got, error_interval, text)): + if not _is_almost_eq(got, expected, error_interval): + _fail(disp) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Asserts that the expected value does not almost equal the value got. +# ------------------------------------------------------------------------------ +func assert_almost_ne(got, not_expected, error_interval, text=''): + var disp = "[" + _str(got) + "] expected to not equal [" + _str(not_expected) + "] +/- [" + str(error_interval) + "]: " + text + if(_do_datatypes_match__fail_if_not(got, not_expected, text) and _do_datatypes_match__fail_if_not(got, error_interval, text)): + if _is_almost_eq(got, not_expected, error_interval): + _fail(disp) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Helper function which correctly compares two variables, +# while properly handling vector2/3 types +# ------------------------------------------------------------------------------ +func _is_almost_eq(got, expected, error_interval) -> bool: + var result = false + if typeof(got) == TYPE_VECTOR2: + if got.x >= (expected.x - error_interval.x) and got.x <= (expected.x + error_interval.x): + if got.y >= (expected.y - error_interval.y) and got.y <= (expected.y + error_interval.y): + result = true + elif typeof(got) == TYPE_VECTOR3: + if got.x >= (expected.x - error_interval.x) and got.x <= (expected.x + error_interval.x): + if got.y >= (expected.y - error_interval.y) and got.y <= (expected.y + error_interval.y): + if got.z >= (expected.z - error_interval.z) and got.z <= (expected.z + error_interval.z): + result = true + elif(got >= (expected - error_interval) and got <= (expected + error_interval)): + result = true + return(result) + +# ------------------------------------------------------------------------------ +# Asserts got is greater than expected +# ------------------------------------------------------------------------------ +func assert_gt(got, expected, text=""): + var disp = "[" + _str(got) + "] expected to be > than [" + _str(expected) + "]: " + text + if(_do_datatypes_match__fail_if_not(got, expected, text)): + if(got > expected): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Asserts got is less than expected +# ------------------------------------------------------------------------------ +func assert_lt(got, expected, text=""): + var disp = "[" + _str(got) + "] expected to be < than [" + _str(expected) + "]: " + text + if(_do_datatypes_match__fail_if_not(got, expected, text)): + if(got < expected): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# asserts that got is true +# ------------------------------------------------------------------------------ +func assert_true(got, text=""): + if(typeof(got) == TYPE_BOOL): + if(got): + _pass(text) + else: + _fail(text) + else: + var msg = str("Cannot convert ", _strutils.type2str(got), " to boolean") + _fail(msg) + +# ------------------------------------------------------------------------------ +# Asserts that got is false +# ------------------------------------------------------------------------------ +func assert_false(got, text=""): + if(typeof(got) == TYPE_BOOL): + if(got): + _fail(text) + else: + _pass(text) + else: + var msg = str("Cannot convert ", _strutils.type2str(got), " to boolean") + _fail(msg) + +# ------------------------------------------------------------------------------ +# Asserts value is between (inclusive) the two expected values. +# ------------------------------------------------------------------------------ +func assert_between(got, expect_low, expect_high, text=""): + var disp = "[" + _str(got) + "] expected to be between [" + _str(expect_low) + "] and [" + str(expect_high) + "]: " + text + + if(_do_datatypes_match__fail_if_not(got, expect_low, text) and _do_datatypes_match__fail_if_not(got, expect_high, text)): + if(expect_low > expect_high): + disp = "INVALID range. [" + str(expect_low) + "] is not less than [" + str(expect_high) + "]" + _fail(disp) + else: + if(got < expect_low or got > expect_high): + _fail(disp) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Asserts value is not between (exclusive) the two expected values. +# ------------------------------------------------------------------------------ +func assert_not_between(got, expect_low, expect_high, text=""): + var disp = "[" + _str(got) + "] expected not to be between [" + _str(expect_low) + "] and [" + str(expect_high) + "]: " + text + + if(_do_datatypes_match__fail_if_not(got, expect_low, text) and _do_datatypes_match__fail_if_not(got, expect_high, text)): + if(expect_low > expect_high): + disp = "INVALID range. [" + str(expect_low) + "] is not less than [" + str(expect_high) + "]" + _fail(disp) + else: + if(got > expect_low and got < expect_high): + _fail(disp) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Uses the 'has' method of the object passed in to determine if it contains +# the passed in element. +# ------------------------------------------------------------------------------ +func assert_has(obj, element, text=""): + var disp = str('Expected [', _str(obj), '] to contain value: [', _str(element), ']: ', text) + if(obj.has(element)): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ +func assert_does_not_have(obj, element, text=""): + var disp = str('Expected [', _str(obj), '] to NOT contain value: [', _str(element), ']: ', text) + if(obj.has(element)): + _fail(disp) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Asserts that a file exists +# ------------------------------------------------------------------------------ +func assert_file_exists(file_path): + var disp = 'expected [' + file_path + '] to exist.' + var f = File.new() + if(f.file_exists(file_path)): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Asserts that a file should not exist +# ------------------------------------------------------------------------------ +func assert_file_does_not_exist(file_path): + var disp = 'expected [' + file_path + '] to NOT exist' + var f = File.new() + if(!f.file_exists(file_path)): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Asserts the specified file is empty +# ------------------------------------------------------------------------------ +func assert_file_empty(file_path): + var disp = 'expected [' + file_path + '] to be empty' + var f = File.new() + if(f.file_exists(file_path) and gut.is_file_empty(file_path)): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Asserts the specified file is not empty +# ------------------------------------------------------------------------------ +func assert_file_not_empty(file_path): + var disp = 'expected [' + file_path + '] to contain data' + if(!gut.is_file_empty(file_path)): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Asserts the object has the specified method +# ------------------------------------------------------------------------------ +func assert_has_method(obj, method, text=''): + var disp = _str(obj) + ' should have method: ' + method + if(text != ''): + disp = _str(obj) + ' ' + text + assert_true(obj.has_method(method), disp) + +# Old deprecated method name +func assert_get_set_methods(obj, property, default, set_to): + _lgr.deprecated('assert_get_set_methods', 'assert_accessors') + assert_accessors(obj, property, default, set_to) + +# ------------------------------------------------------------------------------ +# Verifies the object has get and set methods for the property passed in. The +# property isn't tied to anything, just a name to be appended to the end of +# get_ and set_. Asserts the get_ and set_ methods exist, if not, it stops there. +# If they exist then it asserts get_ returns the expected default then calls +# set_ and asserts get_ has the value it was set to. +# ------------------------------------------------------------------------------ +func assert_accessors(obj, property, default, set_to): + var fail_count = _summary.failed + var get_func = 'get_' + property + var set_func = 'set_' + property + + if(obj.has_method('is_' + property)): + get_func = 'is_' + property + + assert_has_method(obj, get_func, 'should have getter starting with get_ or is_') + assert_has_method(obj, set_func) + # SHORT CIRCUIT + if(_summary.failed > fail_count): + return + assert_eq(obj.call(get_func), default, 'It should have the expected default value.') + obj.call(set_func, set_to) + assert_eq(obj.call(get_func), set_to, 'The set value should have been returned.') + + +# --------------------------------------------------------------------------- +# Property search helper. Used to retrieve Dictionary of specified property +# from passed object. Returns null if not found. +# If provided, property_usage constrains the type of property returned by +# passing either: +# EDITOR_PROPERTY for properties defined as: export(int) var some_value +# VARIABLE_PROPERTY for properties defined as: var another_value +# --------------------------------------------------------------------------- +func _find_object_property(obj, property_name, property_usage=null): + var result = null + var found = false + var properties = obj.get_property_list() + + while !found and !properties.empty(): + var property = properties.pop_back() + if property['name'] == property_name: + if property_usage == null or property['usage'] == property_usage: + result = property + found = true + return result + +# ------------------------------------------------------------------------------ +# Asserts a class exports a variable. +# ------------------------------------------------------------------------------ +func assert_exports(obj, property_name, type): + var disp = 'expected %s to have editor property [%s]' % [_str(obj), property_name] + var property = _find_object_property(obj, property_name, EDITOR_PROPERTY) + if property != null: + disp += ' of type [%s]. Got type [%s].' % [_strutils.types[type], _strutils.types[property['type']]] + if property['type'] == type: + _pass(disp) + else: + _fail(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Signal assertion helper. +# +# Verifies that the object and signal are valid for making signal assertions. +# This will fail with specific messages that indicate why they are not valid. +# This returns true/false to indicate if the object and signal are valid. +# ------------------------------------------------------------------------------ +func _can_make_signal_assertions(object, signal_name): + return !(_fail_if_not_watching(object) or _fail_if_does_not_have_signal(object, signal_name)) + +# ------------------------------------------------------------------------------ +# Check if an object is connected to a signal on another object. Returns True +# if it is and false otherwise +# ------------------------------------------------------------------------------ +func _is_connected(signaler_obj, connect_to_obj, signal_name, method_name=""): + if(method_name != ""): + return signaler_obj.is_connected(signal_name, connect_to_obj, method_name) + else: + var connections = signaler_obj.get_signal_connection_list(signal_name) + for conn in connections: + if((conn.source == signaler_obj) and (conn.target == connect_to_obj)): + return true + return false +# ------------------------------------------------------------------------------ +# Watch the signals for an object. This must be called before you can make +# any assertions about the signals themselves. +# ------------------------------------------------------------------------------ +func watch_signals(object): + _signal_watcher.watch_signals(object) + +# ------------------------------------------------------------------------------ +# Asserts that an object is connected to a signal on another object +# +# This will fail with specific messages if the target object is not connected +# to the specified signal on the source object. +# ------------------------------------------------------------------------------ +func assert_connected(signaler_obj, connect_to_obj, signal_name, method_name=""): + pass + var method_disp = '' + if (method_name != ""): + method_disp = str(' using method: [', method_name, '] ') + var disp = str('Expected object ', _str(signaler_obj),\ + ' to be connected to signal: [', signal_name, '] on ',\ + _str(connect_to_obj), method_disp) + if(_is_connected(signaler_obj, connect_to_obj, signal_name, method_name)): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Asserts that an object is not connected to a signal on another object +# +# This will fail with specific messages if the target object is connected +# to the specified signal on the source object. +# ------------------------------------------------------------------------------ +func assert_not_connected(signaler_obj, connect_to_obj, signal_name, method_name=""): + var method_disp = '' + if (method_name != ""): + method_disp = str(' using method: [', method_name, '] ') + var disp = str('Expected object ', _str(signaler_obj),\ + ' to not be connected to signal: [', signal_name, '] on ',\ + _str(connect_to_obj), method_disp) + if(_is_connected(signaler_obj, connect_to_obj, signal_name, method_name)): + _fail(disp) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Asserts that a signal has been emitted at least once. +# +# This will fail with specific messages if the object is not being watched or +# the object does not have the specified signal +# ------------------------------------------------------------------------------ +func assert_signal_emitted(object, signal_name, text=""): + var disp = str('Expected object ', _str(object), ' to have emitted signal [', signal_name, ']: ', text) + if(_can_make_signal_assertions(object, signal_name)): + if(_signal_watcher.did_emit(object, signal_name)): + _pass(disp) + else: + _fail(_get_fail_msg_including_emitted_signals(disp, object)) + +# ------------------------------------------------------------------------------ +# Asserts that a signal has not been emitted. +# +# This will fail with specific messages if the object is not being watched or +# the object does not have the specified signal +# ------------------------------------------------------------------------------ +func assert_signal_not_emitted(object, signal_name, text=""): + var disp = str('Expected object ', _str(object), ' to NOT emit signal [', signal_name, ']: ', text) + if(_can_make_signal_assertions(object, signal_name)): + if(_signal_watcher.did_emit(object, signal_name)): + _fail(disp) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Asserts that a signal was fired with the specified parameters. The expected +# parameters should be passed in as an array. An optional index can be passed +# when a signal has fired more than once. The default is to retrieve the most +# recent emission of the signal. +# +# This will fail with specific messages if the object is not being watched or +# the object does not have the specified signal +# ------------------------------------------------------------------------------ +func assert_signal_emitted_with_parameters(object, signal_name, parameters, index=-1): + if(typeof(parameters) != TYPE_ARRAY): + _lgr.error("The expected parameters must be wrapped in an array, you passed: " + _str(parameters)) + _fail("Bad Parameters") + return + + var disp = str('Expected object ', _str(object), ' to emit signal [', signal_name, '] with parameters ', parameters, ', got ') + if(_can_make_signal_assertions(object, signal_name)): + if(_signal_watcher.did_emit(object, signal_name)): + var parms_got = _signal_watcher.get_signal_parameters(object, signal_name, index) + var diff_result = _compare.deep(parameters, parms_got) + if(diff_result.are_equal()): + _pass(str(disp, parms_got)) + else: + _fail(str('Expected object ', _str(object), ' to emit signal [', signal_name, '] with parameters ', diff_result.summarize())) + else: + var text = str('Object ', object, ' did not emit signal [', signal_name, ']') + _fail(_get_fail_msg_including_emitted_signals(text, object)) + +# ------------------------------------------------------------------------------ +# Assert that a signal has been emitted a specific number of times. +# +# This will fail with specific messages if the object is not being watched or +# the object does not have the specified signal +# ------------------------------------------------------------------------------ +func assert_signal_emit_count(object, signal_name, times, text=""): + if(_can_make_signal_assertions(object, signal_name)): + var count = _signal_watcher.get_emit_count(object, signal_name) + var disp = str('Expected the signal [', signal_name, '] emit count of [', count, '] to equal [', times, ']: ', text) + if(count== times): + _pass(disp) + else: + _fail(_get_fail_msg_including_emitted_signals(disp, object)) + +# ------------------------------------------------------------------------------ +# Assert that the passed in object has the specified signal +# ------------------------------------------------------------------------------ +func assert_has_signal(object, signal_name, text=""): + var disp = str('Expected object ', _str(object), ' to have signal [', signal_name, ']: ', text) + if(_signal_watcher.does_object_have_signal(object, signal_name)): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Returns the number of times a signal was emitted. -1 returned if the object +# is not being watched. +# ------------------------------------------------------------------------------ +func get_signal_emit_count(object, signal_name): + return _signal_watcher.get_emit_count(object, signal_name) + +# ------------------------------------------------------------------------------ +# Get the parmaters of a fired signal. If the signal was not fired null is +# returned. You can specify an optional index (use get_signal_emit_count to +# determine the number of times it was emitted). The default index is the +# latest time the signal was fired (size() -1 insetead of 0). The parameters +# returned are in an array. +# ------------------------------------------------------------------------------ +func get_signal_parameters(object, signal_name, index=-1): + return _signal_watcher.get_signal_parameters(object, signal_name, index) + +# ------------------------------------------------------------------------------ +# Get the parameters for a method call to a doubled object. By default it will +# return the most recent call. You can optionally specify an index. +# +# Returns: +# * an array of parameter values if a call the method was found +# * null when a call to the method was not found or the index specified was +# invalid. +# ------------------------------------------------------------------------------ +func get_call_parameters(object, method_name, index=-1): + var to_return = null + if(_utils.is_double(object)): + to_return = gut.get_spy().get_call_parameters(object, method_name, index) + else: + _lgr.error('You must pass a doulbed object to get_call_parameters.') + + return to_return + +# ------------------------------------------------------------------------------ +# Returns the call count for a method with optional paramter matching. +# ------------------------------------------------------------------------------ +func get_call_count(object, method_name, parameters=null): + return gut.get_spy().call_count(object, method_name, parameters) + +# ------------------------------------------------------------------------------ +# Assert that object is an instance of a_class +# ------------------------------------------------------------------------------ +func assert_extends(object, a_class, text=''): + _lgr.deprecated('assert_extends', 'assert_is') + assert_is(object, a_class, text) + +# Alias for assert_extends +func assert_is(object, a_class, text=''): + var disp = ''#var disp = str('Expected [', _str(object), '] to be type of [', a_class, ']: ', text) + var NATIVE_CLASS = 'GDScriptNativeClass' + var GDSCRIPT_CLASS = 'GDScript' + var bad_param_2 = 'Parameter 2 must be a Class (like Node2D or Label). You passed ' + + if(typeof(object) != TYPE_OBJECT): + _fail(str('Parameter 1 must be an instance of an object. You passed: ', _str(object))) + elif(typeof(a_class) != TYPE_OBJECT): + _fail(str(bad_param_2, _str(a_class))) + else: + var a_str = _str(a_class) + disp = str('Expected [', _str(object), '] to extend [', a_str, ']: ', text) + if(a_class.get_class() != NATIVE_CLASS and a_class.get_class() != GDSCRIPT_CLASS): + _fail(str(bad_param_2, a_str)) + else: + if(object is a_class): + _pass(disp) + else: + _fail(disp) + +func _get_typeof_string(the_type): + var to_return = "" + if(_strutils.types.has(the_type)): + to_return += str(the_type, '(', _strutils.types[the_type], ')') + else: + to_return += str(the_type) + return to_return + +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ +func assert_typeof(object, type, text=''): + var disp = str('Expected [typeof(', object, ') = ') + disp += _get_typeof_string(typeof(object)) + disp += '] to equal [' + disp += _get_typeof_string(type) + ']' + disp += '. ' + text + if(typeof(object) == type): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ +func assert_not_typeof(object, type, text=''): + var disp = str('Expected [typeof(', object, ') = ') + disp += _get_typeof_string(typeof(object)) + disp += '] to not equal [' + disp += _get_typeof_string(type) + ']' + disp += '. ' + text + if(typeof(object) != type): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Assert that text contains given search string. +# The match_case flag determines case sensitivity. +# ------------------------------------------------------------------------------ +func assert_string_contains(text, search, match_case=true): + var empty_search = 'Expected text and search strings to be non-empty. You passed \'%s\' and \'%s\'.' + var disp = 'Expected \'%s\' to contain \'%s\', match_case=%s' % [text, search, match_case] + if(text == '' or search == ''): + _fail(empty_search % [text, search]) + elif(match_case): + if(text.find(search) == -1): + _fail(disp) + else: + _pass(disp) + else: + if(text.to_lower().find(search.to_lower()) == -1): + _fail(disp) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Assert that text starts with given search string. +# match_case flag determines case sensitivity. +# ------------------------------------------------------------------------------ +func assert_string_starts_with(text, search, match_case=true): + var empty_search = 'Expected text and search strings to be non-empty. You passed \'%s\' and \'%s\'.' + var disp = 'Expected \'%s\' to start with \'%s\', match_case=%s' % [text, search, match_case] + if(text == '' or search == ''): + _fail(empty_search % [text, search]) + elif(match_case): + if(text.find(search) == 0): + _pass(disp) + else: + _fail(disp) + else: + if(text.to_lower().find(search.to_lower()) == 0): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Assert that text ends with given search string. +# match_case flag determines case sensitivity. +# ------------------------------------------------------------------------------ +func assert_string_ends_with(text, search, match_case=true): + var empty_search = 'Expected text and search strings to be non-empty. You passed \'%s\' and \'%s\'.' + var disp = 'Expected \'%s\' to end with \'%s\', match_case=%s' % [text, search, match_case] + var required_index = len(text) - len(search) + if(text == '' or search == ''): + _fail(empty_search % [text, search]) + elif(match_case): + if(text.find(search) == required_index): + _pass(disp) + else: + _fail(disp) + else: + if(text.to_lower().find(search.to_lower()) == required_index): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Assert that a method was called on an instance of a doubled class. If +# parameters are supplied then the params passed in when called must match. +# TODO make 3rd parameter "param_or_text" and add fourth parameter of "text" and +# then work some magic so this can have a "text" parameter without being +# annoying. +# ------------------------------------------------------------------------------ +func assert_called(inst, method_name, parameters=null): + var disp = str('Expected [',method_name,'] to have been called on ',_str(inst)) + + if(_fail_if_parameters_not_array(parameters)): + return + + if(!_utils.is_double(inst)): + _fail('You must pass a doubled instance to assert_called. Check the wiki for info on using double.') + else: + if(gut.get_spy().was_called(inst, method_name, parameters)): + _pass(disp) + else: + if(parameters != null): + disp += str(' with parameters ', parameters) + _fail(str(disp, "\n", _get_desc_of_calls_to_instance(inst))) + +# ------------------------------------------------------------------------------ +# Assert that a method was not called on an instance of a doubled class. If +# parameters are specified then this will only fail if it finds a call that was +# sent matching parameters. +# ------------------------------------------------------------------------------ +func assert_not_called(inst, method_name, parameters=null): + var disp = str('Expected [', method_name, '] to NOT have been called on ', _str(inst)) + + if(_fail_if_parameters_not_array(parameters)): + return + + if(!_utils.is_double(inst)): + _fail('You must pass a doubled instance to assert_not_called. Check the wiki for info on using double.') + else: + if(gut.get_spy().was_called(inst, method_name, parameters)): + if(parameters != null): + disp += str(' with parameters ', parameters) + _fail(str(disp, "\n", _get_desc_of_calls_to_instance(inst))) + else: + _pass(disp) + +# ------------------------------------------------------------------------------ +# Assert that a method on an instance of a doubled class was called a number +# of times. If parameters are specified then only calls with matching +# parameter values will be counted. +# ------------------------------------------------------------------------------ +func assert_call_count(inst, method_name, expected_count, parameters=null): + var count = gut.get_spy().call_count(inst, method_name, parameters) + + if(_fail_if_parameters_not_array(parameters)): + return + + var param_text = '' + if(parameters): + param_text = ' with parameters ' + str(parameters) + var disp = 'Expected [%s] on %s to be called [%s] times%s. It was called [%s] times.' + disp = disp % [method_name, _str(inst), expected_count, param_text, count] + + if(!_utils.is_double(inst)): + _fail('You must pass a doubled instance to assert_call_count. Check the wiki for info on using double.') + else: + if(count == expected_count): + _pass(disp) + else: + _fail(str(disp, "\n", _get_desc_of_calls_to_instance(inst))) + +# ------------------------------------------------------------------------------ +# Asserts the passed in value is null +# ------------------------------------------------------------------------------ +func assert_null(got, text=''): + var disp = str('Expected [', _str(got), '] to be NULL: ', text) + if(got == null): + _pass(disp) + else: + _fail(disp) + +# ------------------------------------------------------------------------------ +# Asserts the passed in value is null +# ------------------------------------------------------------------------------ +func assert_not_null(got, text=''): + var disp = str('Expected [', _str(got), '] to be anything but NULL: ', text) + if(got == null): + _fail(disp) + else: + _pass(disp) + +# ----------------------------------------------------------------------------- +# Asserts object has been freed from memory +# We pass in a title (since if it is freed, we lost all identity data) +# ----------------------------------------------------------------------------- +func assert_freed(obj, title='something'): + var disp = title + if(is_instance_valid(obj)): + disp = _strutils.type2str(obj) + title + assert_true(not is_instance_valid(obj), "Expected [%s] to be freed" % disp) + +# ------------------------------------------------------------------------------ +# Asserts Object has not been freed from memory +# ----------------------------------------------------------------------------- +func assert_not_freed(obj, title): + var disp = title + if(is_instance_valid(obj)): + disp = _strutils.type2str(obj) + title + assert_true(is_instance_valid(obj), "Expected [%s] to not be freed" % disp) + +# ------------------------------------------------------------------------------ +# Asserts that the current test has not introduced any new orphans. This only +# applies to the test code that preceedes a call to this method so it should be +# the last thing your test does. +# ------------------------------------------------------------------------------ +func assert_no_new_orphans(text=''): + var count = gut.get_orphan_counter().get_counter('test') + var msg = '' + if(text != ''): + msg = ': ' + text + # Note that get_counter will return -1 if the counter does not exist. This + # can happen with a misplaced assert_no_new_orphans. Checking for > 0 + # ensures this will not cause some weird failure. + if(count > 0): + _fail(str('Expected no orphans, but found ', count, msg)) + else: + _pass('No new orphans found.' + msg) + +# ------------------------------------------------------------------------------ +# Returns a dictionary that contains +# - an is_valid flag whether validation was successful or not and +# - a message that gives some information about the validation errors. +# ------------------------------------------------------------------------------ +func _validate_assert_setget_called_input(type, name_property + , name_setter, name_getter): + var obj = null + var result = {"is_valid": true, "msg": ""} + + if null == type or typeof(type) != TYPE_OBJECT or not type.is_class("Resource"): + result.is_valid = false + result.msg = str("The type parameter should be a ressource, ", _str(type), ' was passed.') + return result + + if null == double(type): + result.is_valid = false + result.msg = str("Attempt to double the type parameter failed. The type parameter should be a ressource that can be doubled.") + return result + + obj = _create_obj_from_type(type) + var property = _find_object_property(obj, str(name_property)) + + if null == property: + result.is_valid = false + result.msg += str("The property %s does not exist." % _str(name_property)) + if name_setter == "" and name_getter == "": + result.is_valid = false + result.msg += str("Either setter or getter method must be specified.") + if name_setter != "" and not obj.has_method(str(name_setter)): + result.is_valid = false + result.msg += str("Setter method %s does not exist. " % _str(name_setter)) + if name_getter != "" and not obj.has_method(str(name_getter)): + result.is_valid = false + result.msg += str("Getter method %s does not exist. " %_str(name_getter)) + + obj.free() + return result + +# ------------------------------------------------------------------------------ +# Validates the singleton_name is a string and exists. Errors when conditions +# are not met. Returns true/false if singleton_name is valid or not. +# ------------------------------------------------------------------------------ +func _validate_singleton_name(singleton_name): + var is_valid = true + if(typeof(singleton_name) != TYPE_STRING): + _lgr.error("double_singleton requires a Godot singleton name, you passed " + _str(singleton_name)) + is_valid = false + # Sometimes they have underscores in front of them, sometimes they do not. + # The doubler is smart enought of ind the right thing, so this has to be + # that smart as well. + elif(!ClassDB.class_exists(singleton_name) and !ClassDB.class_exists('_' + singleton_name)): + var txt = str("The singleton [", singleton_name, "] could not be found. ", + "Check the GlobalScope page for a list of singletons.") + _lgr.error(txt) + is_valid = false + return is_valid + + +# ------------------------------------------------------------------------------ +# Asserts the given setter and getter methods are called when the given property +# is accessed. +# ------------------------------------------------------------------------------ +func _assert_setget_called(type, name_property, setter = "", getter = ""): + var name_setter = _utils.nvl(setter, "") + var name_getter = _utils.nvl(getter, "") + + var validation = _validate_assert_setget_called_input(type, name_property, str(name_setter), str(name_getter)) + if not validation.is_valid: + _fail(validation.msg) + return + + var message = "" + var amount_calls_setter = 0 + var amount_calls_getter = 0 + var expected_calls_setter = 0 + var expected_calls_getter = 0 + var obj = _create_obj_from_type(double(type)) + + if name_setter != '': + expected_calls_setter = 1 + stub(obj, name_setter).to_do_nothing() + obj.set(name_property, null) + amount_calls_setter = gut.get_spy().call_count(obj, str(name_setter)) + + if name_getter != '': + expected_calls_getter = 1 + stub(obj, name_getter).to_do_nothing() + var __new_property = obj.get(name_property) + amount_calls_getter = gut.get_spy().call_count(obj, str(name_getter)) + + obj.free() + + # assert + + if amount_calls_setter == expected_calls_setter and amount_calls_getter == expected_calls_getter: + _pass(str("setget for %s is correctly configured." % _str(name_property))) + else: + if amount_calls_setter < expected_calls_setter: + message += " The setter was not called." + elif amount_calls_setter > expected_calls_setter: + message += " The setter was called but should not have been." + if amount_calls_getter < expected_calls_getter: + message += " The getter was not called." + elif amount_calls_getter > expected_calls_getter: + message += " The getter was called but should not have been." + _fail(str(message)) + +# ------------------------------------------------------------------------------ +# Wrapper: invokes assert_setget_called but provides a slightly more convenient +# signature +# ------------------------------------------------------------------------------ +func assert_setget( + instance, name_property, + const_or_setter = DEFAULT_SETTER_GETTER, getter="__not_set__"): + + var getter_name = null + if(getter != "__not_set__"): + getter_name = getter + + var setter_name = null + if(typeof(const_or_setter) == TYPE_INT): + if(const_or_setter in [SETTER_ONLY, DEFAULT_SETTER_GETTER]): + setter_name = str("set_", name_property) + + if(const_or_setter in [GETTER_ONLY, DEFAULT_SETTER_GETTER]): + getter_name = str("get_", name_property) + else: + setter_name = const_or_setter + + var resource = null + if instance.is_class("Resource"): + resource = instance + else: + resource = instance.get_script() + + _assert_setget_called(resource, str(name_property), setter_name, getter_name) + + +# ------------------------------------------------------------------------------ +# Wrapper: asserts if the property exists, the accessor methods exist and the +# setget keyword is set for accessor methods +# ------------------------------------------------------------------------------ +func assert_property(instance, name_property, default_value, new_value) -> void: + var free_me = [] + var resource = null + var obj = null + if instance.is_class("Resource"): + resource = instance + obj = _create_obj_from_type(resource) + free_me.append(obj) + else: + resource = instance.get_script() + obj = instance + + var name_setter = "set_" + str(name_property) + var name_getter = "get_" + str(name_property) + + var pre_fail_count = get_fail_count() + assert_accessors(obj, str(name_property), default_value, new_value) + _assert_setget_called(resource, str(name_property), name_setter, name_getter) + + for entry in free_me: + entry.free() + + # assert + if get_fail_count() == pre_fail_count: + _pass(str("The property is set up as expected.")) + else: + _fail(str("The property is not set up as expected. Examine subtests to see what failed.")) + + +# ------------------------------------------------------------------------------ +# Mark the current test as pending. +# ------------------------------------------------------------------------------ +func pending(text=""): + _summary.pending += 1 + if(gut): + _lgr.pending(text) + gut._pending(text) + +# ------------------------------------------------------------------------------ +# Returns the number of times a signal was emitted. -1 returned if the object +# is not being watched. +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# Yield for the time sent in. The optional message will be printed when +# Gut detects the yield. When the time expires the YIELD signal will be +# emitted. +# ------------------------------------------------------------------------------ +func yield_for(time, msg=''): + return gut.set_yield_time(time, msg) + +# ------------------------------------------------------------------------------ +# Yield to a signal or a maximum amount of time, whichever comes first. When +# the conditions are met the YIELD signal will be emitted. +# ------------------------------------------------------------------------------ +func yield_to(obj, signal_name, max_wait, msg=''): + watch_signals(obj) + gut.set_yield_signal_or_time(obj, signal_name, max_wait, msg) + + return gut + +# ------------------------------------------------------------------------------ +# Yield for a number of frames. The optional message will be printed. when +# Gut detects a yield. When the number of frames have elapsed (counted in gut's +# _process function) the YIELD signal will be emitted. +# ------------------------------------------------------------------------------ +func yield_frames(frames, msg=''): + if(frames <= 0): + var text = str('yeild_frames: frames must be > 0, you passed ', frames, '. 0 frames waited.') + _lgr.error(text) + frames = 0 + + gut.set_yield_frames(frames, msg) + return gut + +# ------------------------------------------------------------------------------ +# Ends a test that had a yield in it. You only need to use this if you do +# not make assertions after a yield. +# ------------------------------------------------------------------------------ +func end_test(): + _lgr.deprecated('end_test is no longer necessary, you can remove it.') + #gut.end_yielded_test() + +func get_summary(): + return _summary + +func get_fail_count(): + return _summary.failed + +func get_pass_count(): + return _summary.passed + +func get_pending_count(): + return _summary.pending + +func get_assert_count(): + return _summary.asserts + +func clear_signal_watcher(): + _signal_watcher.clear() + +func get_double_strategy(): + return gut.get_doubler().get_strategy() + +func set_double_strategy(double_strategy): + gut.get_doubler().set_strategy(double_strategy) + +func pause_before_teardown(): + gut.pause_before_teardown() + +# ------------------------------------------------------------------------------ +# Convert the _summary dictionary into text +# ------------------------------------------------------------------------------ +func get_summary_text(): + var to_return = get_script().get_path() + "\n" + to_return += str(' ', _summary.passed, ' of ', _summary.asserts, ' passed.') + if(_summary.pending > 0): + to_return += str("\n ", _summary.pending, ' pending') + if(_summary.failed > 0): + to_return += str("\n ", _summary.failed, ' failed.') + return to_return + +# ------------------------------------------------------------------------------ +# Double a script, inner class, or scene using a path or a loaded script/scene. +# +# +# ------------------------------------------------------------------------------ + +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ +func _smart_double(double_info): + var override_strat = _utils.nvl(double_info.strategy, gut.get_doubler().get_strategy()) + var to_return = null + + if(double_info.is_scene()): + if(double_info.make_partial): + to_return = gut.get_doubler().partial_double_scene(double_info.path, override_strat) + else: + to_return = gut.get_doubler().double_scene(double_info.path, override_strat) + + elif(double_info.is_native()): + if(double_info.make_partial): + to_return = gut.get_doubler().partial_double_gdnative(double_info.path) + else: + to_return = gut.get_doubler().double_gdnative(double_info.path) + + elif(double_info.is_script()): + if(double_info.subpath == null): + if(double_info.make_partial): + to_return = gut.get_doubler().partial_double(double_info.path, override_strat) + else: + to_return = gut.get_doubler().double(double_info.path, override_strat) + else: + if(double_info.make_partial): + to_return = gut.get_doubler().partial_double_inner(double_info.path, double_info.subpath, override_strat) + else: + to_return = gut.get_doubler().double_inner(double_info.path, double_info.subpath, override_strat) + return to_return + +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ +func double(thing, p2=null, p3=null): + var double_info = DoubleInfo.new(thing, p2, p3) + if(!double_info.is_valid): + _lgr.error('double requires a class or path, you passed an instance: ' + _str(thing)) + return null + + double_info.make_partial = false + + return _smart_double(double_info) + +# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ +func partial_double(thing, p2=null, p3=null): + var double_info = DoubleInfo.new(thing, p2, p3) + if(!double_info.is_valid): + _lgr.error('partial_double requires a class or path, you passed an instance: ' + _str(thing)) + return null + + double_info.make_partial = true + + return _smart_double(double_info) + +# ------------------------------------------------------------------------------ +# Doubles a Godot singleton +# ------------------------------------------------------------------------------ +func double_singleton(singleton_name): + return null + # var to_return = null + # if(_validate_singleton_name(singleton_name)): + # to_return = gut.get_doubler().double_singleton(singleton_name) + # return to_return + +# ------------------------------------------------------------------------------ +# Partial Doubles a Godot singleton +# ------------------------------------------------------------------------------ +func partial_double_singleton(singleton_name): + return null + # var to_return = null + # if(_validate_singleton_name(singleton_name)): + # to_return = gut.get_doubler().partial_double_singleton(singleton_name) + # return to_return + +# ------------------------------------------------------------------------------ +# Specifically double a scene +# ------------------------------------------------------------------------------ +func double_scene(path, strategy=null): + var override_strat = _utils.nvl(strategy, gut.get_doubler().get_strategy()) + return gut.get_doubler().double_scene(path, override_strat) + +# ------------------------------------------------------------------------------ +# Specifically double a script +# ------------------------------------------------------------------------------ +func double_script(path, strategy=null): + var override_strat = _utils.nvl(strategy, gut.get_doubler().get_strategy()) + return gut.get_doubler().double(path, override_strat) + +# ------------------------------------------------------------------------------ +# Specifically double an Inner class in a a script +# ------------------------------------------------------------------------------ +func double_inner(path, subpath, strategy=null): + var override_strat = _utils.nvl(strategy, gut.get_doubler().get_strategy()) + return gut.get_doubler().double_inner(path, subpath, override_strat) + + +# ------------------------------------------------------------------------------ +# Add a method that the doubler will ignore. You can pass this the path to a +# script or scene or a loaded script or scene. When running tests, these +# ignores are cleared after every test. +# ------------------------------------------------------------------------------ +func ignore_method_when_doubling(thing, method_name): + var double_info = DoubleInfo.new(thing) + var path = double_info.path + + if(double_info.is_scene()): + var inst = thing.instance() + if(inst.get_script()): + path = inst.get_script().get_path() + + gut.get_doubler().add_ignored_method(path, method_name) + +# ------------------------------------------------------------------------------ +# Stub something. +# +# Parameters +# 1: the thing to stub, a file path or a instance or a class +# 2: either an inner class subpath or the method name +# 3: the method name if an inner class subpath was specified +# NOTE: right now we cannot stub inner classes at the path level so this should +# only be called with two parameters. I did the work though so I'm going +# to leave it but not update the wiki. +# ------------------------------------------------------------------------------ +func stub(thing, p2, p3=null): + if(_utils.is_instance(thing) and !_utils.is_double(thing)): + _lgr.error(str('You cannot use stub on ', _str(thing), ' because it is not a double.')) + return _utils.StubParams.new() + + var method_name = p2 + var subpath = null + if(p3 != null): + subpath = p2 + method_name = p3 + + var sp = _utils.StubParams.new(thing, method_name, subpath) + gut.get_stubber().add_stub(sp) + return sp + +# ------------------------------------------------------------------------------ +# convenience wrapper. +# ------------------------------------------------------------------------------ +func simulate(obj, times, delta): + gut.simulate(obj, times, delta) + +# ------------------------------------------------------------------------------ +# Replace the node at base_node.get_node(path) with with_this. All references +# to the node via $ and get_node(...) will now return with_this. with_this will +# get all the groups that the node that was replaced had. +# +# The node that was replaced is queued to be freed. +# +# TODO see replace_by method, this could simplify the logic here. +# ------------------------------------------------------------------------------ +func replace_node(base_node, path_or_node, with_this): + var path = path_or_node + + if(typeof(path_or_node) != TYPE_STRING): + # This will cause an engine error if it fails. It always returns a + # NodePath, even if it fails. Checking the name count is the only way + # I found to check if it found something or not (after it worked I + # didn't look any farther). + path = base_node.get_path_to(path_or_node) + if(path.get_name_count() == 0): + _lgr.error('You passed an object that base_node does not have. Cannot replace node.') + return + + if(!base_node.has_node(path)): + _lgr.error(str('Could not find node at path [', path, ']')) + return + + var to_replace = base_node.get_node(path) + var parent = to_replace.get_parent() + var replace_name = to_replace.get_name() + + parent.remove_child(to_replace) + parent.add_child(with_this) + with_this.set_name(replace_name) + with_this.set_owner(parent) + + var groups = to_replace.get_groups() + for i in range(groups.size()): + with_this.add_to_group(groups[i]) + + to_replace.queue_free() + + +# ------------------------------------------------------------------------------ +# This method does a somewhat complicated dance with Gut. It assumes that Gut +# will clear its parameter handler after it finishes calling a parameterized test +# enough times. +# ------------------------------------------------------------------------------ +func use_parameters(params): + var ph = gut.get_parameter_handler() + if(ph == null): + ph = _utils.ParameterHandler.new(params) + gut.set_parameter_handler(ph) + + var output = str('(call #', ph.get_call_count() + 1, ') with paramters: ', ph.get_current_parameters()) + _lgr.log(output) + _lgr.inc_indent() + return ph.next_parameters() + +# ------------------------------------------------------------------------------ +# Marks whatever is passed in to be freed after the test finishes. It also +# returns what is passed in so you can save a line of code. +# var thing = autofree(Thing.new()) +# ------------------------------------------------------------------------------ +func autofree(thing): + gut.get_autofree().add_free(thing) + return thing + +# ------------------------------------------------------------------------------ +# Works the same as autofree except queue_free will be called on the object +# instead. This also imparts a brief pause after the test finishes so that +# the queued object has time to free. +# ------------------------------------------------------------------------------ +func autoqfree(thing): + gut.get_autofree().add_queue_free(thing) + return thing + +# ------------------------------------------------------------------------------ +# The same as autofree but it also adds the object as a child of the test. +# ------------------------------------------------------------------------------ +func add_child_autofree(node, legible_unique_name = false): + gut.get_autofree().add_free(node) + # Explicitly calling super here b/c add_child MIGHT change and I don't want + # a bug sneaking its way in here. + .add_child(node, legible_unique_name) + return node + +# ------------------------------------------------------------------------------ +# The same as autoqfree but it also adds the object as a child of the test. +# ------------------------------------------------------------------------------ +func add_child_autoqfree(node, legible_unique_name=false): + gut.get_autofree().add_queue_free(node) + # Explicitly calling super here b/c add_child MIGHT change and I don't want + # a bug sneaking its way in here. + .add_child(node, legible_unique_name) + return node + +# ------------------------------------------------------------------------------ +# Returns true if the test is passing as of the time of this call. False if not. +# ------------------------------------------------------------------------------ +func is_passing(): + if(gut.get_current_test_object() != null and + !['before_all', 'after_all'].has(gut.get_current_test_object().name)): + return gut.get_current_test_object().passed and \ + gut.get_current_test_object().assert_count > 0 + else: + _lgr.error('No current test object found. is_passing must be called inside a test.') + return null + +# ------------------------------------------------------------------------------ +# Returns true if the test is failing as of the time of this call. False if not. +# ------------------------------------------------------------------------------ +func is_failing(): + if(gut.get_current_test_object() != null and + !['before_all', 'after_all'].has(gut.get_current_test_object().name)): + return !gut.get_current_test_object().passed + else: + _lgr.error('No current test object found. is_failing must be called inside a test.') + return null + +# ------------------------------------------------------------------------------ +# Marks the test as passing. Does not override any failing asserts or calls to +# fail_test. Same as a passing assert. +# ------------------------------------------------------------------------------ +func pass_test(text): + _pass(text) + +# ------------------------------------------------------------------------------ +# Marks the test as failing. Same as a failing assert. +# ------------------------------------------------------------------------------ +func fail_test(text): + _fail(text) + +# ------------------------------------------------------------------------------ +# Peforms a deep compare on both values, a CompareResult instnace is returned. +# The optional max_differences paramter sets the max_differences to be displayed. +# ------------------------------------------------------------------------------ +func compare_deep(v1, v2, max_differences=null): + var result = _compare.deep(v1, v2) + if(max_differences != null): + result.max_differences = max_differences + return result + +# ------------------------------------------------------------------------------ +# Peforms a shallow compare on both values, a CompareResult instnace is returned. +# The optional max_differences paramter sets the max_differences to be displayed. +# ------------------------------------------------------------------------------ +func compare_shallow(v1, v2, max_differences=null): + var result = _compare.shallow(v1, v2) + if(max_differences != null): + result.max_differences = max_differences + return result + +# ------------------------------------------------------------------------------ +# Performs a deep compare and asserts the values are equal +# ------------------------------------------------------------------------------ +func assert_eq_deep(v1, v2): + var result = compare_deep(v1, v2) + if(result.are_equal): + _pass(result.get_short_summary()) + else: + _fail(result.summary) + +# ------------------------------------------------------------------------------ +# Performs a deep compare and asserts the values are not equal +# ------------------------------------------------------------------------------ +func assert_ne_deep(v1, v2): + var result = compare_deep(v1, v2) + if(!result.are_equal): + _pass(result.get_short_summary()) + else: + _fail(result.get_short_summary()) + +# ------------------------------------------------------------------------------ +# Performs a shallow compare and asserts the values are equal +# ------------------------------------------------------------------------------ +func assert_eq_shallow(v1, v2): + var result = compare_shallow(v1, v2) + if(result.are_equal): + _pass(result.get_short_summary()) + else: + _fail(result.summary) + +# ------------------------------------------------------------------------------ +# Performs a shallow compare and asserts the values are not equal +# ------------------------------------------------------------------------------ +func assert_ne_shallow(v1, v2): + var result = compare_shallow(v1, v2) + if(!result.are_equal): + _pass(result.get_short_summary()) + else: + _fail(result.get_short_summary()) -- cgit v1.2.3