## This is the base class for all test scripts.  Extend this...test the world!
class_name GutTest
extends Node

var _compare = GutUtils.Comparator.new()


# Need a reference to the instance that is running the tests.  This
# is set by the gut class when it runs the test script.
var gut: GutMain = 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

# 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 GutUtils.DOUBLE_STRATEGY
var DOUBLE_STRATEGY = GutUtils.DOUBLE_STRATEGY

var _lgr = GutUtils.get_logger()
var _strutils = GutUtils.Strutils.new()
var _awaiter = null

# syntax sugar
## Reference to [addons/gut/parameter_factory.gd] script.
var ParameterFactory = GutUtils.ParameterFactory
## @ignore
var CompareResult = GutUtils.CompareResult
## Reference to [addons/gut/input_factory.gd] script.
var InputFactory = GutUtils.InputFactory
## Reference to [GutInputSender].  This was the way you got to the [GutInputSender]
## before it was given a [code]class_name[/code]
var InputSender = GutUtils.InputSender



var _was_ready_called = false
# I haven't decided if we should be using _ready or not.  Right now gut.gd will
# call this if _ready was not called (because it was overridden without a super
# call).  Maybe gut.gd should just call _do_ready_stuff (after we rename it to
# something better).  I'm leaving all this as it is until it bothers me more.
func _do_ready_stuff():
	_awaiter = GutUtils.Awaiter.new()
	add_child(_awaiter)
	_was_ready_called = true


func _ready():
	_do_ready_stuff()


func _notification(what):
	# Tests are never expected to re-enter the tree.  Tests are removed from the
	# tree after they are run.
	if(what == NOTIFICATION_EXIT_TREE):
		_awaiter.queue_free()


func _str(thing):
	return _strutils.type2str(thing)


func _str_precision(value, precision):
	var to_return = _str(value)
	var format = str('%.', precision, 'f')
	if(typeof(value) == TYPE_FLOAT):
		to_return = format % value
	elif(typeof(value) == TYPE_VECTOR2):
		to_return = str('VECTOR2(', format % value.x, ', ', format %value.y, ')')
	elif(typeof(value) == TYPE_VECTOR3):
		to_return = str('VECTOR3(', format % value.x, ', ', format %value.y, ', ', format % value.z, ')')

	return to_return

# ------------------------------------------------------------------------------
# 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(gut.get_call_count_text() + 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]))
			elif([TYPE_STRING, TYPE_STRING_NAME].has(got_type) and [TYPE_STRING, TYPE_STRING_NAME].has(expect_type)):
				pass
			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_at 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 parameter values because an array was not passed.')
	return invalid


# ------------------------------------------------------------------------------
# A bunch of common checkes used when validating a double/method pair.  If
# everything is ok then an empty string is returned, otherwise the message
# is returned.
# ------------------------------------------------------------------------------
func _get_bad_double_or_method_message(inst, method_name, what_you_cant_do):
	var to_return = ''

	if(!GutUtils.is_double(inst)):
		to_return = str("An instance of a Double was expected, you passed:  ", _str(inst))
	elif(!inst.has_method(method_name)):
		to_return = str("You cannot ", what_you_cant_do, " [", method_name, "] because the method does not exist.  ",
			"This can happen if the method is virtual and not overloaded (i.e. _ready) ",
			"or you have mistyped the name of the method.")
	elif(!inst.__gutdbl_values.doubled_methods.has(method_name)):
		to_return = str("You cannot ", what_you_cant_do, " [", method_name, "] because ",
			_str(inst), ' does not overload it or it was ignored with ',
			'ignore_method_when_doubling.  See Doubling ',
			'Strategy in the wiki for details on including non-overloaded ',
			'methods in a double.')

	return to_return


# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
func _fail_if_not_double_or_does_not_have_method(inst, method_name):
	var to_return = OK

	var msg = _get_bad_double_or_method_message(inst, method_name, 'spy on')
	if(msg != ''):
		_fail(msg)
		to_return = ERR_INVALID_DATA

	return to_return


# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
func _create_obj_from_type(type):
	var obj = null
	if type.is_class("PackedScene"):
		obj = type.instantiate()
		add_child(obj)
	else:
		obj = type.new()
	return obj


# #######################
# Virtual Methods
# #######################

func should_skip_script():
	return false


func before_all():
	pass


func before_each():
	pass


func after_all():
	pass


func after_each():
	pass

# #######################
# Public
# #######################

## @internal
func get_logger():
	return _lgr

## @internal
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

		result = _compare.simple(got, expected)

		if(typeof(got) in [TYPE_ARRAY, TYPE_DICTIONARY]):
			disp = str(result.summary, '  ', text)
			_lgr.info('Array/Dictionary compared by value.  Use assert_same to compare references.  Use assert_eq_deep to see diff when failing.')

		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

		result = _compare.simple(got, not_expected)

		if(typeof(got) in [TYPE_ARRAY, TYPE_DICTIONARY]):
			disp = str(result.summary, '  ', text)
			_lgr.info('Array/Dictionary compared by value.  Use assert_not_same to compare references.  Use assert_ne_deep to see diff.')

		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_precision(got, 20) + "] 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_precision(got, 20) + "] 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 compares a value against a expected and a +/- range.  Compares
# all components of Vector2, Vector3, and Vector4 as well.
# ------------------------------------------------------------------------------
func _is_almost_eq(got, expected, error_interval) -> bool:
	var result = false
	var upper = expected + error_interval
	var lower = expected - error_interval

	if typeof(got) in [TYPE_VECTOR2, TYPE_VECTOR3, TYPE_VECTOR4]:
		result = got.clamp(lower, upper) == got
	else:
		result = got >= (lower) and got <= (upper)

	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 greater than or equal to expected
# ------------------------------------------------------------------------------
func assert_gte(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 got is less than or equal to expected
# ------------------------------------------------------------------------------
func assert_lte(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_precision(got, 20) + "] 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_precision(got, 20) + "] 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.'
	if(FileAccess.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'
	if(!FileAccess.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'
	if(FileAccess.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)


# ------------------------------------------------------------------------------
# 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 var some_value: int
# 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.is_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,Callable(connect_to_obj,method_name))
	else:
		var connections = signaler_obj.get_signal_connection_list(signal_name)
		for conn in connections:
			if(conn['signal'].get_name() == signal_name and conn['callable'].get_object() == 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(GutUtils.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_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(!GutUtils.is_native_class(a_class) and !GutUtils.is_gdscript(a_class)):
			_fail(str(bad_param_2, a_str))
		else:
			if(is_instance_of(object, 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):
	const empty_search = 'Expected text and search strings to be non-empty. You passed %s and %s.'
	const non_strings = 'Expected text and search to both be strings.  You passed %s and %s.'
	var disp = 'Expected \'%s\' to contain \'%s\', match_case=%s' % [text, search, match_case]
	if(typeof(text) != TYPE_STRING or typeof(search) != TYPE_STRING):
		_fail(non_strings % [_str(text), _str(search)])
	elif(text == '' or search == ''):
		_fail(empty_search % [_str(text), _str(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(_fail_if_not_double_or_does_not_have_method(inst, method_name) == OK):
		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(_fail_if_not_double_or_does_not_have_method(inst, method_name) == OK):
		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(_fail_if_not_double_or_does_not_have_method(inst, method_name) == OK):
		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_orphans_since('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)


# ------------------------------------------------------------------------------
# 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


## @deprecated: no longer supported.
func assert_setget(
	instance, name_property,
	const_or_setter = null, getter="__not_set__"):
	_lgr.deprecated('assert_property')
	_fail('assert_setget has been removed.  Use assert_property, assert_set_property, assert_readonly_property instead.')


# ------------------------------------------------------------------------------
# This will set the property through the setter and compare the result to the
# expected value.  Useful when setter is not simple.
# ------------------------------------------------------------------------------
func assert_set_property(obj, property_name, new_value, expected_value):
	pending("this hasn't been implemented yet")


# ------------------------------------------------------------------------------
# This will attempt to assign new_value to the property and verify that it
# is equal to expected_value.
# ------------------------------------------------------------------------------
func assert_readonly_property(obj, property_name, new_value, expected_value):
	pending("this hasn't been implemented yet")


# ------------------------------------------------------------------------------
# Checks the object for 'get_' and 'set_' methods for the specified property.
# If found a warning is generated.
# ------------------------------------------------------------------------------
func _warn_for_public_accessors(obj, property_name):
	var public_accessors = []
	var accessor_names = [
		str('get_', property_name),
		str('is_', property_name),
		str('set_', property_name)
	]

	for acc in accessor_names:
		if(obj.has_method(acc)):
			public_accessors.append(acc)

	if(public_accessors.size() > 0):
		_lgr.warn (str('Public accessors ', public_accessors, ' found for property ', property_name))

# ------------------------------------------------------------------------------
# Assumes backing varible with be _<property_name>.  This will perform all the
# asserts of assert_property.  Then this will set the value through the setter
# and check the backing variable value.  It will then reset throught the setter
# and set the backing variable and check the getter.
# ------------------------------------------------------------------------------
func assert_property_with_backing_variable(obj, property_name, default_value, new_value, backed_by_name=null):
	var setter_name = str('@', property_name, '_setter')
	var getter_name = str('@', property_name, '_getter')
	var backing_name = GutUtils.nvl(backed_by_name, str('_', property_name))
	var pre_fail_count = get_fail_count()

	var props = obj.get_property_list()
	var found = false
	var idx = 0
	while(idx < props.size() and !found):
		found = props[idx].name == backing_name
		idx += 1

	assert_true(found, str(obj, ' has ', backing_name, ' variable.'))
	assert_true(obj.has_method(setter_name), str('There should be a setter for ', property_name))
	assert_true(obj.has_method(getter_name), str('There should be a getter for ', property_name))

	if(pre_fail_count == get_fail_count()):
		var call_setter = Callable(obj, setter_name)
		var call_getter = Callable(obj, getter_name)

		assert_eq(obj.get(backing_name), default_value, str('Variable ', backing_name, ' has default value.'))
		assert_eq(call_getter.call(), default_value, 'Getter returns default value.')
		call_setter.call(new_value)
		assert_eq(call_getter.call(), new_value, 'Getter returns value from Setter.')
		assert_eq(obj.get(backing_name), new_value, str('Variable ', backing_name, ' was set'))

	_warn_for_public_accessors(obj, property_name)


# ------------------------------------------------------------------------------
# This will verify that the method has a setter and getter for the property.
# It will then use the getter to check the default.  Then use the
# setter with new_value and verify the getter returns the same value.
# ------------------------------------------------------------------------------
func assert_property(obj, property_name, default_value, new_value) -> void:
	var free_me = null
	var resource = null
	var pre_fail_count = get_fail_count()

	var setter_name = str('@', property_name, '_setter')
	var getter_name = str('@', property_name, '_getter')

	if(typeof(obj) != TYPE_OBJECT):
		_fail(str(_str(obj), ' is not an object'))
		return

	assert_has_method(obj, setter_name)
	assert_has_method(obj, getter_name)

	if(pre_fail_count == get_fail_count()):
		var call_setter = Callable(obj, setter_name)
		var call_getter = Callable(obj, getter_name)

		assert_eq(call_getter.call(), default_value, 'Default value')
		call_setter.call(new_value)
		assert_eq(call_getter.call(), new_value, 'Getter gets Setter value')

	_warn_for_public_accessors(obj, property_name)


# ------------------------------------------------------------------------------
## Mark the current test as pending.
func pending(text=""):
	_summary.pending += 1
	if(gut):
		_lgr.pending(text)
		gut._pending(text)


# ------------------------------------------------------------------------------
## Await for the time sent in.  The optional message will be printed when the
## await starts
func wait_seconds(time, msg=''):
	_lgr.yield_msg(str('-- Awaiting ', time, ' second(s) -- ', msg))
	_awaiter.wait_seconds(time)
	return _awaiter.timeout


# ------------------------------------------------------------------------------
## @deprecated: use wait_seconds
func yield_for(time, msg=''):
	_lgr.deprecated('yield_for', 'wait_seconds')
	return wait_seconds(time, msg)


# ------------------------------------------------------------------------------
## Yield to a signal or a maximum amount of time, whichever comes first.
func wait_for_signal(sig : Signal, max_wait, msg=''):
	watch_signals(sig.get_object())
	_lgr.yield_msg(str('-- Awaiting signal "', sig.get_name(), '" or for ', max_wait, ' second(s) -- ', msg))
	_awaiter.wait_for_signal(sig, max_wait)
	await _awaiter.timeout
	return !_awaiter.did_last_wait_timeout


# ------------------------------------------------------------------------------
## @deprecated: use wait_for_signal
func yield_to(obj, signal_name, max_wait, msg=''):
	_lgr.deprecated('yield_to', 'wait_for_signal')
	return await wait_for_signal(Signal(obj, signal_name), max_wait, msg)


# ------------------------------------------------------------------------------
## Yield for a number of frames.  The optional message will be printed. when
## Gut detects a yield.
func wait_frames(frames, msg=''):
	if(frames <= 0):
		var text = str('wait_frames:  frames must be > 0, you passed  ', frames, '.  0 frames waited.')
		_lgr.error(text)
		frames = 1

	_lgr.yield_msg(str('-- Awaiting ', frames, ' frame(s) -- ', msg))
	_awaiter.wait_frames(frames)
	return _awaiter.timeout


# ------------------------------------------------------------------------------
# p3 can be the optional message or an amount of time to wait between tests.
# p4 is the optional message if you have specified an amount of time to
#	wait between tests.
func wait_until(callable, max_wait, p3='', p4=''):
	var time_between = 0.0
	var message = p4
	if(typeof(p3) != TYPE_STRING):
		time_between = p3
	else:
		message = p3

	_lgr.yield_msg(str("--Awaiting callable to return TRUE or ", max_wait, "s.  ", message))
	_awaiter.wait_until(callable, max_wait, time_between)
	await _awaiter.timeout
	return !_awaiter.did_last_wait_timeout

## Returns whether the last wait_* method timed out.  This is always true if
## the last method was wait_frames or wait_seconds.  It will be false when
## using wait_for_signal and wait_until if the timeout occurs before what
## is being waited on.  The wait_* methods return this value so you should be
## able to avoid calling this directly, but you can.
func did_wait_timeout():
	return _awaiter.did_last_wait_timeout


## @deprecated: use wait_frames
func yield_frames(frames, msg=''):
	_lgr.deprecated("yield_frames", "wait_frames")
	return wait_frames(frames, msg)

## @internal
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

## @internal
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
# ------------------------------------------------------------------------------
## @internal
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(thing, double_strat, partial):
	var override_strat = GutUtils.nvl(double_strat, gut.get_doubler().get_strategy())
	var to_return = null

	if(thing is PackedScene):
		if(partial):
			to_return =  gut.get_doubler().partial_double_scene(thing, override_strat)
		else:
			to_return =  gut.get_doubler().double_scene(thing, override_strat)

	elif(GutUtils.is_native_class(thing)):
		if(partial):
			to_return = gut.get_doubler().partial_double_gdnative(thing)
		else:
			to_return = gut.get_doubler().double_gdnative(thing)

	elif(thing is GDScript):
		if(partial):
			to_return = gut.get_doubler().partial_double(thing, override_strat)
		else:
			to_return = gut.get_doubler().double(thing, override_strat)

	return to_return

# ------------------------------------------------------------------------------
# This is here to aid in the transition to the new doubling sytnax.  Once this
# has been established it could be removed.  We must keep the is_instance check
# going forward though.
# ------------------------------------------------------------------------------
func _are_double_parameters_valid(thing, p2, p3):
	var bad_msg = ""
	if(p3 != null or typeof(p2) == TYPE_STRING):
		bad_msg += "Doubling using a subpath is not supported.  Call register_inner_class and then pass the Inner Class to double().\n"

	if(typeof(thing) == TYPE_STRING):
		bad_msg += "Doubling using the path to a script or scene is no longer supported.  Load the script or scene and pass that to double instead.\n"

	if(GutUtils.is_instance(thing)):
		bad_msg += "double requires a script, you passed an instance:  " + _str(thing)

	if(bad_msg != ""):
		_lgr.error(bad_msg)

	return bad_msg == ""


# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
func double(thing, double_strat=null, not_used_anymore=null):
	if(!_are_double_parameters_valid(thing, double_strat, not_used_anymore)):
		return null

	return _smart_double(thing, double_strat, false)

# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
func partial_double(thing, double_strat=null, not_used_anymore=null):
	if(!_are_double_parameters_valid(thing, double_strat, not_used_anymore)):
		return null

	return _smart_double(thing, double_strat, true)

# ------------------------------------------------------------------------------
# Doubles a Godot singleton
# ------------------------------------------------------------------------------
## @internal
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
# ------------------------------------------------------------------------------
## @internal
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

## @deprecated: no longer supported.  Use double
func double_scene(path, strategy=null):
	_lgr.deprecated('test.double_scene has been removed.', 'double')
	return null



## @deprecated: no longer supported.  Use double
func double_script(path, strategy=null):
	_lgr.deprecated('test.double_script has been removed.', 'double')
	return null

	# var override_strat = GutUtils.nvl(strategy, gut.get_doubler().get_strategy())
	# return gut.get_doubler().double(path, override_strat)


## @deprecated: no longer supported.  Use register_inner_classes + double
func double_inner(path, subpath, strategy=null):
	_lgr.deprecated('double_inner should not be used.  Use register_inner_classes and double instead.', 'double')
	return null

	var override_strat = GutUtils.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 a loaded script
# or scene.  These ignores are cleared after every test.
# ------------------------------------------------------------------------------
func ignore_method_when_doubling(thing, method_name):
	if(typeof(thing) == TYPE_STRING):
		_lgr.error('ignore_method_when_doubling no longer supports paths to scripts or scenes.  Load them and pass them instead.')
		return

	var r = thing
	if(thing is PackedScene):
		r = GutUtils.get_scene_script_object(thing)

	gut.get_doubler().add_ignored_method(r, method_name)

# ------------------------------------------------------------------------------
# Stub something.
#
# Parameters
# 1: A callable OR the thing to stub OR a file path OR an instance OR a Script
# 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=null, p3=null):
	var method_name = p2
	var subpath = null

	if(p3 != null):
		subpath = p2
		method_name = p3

	if(GutUtils.is_instance(thing)):
		var msg = _get_bad_double_or_method_message(thing, method_name, 'stub')
		if(msg != ''):
			_lgr.error(msg)
			return GutUtils.StubParams.new()

	var sp = null
	if(typeof(thing) == TYPE_CALLABLE):
		if(p2 != null or p3 != null):
			_lgr.error("Only one parameter expected when using a callable.")
		sp = GutUtils.StubParams.new(thing)
	else:
		sp = GutUtils.StubParams.new(thing, method_name, subpath)

	sp.logger = _lgr
	gut.get_stubber().add_stub(sp)
	return sp

# ------------------------------------------------------------------------------
# convenience wrapper.
# ------------------------------------------------------------------------------
func simulate(obj, times, delta, check_is_processing: bool = false):
	gut.simulate(obj, times, delta, check_is_processing)

# ------------------------------------------------------------------------------
# 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.parameter_handler
	if(ph == null):
		ph = GutUtils.ParameterHandler.new(params)
		gut.parameter_handler = ph

	# DO NOT use gut.gd's get_call_count_text here since it decrements the
	# get_call_count value.  This method increments the call count in its
	# return statement.
	var output = str('- params[', ph.get_call_count(), ']','(', ph.get_current_parameters(), ')')
	gut.p(output, gut.LOG_LEVEL_TEST_AND_FAILURES)

	return ph.next_parameters()


## @internal
## When used as the default for a test method parameter, it will cause the test
## to be run x times.
##
## I Hacked this together to test a method that was occassionally failing due to
## timing issues.  I don't think it's a great idea, but you be the judge.  If
## you find a good use for it, let me know and I'll make it a legit member
## of the api.
func run_x_times(x):
	var ph = gut.parameter_handler
	if(ph == null):
		_lgr.warn(
			str("This test uses run_x_times and you really should not be ",
			"using it.  I don't think it's a good thing, but I did find it ",
			"temporarily useful so I left it in here and didn't document it.  ",
			"Well, you found it, might as well open up an issue and let me ",
			"know why you're doing this."))
		var params = []
		for i in range(x):
			params.append(i)

		ph = GutUtils.ParameterHandler.new(params)
		gut.parameter_handler = ph
	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.
	super.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.
	super.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().is_passing() 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().is_failing()
	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

# ------------------------------------------------------------------------------
# REMOVED
# ------------------------------------------------------------------------------
func compare_shallow(v1, v2, max_differences=null):
	_fail('compare_shallow has been removed.  Use compare_deep or just compare using == instead.')
	_lgr.error('compare_shallow has been removed.  Use compare_deep or just compare using == instead.')
	return null


# ------------------------------------------------------------------------------
# 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())

# ------------------------------------------------------------------------------
# REMOVED
# ------------------------------------------------------------------------------
func assert_eq_shallow(v1, v2):
	_fail('assert_eq_shallow has been removed.  Use assert_eq/assert_same/assert_eq_deep')

# ------------------------------------------------------------------------------
# REMOVED
# ------------------------------------------------------------------------------
func assert_ne_shallow(v1, v2):
	_fail('assert_eq_shallow has been removed.  Use assert_eq/assert_same/assert_eq_deep')


# ------------------------------------------------------------------------------
# Assert wrapper for is_same
# ------------------------------------------------------------------------------
func assert_same(v1, v2, text=''):
	var disp = "[" + _str(v1) + "] expected to be same as  [" + _str(v2) + "]:  " + text
	if(is_same(v1, v2)):
		_pass(disp)
	else:
		_fail(disp)

func assert_not_same(v1, v2, text=''):
	var disp = "[" + _str(v1) + "] expected to not be same as  [" + _str(v2) + "]:  " + text
	if(is_same(v1, v2)):
		_fail(disp)
	else:
		_pass(disp)

# ------------------------------------------------------------------------------
# Checks the passed in version string (x.x.x) against the engine version to see
# if the engine version is less than the expected version.  If it is then the
# test is mareked as passed (for a lack of anything better to do).  The result
# of the check is returned.
#
# Example:
# if(skip_if_godot_version_lt('3.5.0')):
# 	return
# ------------------------------------------------------------------------------
func skip_if_godot_version_lt(expected):
	var should_skip = !GutUtils.is_godot_version_gte(expected)
	if(should_skip):
		_pass(str('Skipping: ', GutUtils.godot_version_string(), ' is less than ', expected))
	return should_skip


# ------------------------------------------------------------------------------
# Checks if the passed in version matches the engine version.  The passed in
# version can contain just the major, major.minor or major.minor.path.  If
# the version is not the same then the test is marked as passed.  The result of
# the check is returned.
#
# Example:
# if(skip_if_godot_version_ne('3.4')):
# 	return
# ------------------------------------------------------------------------------
func skip_if_godot_version_ne(expected):
	var should_skip = !GutUtils.is_godot_version(expected)
	if(should_skip):
		_pass(str('Skipping: ', GutUtils.godot_version_string(), ' is not ', expected))
	return should_skip


# ------------------------------------------------------------------------------
# Registers all the inner classes in a script with the doubler.  This is required
# before you can double any inner class.
# ------------------------------------------------------------------------------
func register_inner_classes(base_script):
	gut.get_doubler().inner_class_registry.register(base_script)









# ##############################################################################
#(G)odot (U)nit (T)est class
#
# ##############################################################################
# The MIT License (MIT)
# =====================
#
# Copyright (c) 2025 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
# ##############################################################################
