godot-xterm/addons/gut/stubber.gd
2023-01-21 15:33:25 +13:00

244 lines
6.7 KiB
GDScript

# -------------
# returns{} and parameters {} have the followin structure
# -------------
# {
# inst_id_or_path1:{
# method_name1: [StubParams, StubParams],
# method_name2: [StubParams, StubParams]
# },
# inst_id_or_path2:{
# method_name1: [StubParams, StubParams],
# method_name2: [StubParams, StubParams]
# }
# }
var returns = {}
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _lgr = _utils.get_logger()
var _strutils = _utils.Strutils.new()
var _class_db_name_hash = {}
func _init():
_class_db_name_hash = _make_crazy_dynamic_over_engineered_class_db_hash()
# So, I couldn't figure out how to get to a reference for a GDNative Class
# using a string. ClassDB has all thier names...so I made a hash using those
# names and the classes. Then I dynmaically make a script that has that as
# the source and grab the hash out of it and return it. Super Rube Golbergery,
# but tons of fun.
func _make_crazy_dynamic_over_engineered_class_db_hash():
var text = "var all_the_classes = {\n"
for classname in ClassDB.get_class_list():
if ClassDB.can_instantiate(classname):
text += str('"', classname, '": ', classname, ", \n")
else:
text += str("# ", classname, "\n")
text += "}"
var inst = _utils.create_script_from_source(text).new()
return inst.all_the_classes
func _find_matches(obj, method):
var matches = null
var last_not_null_parent = null
# Search for what is passed in first. This could be a class or an instance.
# We want to find the instance before we find the class. If we do not have
# an entry for the instance then see if we have an entry for the class.
if returns.has(obj) and returns[obj].has(method):
matches = returns[obj][method]
elif _utils.is_instance(obj):
var parent = obj.get_script()
var found = false
while parent != null and !found:
found = returns.has(parent)
if !found:
last_not_null_parent = parent
parent = parent.get_base_script()
# Could not find the script so check to see if a native class of this
# type was stubbed.
if !found:
var base_type = last_not_null_parent.get_instance_base_type()
if _class_db_name_hash.has(base_type):
parent = _class_db_name_hash[base_type]
found = returns.has(parent)
if found and returns[parent].has(method):
matches = returns[parent][method]
return matches
# Searches returns for an entry that matches the instance or the class that
# passed in obj is.
#
# obj can be an instance, class, or a path.
func _find_stub(obj, method, parameters = null, find_overloads = false):
var to_return = null
var matches = _find_matches(obj, method)
if matches == null:
return null
var param_match = null
var null_match = null
var overload_match = null
for i in range(matches.size()):
var cur_stub = matches[i]
if cur_stub.parameters == parameters:
param_match = cur_stub
if cur_stub.parameters == null and !cur_stub.is_param_override_only():
null_match = cur_stub
if cur_stub.has_param_override():
if overload_match == null || overload_match.is_script_default:
overload_match = cur_stub
if find_overloads and overload_match != null:
to_return = overload_match
# We have matching parameter values so return the stub value for that
elif param_match != null:
to_return = param_match
# We found a case where the parameters were not specified so return
# parameters for that. Only do this if the null match is not *just*
# a paramerter override stub.
elif null_match != null:
to_return = null_match
return to_return
# ##############
# Public
# ##############
func add_stub(stub_params):
stub_params._lgr = _lgr
var key = stub_params.stub_target
if !returns.has(key):
returns[key] = {}
if !returns[key].has(stub_params.stub_method):
returns[key][stub_params.stub_method] = []
returns[key][stub_params.stub_method].append(stub_params)
# Gets a stubbed return value for the object and method passed in. If the
# instance was stubbed it will use that, otherwise it will use the path and
# subpath of the object to try to find a value.
#
# It will also use the optional list of parameter values to find a value. If
# the object was stubbed with no parameters than any parameters will match.
# If it was stubbed with specific parameter values then it will try to match.
# If the parameters do not match BUT there was also an empty parameter list stub
# then it will return those.
# If it cannot find anything that matches then null is returned.for
#
# Parameters
# obj: this should be an instance of a doubled object.
# method: the method called
# parameters: optional array of parameter vales to find a return value for.
func get_return(obj, method, parameters = null):
var stub_info = _find_stub(obj, method, parameters)
if stub_info != null:
return stub_info.return_val
else:
_lgr.warn(
str(
"Call to [",
method,
"] was not stubbed for the supplied parameters ",
parameters,
". Null was returned."
)
)
return null
func should_call_super(obj, method, parameters = null):
if _utils.non_super_methods.has(method):
return false
var stub_info = _find_stub(obj, method, parameters)
var is_partial = false
if typeof(obj) != TYPE_STRING: # some stubber tests test with strings
is_partial = obj.__gutdbl.is_partial
var should = is_partial
if stub_info != null:
should = stub_info.call_super
elif !is_partial:
# this log message is here because of how the generated doubled scripts
# are structured. With this log msg here, you will only see one
# "unstubbed" info instead of multiple.
_lgr.info("Unstubbed call to " + method + "::" + _strutils.type2str(obj))
should = false
return should
func get_parameter_count(obj, method):
var to_return = null
var stub_info = _find_stub(obj, method, null, true)
if stub_info != null and stub_info.has_param_override():
to_return = stub_info.parameter_count
return to_return
func get_default_value(obj, method, p_index):
var to_return = null
var stub_info = _find_stub(obj, method, null, true)
if (
stub_info != null
and stub_info.parameter_defaults != null
and stub_info.parameter_defaults.size() > p_index
):
to_return = stub_info.parameter_defaults[p_index]
return to_return
func clear():
returns.clear()
func get_logger():
return _lgr
func set_logger(logger):
_lgr = logger
func to_s():
var text = ""
for thing in returns:
text += str("-- ", thing, " --\n")
for method in returns[thing]:
text += str("\t", method, "\n")
for i in range(returns[thing][method].size()):
text += "\t\t" + returns[thing][method][i].to_s() + "\n"
if text == "":
text = "Stubber is empty"
return text
func stub_defaults_from_meta(target, method_meta):
var params = _utils.StubParams.new(target, method_meta)
params.is_script_default = true
add_stub(params)