taqin/addons/gut/test.gd

1089 lines
44 KiB
GDScript

################################################################################
#(G)odot (U)nit (T)est class
#
################################################################################
#The MIT License (MIT)
#=====================
#
#Copyright (c) 2019 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').new()
var _is_native = 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
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()
func is_scene():
return extension == 'tscn'
func is_script():
return extension == 'gd'
func is_native():
return _is_native
# ------------------------------------------------------------------------------
# Begin test.gd
# ------------------------------------------------------------------------------
# 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 passed = false
var failed = false
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 = []
# Hash containing all the built in types in Godot. This provides an English
# name for the types that corosponds with the type constants defined in the
# engine. This is used for priting out messages when comparing types fails.
var types = {}
func _init_types_dictionary():
types[TYPE_NIL] = 'TYPE_NIL'
types[TYPE_BOOL] = 'Bool'
types[TYPE_INT] = 'Int'
types[TYPE_REAL] = 'Float/Real'
types[TYPE_STRING] = 'String'
types[TYPE_VECTOR2] = 'Vector2'
types[TYPE_RECT2] = 'Rect2'
types[TYPE_VECTOR3] = 'Vector3'
#types[8] = 'Matrix32'
types[TYPE_PLANE] = 'Plane'
types[TYPE_QUAT] = 'QUAT'
types[TYPE_AABB] = 'AABB'
#types[12] = 'Matrix3'
types[TYPE_TRANSFORM] = 'Transform'
types[TYPE_COLOR] = 'Color'
#types[15] = 'Image'
types[TYPE_NODE_PATH] = 'Node Path'
types[TYPE_RID] = 'RID'
types[TYPE_OBJECT] = 'TYPE_OBJECT'
#types[19] = 'TYPE_INPUT_EVENT'
types[TYPE_DICTIONARY] = 'Dictionary'
types[TYPE_ARRAY] = 'Array'
types[TYPE_RAW_ARRAY] = 'TYPE_RAW_ARRAY'
types[TYPE_INT_ARRAY] = 'TYPE_INT_ARRAY'
types[TYPE_REAL_ARRAY] = 'TYPE_REAL_ARRAY'
types[TYPE_STRING_ARRAY] = 'TYPE_STRING_ARRAY'
types[TYPE_VECTOR2_ARRAY] = 'TYPE_VECTOR2_ARRAY'
types[TYPE_VECTOR3_ARRAY] = 'TYPE_VECTOR3_ARRAY'
types[TYPE_COLOR_ARRAY] = 'TYPE_COLOR_ARRAY'
types[TYPE_MAX] = 'TYPE_MAX'
const EDITOR_PROPERTY = PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT
const VARIABLE_PROPERTY = PROPERTY_USAGE_SCRIPT_VARIABLE
# 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 _utils = load('res://addons/gut/utils.gd').new()
var _lgr = _utils.get_logger()
func _init():
_init_types_dictionary()
DOUBLE_STRATEGY = _utils.DOUBLE_STRATEGY # yes, this is right
# ------------------------------------------------------------------------------
# Fail an assertion. Causes test and script to fail as well.
# ------------------------------------------------------------------------------
func _fail(text):
_summary.asserts += 1
_summary.failed += 1
var msg = 'FAILED: ' + text
_fail_pass_text.append(msg)
if(gut):
gut.p(msg, gut.LOG_LEVEL_FAIL_ONLY)
gut._fail(text)
# ------------------------------------------------------------------------------
# Pass an assertion.
# ------------------------------------------------------------------------------
func _pass(text):
_summary.asserts += 1
_summary.passed += 1
var msg = "PASSED: " + text
_fail_pass_text.append(msg)
if(gut):
gut.p(msg, gut.LOG_LEVEL_ALL_ASSERTS)
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 passed = 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 ', types[got_type], ' but expected ', types[expect_type]))
else:
_fail('Cannot compare ' + types[got_type] + '[' + str(got) + '] to ' + types[expect_type] + '[' + str(expected) + ']. ' + text)
passed = false
return passed
# ------------------------------------------------------------------------------
# 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
# #######################
# 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=""):
var disp = "[" + str(got) + "] expected to equal [" + str(expected) + "]: " + text
if(_do_datatypes_match__fail_if_not(got, expected, text)):
if(expected != got):
_fail(disp)
else:
_pass(disp)
# ------------------------------------------------------------------------------
# Asserts that the value got does not equal the "not expected" value.
# ------------------------------------------------------------------------------
func assert_ne(got, not_expected, text=""):
var disp = "[" + str(got) + "] expected to be anything except [" + str(not_expected) + "]: " + text
if(_do_datatypes_match__fail_if_not(got, not_expected, text)):
if(got == not_expected):
_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(got < (expected - error_interval) or 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(got < (not_expected - error_interval) or got > (not_expected + error_interval)):
_pass(disp)
else:
_fail(disp)
# ------------------------------------------------------------------------------
# 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(!got):
_fail(text)
else:
_pass(text)
# ------------------------------------------------------------------------------
# Asserts that got is false
# ------------------------------------------------------------------------------
func assert_false(got, text=""):
if(got):
_fail(text)
else:
_pass(text)
# ------------------------------------------------------------------------------
# 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)
# ------------------------------------------------------------------------------
# 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 [', obj, '] to contain value: [', element, ']: ', text)
if(obj.has(element)):
_pass(disp)
else:
_fail(disp)
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
func assert_does_not_have(obj, element, text=""):
var disp = str('Expected [', obj, '] to NOT contain value: [', 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):
assert_true(obj.has_method(method), 'Should have method: ' + method)
# 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 = 'get_' + property
var set = 'set_' + property
assert_has_method(obj, get)
assert_has_method(obj, set)
# SHORT CIRCUIT
if(_summary.failed > fail_count):
return
assert_eq(obj.call(get), default, 'It should have the expected default value.')
obj.call(set, set_to)
assert_eq(obj.call(get), 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 defunded 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]' % [obj, property_name]
var property = _find_object_property(obj, property_name, EDITOR_PROPERTY)
if property != null:
disp += ' of type [%s]. Got type [%s].' % [types[type], 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))
# ------------------------------------------------------------------------------
# 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 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 ', 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 ', 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):
var disp = str('Expected object ', 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)
if(parameters == parms_got):
_pass(str(disp, parms_got))
else:
_fail(str(disp, parms_got))
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 ', 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
# ------------------------------------------------------------------------------
# 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 = str('Expected [', 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: ', types[typeof(object)]))
elif(typeof(a_class) != TYPE_OBJECT):
_fail(str(bad_param_2, types[typeof(a_class)]))
else:
disp = str('Expected [', object.get_class(), '] to extend [', a_class.get_class(), ']: ', text)
if(a_class.get_class() != NATIVE_CLASS and a_class.get_class() != GDSCRIPT_CLASS):
_fail(str(bad_param_2, a_class.get_class(), ' ', types[typeof(a_class)]))
else:
if(object is a_class):
_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 ',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 ', 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, 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 [', 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 [', 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):
assert_true(not is_instance_valid(obj), "Object %s is freed" % title)
# ------------------------------------------------------------------------------
# Asserts Object has not been freed from memory
# -----------------------------------------------------------------------------
func assert_not_freed(obj, title):
assert_true(is_instance_valid(obj), "Object %s is not freed" % title)
# ------------------------------------------------------------------------------
# Mark the current test as pending.
# ------------------------------------------------------------------------------
func pending(text=""):
_summary.pending += 1
if(gut):
if(text == ""):
gut.p("Pending")
else:
gut.p("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
# ------------------------------------------------------------------------------
# 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)
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)
double_info.make_partial = true
return _smart_double(double_info)
# ------------------------------------------------------------------------------
# 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):
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.
# ------------------------------------------------------------------------------
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()