2022-06-02 04:13:43 +02:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Used to keep track of info about each test ran.
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class Test:
|
|
|
|
# indicator if it passed or not. defaults to true since it takes only
|
|
|
|
# one failure to make it not pass. _fail in gut will set this.
|
|
|
|
var passed = true
|
|
|
|
# the name of the function
|
|
|
|
var name = ""
|
|
|
|
# flag to know if the name has been printed yet.
|
|
|
|
var has_printed_name = false
|
|
|
|
# the number of arguments the method has
|
|
|
|
var arg_count = 0
|
|
|
|
# The number of asserts in the test
|
|
|
|
var assert_count = 0
|
|
|
|
# if the test has been marked pending at anypont during
|
|
|
|
# execution.
|
|
|
|
var pending = false
|
2023-01-07 20:26:17 +01:00
|
|
|
# the line number when the test fails
|
|
|
|
var line_number = -1
|
|
|
|
# Set this to true to prevent GUT from running the test.
|
|
|
|
var should_skip = false
|
|
|
|
|
|
|
|
func did_pass():
|
|
|
|
return passed and !pending and assert_count > 0
|
|
|
|
|
|
|
|
func did_assert():
|
|
|
|
return assert_count > 0 or pending
|
2022-06-02 04:13:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# This holds all the meta information for a test script. It contains the
|
|
|
|
# name of the inner class and an array of Test "structs".
|
|
|
|
#
|
|
|
|
# This class also facilitates all the exporting and importing of tests.
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class TestScript:
|
2023-01-20 23:34:39 +01:00
|
|
|
var inner_class_name: StringName
|
2022-06-02 04:13:43 +02:00
|
|
|
var tests = []
|
2023-01-20 23:34:39 +01:00
|
|
|
var path: String
|
2022-06-02 04:13:43 +02:00
|
|
|
var _utils = null
|
|
|
|
var _lgr = null
|
2023-01-07 20:26:17 +01:00
|
|
|
var is_loaded = false
|
2022-06-02 04:13:43 +02:00
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
func _init(utils = null, logger = null):
|
2022-06-02 04:13:43 +02:00
|
|
|
_utils = utils
|
|
|
|
_lgr = logger
|
|
|
|
|
|
|
|
func to_s():
|
|
|
|
var to_return = path
|
2023-01-20 23:34:39 +01:00
|
|
|
if inner_class_name != null:
|
|
|
|
to_return += str(".", inner_class_name)
|
2022-06-02 04:13:43 +02:00
|
|
|
to_return += "\n"
|
|
|
|
for i in range(tests.size()):
|
2023-01-20 23:34:39 +01:00
|
|
|
to_return += str(" ", tests[i].name, "\n")
|
2022-06-02 04:13:43 +02:00
|
|
|
return to_return
|
|
|
|
|
|
|
|
func get_new():
|
|
|
|
return load_script().new()
|
|
|
|
|
|
|
|
func load_script():
|
|
|
|
var to_return = load(path)
|
2023-01-07 20:26:17 +01:00
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
if inner_class_name != null and inner_class_name != "":
|
2022-06-02 04:13:43 +02:00
|
|
|
# If we wanted to do inner classes in inner classses
|
|
|
|
# then this would have to become some kind of loop or recursive
|
|
|
|
# call to go all the way down the chain or this class would
|
|
|
|
# have to change to hold onto the loaded class instead of
|
|
|
|
# just path information.
|
|
|
|
to_return = to_return.get(inner_class_name)
|
2023-01-07 20:26:17 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
return to_return
|
|
|
|
|
|
|
|
func get_filename_and_inner():
|
2023-01-07 20:26:17 +01:00
|
|
|
var to_return = get_filename()
|
2023-01-20 23:34:39 +01:00
|
|
|
if inner_class_name != "":
|
|
|
|
to_return += "." + String(inner_class_name)
|
2022-06-02 04:13:43 +02:00
|
|
|
return to_return
|
|
|
|
|
|
|
|
func get_full_name():
|
|
|
|
var to_return = path
|
2023-01-20 23:34:39 +01:00
|
|
|
if inner_class_name != "":
|
|
|
|
to_return += "." + String(inner_class_name)
|
2022-06-02 04:13:43 +02:00
|
|
|
return to_return
|
|
|
|
|
2023-01-07 20:26:17 +01:00
|
|
|
func get_filename():
|
2022-06-02 04:13:43 +02:00
|
|
|
return path.get_file()
|
|
|
|
|
|
|
|
func has_inner_class():
|
2023-01-20 23:34:39 +01:00
|
|
|
return inner_class_name != ""
|
2022-06-02 04:13:43 +02:00
|
|
|
|
|
|
|
# Note: although this no longer needs to export the inner_class names since
|
|
|
|
# they are pulled from metadata now, it is easier to leave that in
|
|
|
|
# so we don't have to cut the export down to unique script names.
|
|
|
|
func export_to(config_file, section):
|
2023-01-20 23:34:39 +01:00
|
|
|
config_file.set_value(section, "path", path)
|
|
|
|
config_file.set_value(section, "inner_class", inner_class_name)
|
2022-06-02 04:13:43 +02:00
|
|
|
var names = []
|
|
|
|
for i in range(tests.size()):
|
|
|
|
names.append(tests[i].name)
|
2023-01-20 23:34:39 +01:00
|
|
|
config_file.set_value(section, "tests", names)
|
2022-06-02 04:13:43 +02:00
|
|
|
|
|
|
|
func _remap_path(source_path):
|
|
|
|
var to_return = source_path
|
2023-01-20 23:34:39 +01:00
|
|
|
if !_utils.file_exists(source_path):
|
|
|
|
_lgr.debug("Checking for remap for: " + source_path)
|
|
|
|
var remap_path = source_path.get_basename() + ".gd.remap"
|
|
|
|
if _utils.file_exists(remap_path):
|
2022-06-02 04:13:43 +02:00
|
|
|
var cf = ConfigFile.new()
|
|
|
|
cf.load(remap_path)
|
2023-01-20 23:34:39 +01:00
|
|
|
to_return = cf.get_value("remap", "path")
|
2022-06-02 04:13:43 +02:00
|
|
|
else:
|
2023-01-20 23:34:39 +01:00
|
|
|
_lgr.warn("Could not find remap file " + remap_path)
|
2022-06-02 04:13:43 +02:00
|
|
|
return to_return
|
|
|
|
|
|
|
|
func import_from(config_file, section):
|
2023-01-20 23:34:39 +01:00
|
|
|
path = config_file.get_value(section, "path")
|
2022-06-02 04:13:43 +02:00
|
|
|
path = _remap_path(path)
|
|
|
|
# Null is an acceptable value, but you can't pass null as a default to
|
|
|
|
# get_value since it thinks you didn't send a default...then it spits
|
|
|
|
# out red text. This works around that.
|
2023-01-20 23:34:39 +01:00
|
|
|
var inner_name = config_file.get_value(section, "inner_class", "Placeholder")
|
|
|
|
if inner_name != "Placeholder":
|
2022-06-02 04:13:43 +02:00
|
|
|
inner_class_name = inner_name
|
2023-01-20 23:34:39 +01:00
|
|
|
else: # just being explicit
|
2023-01-07 20:26:17 +01:00
|
|
|
inner_class_name = StringName("")
|
2022-06-02 04:13:43 +02:00
|
|
|
|
|
|
|
func get_test_named(name):
|
2023-01-20 23:34:39 +01:00
|
|
|
return _utils.search_array(tests, "name", name)
|
2023-01-07 20:26:17 +01:00
|
|
|
|
|
|
|
func mark_tests_to_skip_with_suffix(suffix):
|
|
|
|
for single_test in tests:
|
|
|
|
single_test.should_skip = single_test.name.ends_with(suffix)
|
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# start test_collector, I don't think I like the name.
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
var scripts = []
|
2023-01-20 23:34:39 +01:00
|
|
|
var _test_prefix = "test_"
|
|
|
|
var _test_class_prefix = "Test"
|
2022-06-02 04:13:43 +02:00
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
var _utils = load("res://addons/gut/utils.gd").get_instance()
|
2022-06-02 04:13:43 +02:00
|
|
|
var _lgr = _utils.get_logger()
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func _does_inherit_from_test(thing):
|
|
|
|
var base_script = thing.get_base_script()
|
|
|
|
var to_return = false
|
2023-01-20 23:34:39 +01:00
|
|
|
if base_script != null:
|
2022-06-02 04:13:43 +02:00
|
|
|
var base_path = base_script.get_path()
|
2023-01-20 23:34:39 +01:00
|
|
|
if base_path == "res://addons/gut/test.gd":
|
2022-06-02 04:13:43 +02:00
|
|
|
to_return = true
|
|
|
|
else:
|
|
|
|
to_return = _does_inherit_from_test(base_script)
|
|
|
|
return to_return
|
|
|
|
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
func _populate_tests(test_script: TestScript):
|
|
|
|
var script = test_script.load_script()
|
|
|
|
if script == null:
|
|
|
|
print(" !!! ", test_script.path, " could not be loaded")
|
2023-01-07 20:26:17 +01:00
|
|
|
return false
|
|
|
|
|
|
|
|
test_script.is_loaded = true
|
|
|
|
var methods = script.get_script_method_list()
|
2022-06-02 04:13:43 +02:00
|
|
|
for i in range(methods.size()):
|
2023-01-20 23:34:39 +01:00
|
|
|
var name = methods[i]["name"]
|
|
|
|
if name.begins_with(_test_prefix):
|
2022-06-02 04:13:43 +02:00
|
|
|
var t = Test.new()
|
|
|
|
t.name = name
|
2023-01-20 23:34:39 +01:00
|
|
|
t.arg_count = methods[i]["args"].size()
|
2022-06-02 04:13:43 +02:00
|
|
|
test_script.tests.append(t)
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func _get_inner_test_class_names(loaded):
|
|
|
|
var inner_classes = []
|
|
|
|
var const_map = loaded.get_script_constant_map()
|
|
|
|
for key in const_map:
|
|
|
|
var thing = const_map[key]
|
2023-01-20 23:34:39 +01:00
|
|
|
if _utils.is_gdscript(thing):
|
|
|
|
if key.begins_with(_test_class_prefix):
|
|
|
|
if _does_inherit_from_test(thing):
|
2022-06-02 04:13:43 +02:00
|
|
|
inner_classes.append(key)
|
|
|
|
else:
|
2023-01-20 23:34:39 +01:00
|
|
|
_lgr.warn(
|
|
|
|
str(
|
|
|
|
"Ignoring Inner Class ",
|
|
|
|
key,
|
|
|
|
" because it does not extend res://addons/gut/test.gd"
|
|
|
|
)
|
|
|
|
)
|
2022-06-02 04:13:43 +02:00
|
|
|
|
|
|
|
# This could go deeper and find inner classes within inner classes
|
|
|
|
# but requires more experimentation. Right now I'm keeping it at
|
|
|
|
# one level since that is what the previous version did and there
|
|
|
|
# has been no demand for deeper nesting.
|
|
|
|
# _populate_inner_test_classes(thing)
|
|
|
|
return inner_classes
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func _parse_script(test_script):
|
|
|
|
var inner_classes = []
|
|
|
|
var scripts_found = []
|
|
|
|
|
|
|
|
var loaded = load(test_script.path)
|
2023-01-20 23:34:39 +01:00
|
|
|
if _does_inherit_from_test(loaded):
|
2022-06-02 04:13:43 +02:00
|
|
|
_populate_tests(test_script)
|
|
|
|
scripts_found.append(test_script.path)
|
|
|
|
inner_classes = _get_inner_test_class_names(loaded)
|
|
|
|
|
|
|
|
for i in range(inner_classes.size()):
|
|
|
|
var loaded_inner = loaded.get(inner_classes[i])
|
2023-01-20 23:34:39 +01:00
|
|
|
if _does_inherit_from_test(loaded_inner):
|
2022-06-02 04:13:43 +02:00
|
|
|
var ts = TestScript.new(_utils, _lgr)
|
|
|
|
ts.path = test_script.path
|
|
|
|
ts.inner_class_name = inner_classes[i]
|
|
|
|
_populate_tests(ts)
|
|
|
|
scripts.append(ts)
|
2023-01-20 23:34:39 +01:00
|
|
|
scripts_found.append(test_script.path + "[" + inner_classes[i] + "]")
|
2022-06-02 04:13:43 +02:00
|
|
|
|
|
|
|
return scripts_found
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
# -----------------
|
|
|
|
# Public
|
|
|
|
# -----------------
|
|
|
|
func add_script(path):
|
2023-01-07 20:26:17 +01:00
|
|
|
# print('Adding ', path)
|
2022-06-02 04:13:43 +02:00
|
|
|
# SHORTCIRCUIT
|
2023-01-20 23:34:39 +01:00
|
|
|
if has_script(path):
|
2022-06-02 04:13:43 +02:00
|
|
|
return []
|
|
|
|
|
|
|
|
# SHORTCIRCUIT
|
2023-01-20 23:34:39 +01:00
|
|
|
if !FileAccess.file_exists(path):
|
|
|
|
_lgr.error("Could not find script: " + path)
|
2022-06-02 04:13:43 +02:00
|
|
|
return
|
|
|
|
|
|
|
|
var ts = TestScript.new(_utils, _lgr)
|
|
|
|
ts.path = path
|
|
|
|
scripts.append(ts)
|
|
|
|
return _parse_script(ts)
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func clear():
|
|
|
|
scripts.clear()
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func has_script(path):
|
|
|
|
var found = false
|
|
|
|
var idx = 0
|
2023-01-20 23:34:39 +01:00
|
|
|
while idx < scripts.size() and !found:
|
|
|
|
if scripts[idx].get_full_name() == path:
|
2022-06-02 04:13:43 +02:00
|
|
|
found = true
|
|
|
|
else:
|
|
|
|
idx += 1
|
|
|
|
return found
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func export_tests(path):
|
|
|
|
var success = true
|
|
|
|
var f = ConfigFile.new()
|
|
|
|
for i in range(scripts.size()):
|
2023-01-20 23:34:39 +01:00
|
|
|
scripts[i].export_to(f, str("TestScript-", i))
|
2022-06-02 04:13:43 +02:00
|
|
|
var result = f.save(path)
|
2023-01-20 23:34:39 +01:00
|
|
|
if result != OK:
|
|
|
|
_lgr.error(str("Could not save exported tests to [", path, "]. Error code: ", result))
|
2022-06-02 04:13:43 +02:00
|
|
|
success = false
|
|
|
|
return success
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func import_tests(path):
|
|
|
|
var success = false
|
|
|
|
var f = ConfigFile.new()
|
|
|
|
var result = f.load(path)
|
2023-01-20 23:34:39 +01:00
|
|
|
if result != OK:
|
|
|
|
_lgr.error(str("Could not load exported tests from [", path, "]. Error code: ", result))
|
2022-06-02 04:13:43 +02:00
|
|
|
else:
|
|
|
|
var sections = f.get_sections()
|
|
|
|
for key in sections:
|
|
|
|
var ts = TestScript.new(_utils, _lgr)
|
|
|
|
ts.import_from(f, key)
|
|
|
|
_populate_tests(ts)
|
|
|
|
scripts.append(ts)
|
|
|
|
success = true
|
|
|
|
return success
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func get_script_named(name):
|
2023-01-20 23:34:39 +01:00
|
|
|
return _utils.search_array(scripts, "get_filename_and_inner", name)
|
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
|
|
|
|
func get_test_named(script_name, test_name):
|
|
|
|
var s = get_script_named(script_name)
|
2023-01-20 23:34:39 +01:00
|
|
|
if s != null:
|
2022-06-02 04:13:43 +02:00
|
|
|
return s.get_test_named(test_name)
|
|
|
|
else:
|
|
|
|
return null
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func to_s():
|
2023-01-20 23:34:39 +01:00
|
|
|
var to_return = ""
|
2022-06-02 04:13:43 +02:00
|
|
|
for i in range(scripts.size()):
|
|
|
|
to_return += scripts[i].to_s() + "\n"
|
|
|
|
return to_return
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
# ---------------------
|
|
|
|
# Accessors
|
|
|
|
# ---------------------
|
|
|
|
func get_logger():
|
|
|
|
return _lgr
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func set_logger(logger):
|
|
|
|
_lgr = logger
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func get_test_prefix():
|
|
|
|
return _test_prefix
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func set_test_prefix(test_prefix):
|
|
|
|
_test_prefix = test_prefix
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func get_test_class_prefix():
|
|
|
|
return _test_class_prefix
|
|
|
|
|
2023-01-20 23:34:39 +01:00
|
|
|
|
2022-06-02 04:13:43 +02:00
|
|
|
func set_test_class_prefix(test_class_prefix):
|
|
|
|
_test_class_prefix = test_class_prefix
|