godot-xterm/addons/gut/test_collector.gd
Leroy Hopson b5d3c6c9a5
Update Gut for Godot 4
Copied from Gut repo godot_4 branch commit:
ba19a4c1b6f88160641a67a39729144046c6391f
2023-01-08 21:36:37 +13:00

314 lines
8.9 KiB
GDScript

# ------------------------------------------------------------------------------
# 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
# 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
# ------------------------------------------------------------------------------
# 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:
var inner_class_name:StringName
var tests = []
var path:String
var _utils = null
var _lgr = null
var is_loaded = false
func _init(utils=null,logger=null):
_utils = utils
_lgr = logger
func to_s():
var to_return = path
if(inner_class_name != null):
to_return += str('.', inner_class_name)
to_return += "\n"
for i in range(tests.size()):
to_return += str(' ', tests[i].name, "\n")
return to_return
func get_new():
return load_script().new()
func load_script():
var to_return = load(path)
if(inner_class_name != null and inner_class_name != ''):
# 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)
return to_return
func get_filename_and_inner():
var to_return = get_filename()
if(inner_class_name != ''):
to_return += '.' + String(inner_class_name)
return to_return
func get_full_name():
var to_return = path
if(inner_class_name != ''):
to_return += '.' + String(inner_class_name)
return to_return
func get_filename():
return path.get_file()
func has_inner_class():
return inner_class_name != ''
# 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):
config_file.set_value(section, 'path', path)
config_file.set_value(section, 'inner_class', inner_class_name)
var names = []
for i in range(tests.size()):
names.append(tests[i].name)
config_file.set_value(section, 'tests', names)
func _remap_path(source_path):
var to_return = source_path
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)):
var cf = ConfigFile.new()
cf.load(remap_path)
to_return = cf.get_value('remap', 'path')
else:
_lgr.warn('Could not find remap file ' + remap_path)
return to_return
func import_from(config_file, section):
path = config_file.get_value(section, 'path')
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.
var inner_name = config_file.get_value(section, 'inner_class', 'Placeholder')
if(inner_name != 'Placeholder'):
inner_class_name = inner_name
else: # just being explicit
inner_class_name = StringName("")
func get_test_named(name):
return _utils.search_array(tests, 'name', name)
func mark_tests_to_skip_with_suffix(suffix):
for single_test in tests:
single_test.should_skip = single_test.name.ends_with(suffix)
# ------------------------------------------------------------------------------
# start test_collector, I don't think I like the name.
# ------------------------------------------------------------------------------
var scripts = []
var _test_prefix = 'test_'
var _test_class_prefix = 'Test'
var _utils = load('res://addons/gut/utils.gd').get_instance()
var _lgr = _utils.get_logger()
func _does_inherit_from_test(thing):
var base_script = thing.get_base_script()
var to_return = false
if(base_script != null):
var base_path = base_script.get_path()
if(base_path == 'res://addons/gut/test.gd'):
to_return = true
else:
to_return = _does_inherit_from_test(base_script)
return to_return
func _populate_tests(test_script:TestScript):
var script = test_script.load_script()
if(script == null):
print(' !!! ', test_script.path, ' could not be loaded')
return false
test_script.is_loaded = true
var methods = script.get_script_method_list()
for i in range(methods.size()):
var name = methods[i]['name']
if(name.begins_with(_test_prefix)):
var t = Test.new()
t.name = name
t.arg_count = methods[i]['args'].size()
test_script.tests.append(t)
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]
if(_utils.is_gdscript(thing)):
if(key.begins_with(_test_class_prefix)):
if(_does_inherit_from_test(thing)):
inner_classes.append(key)
else:
_lgr.warn(str('Ignoring Inner Class ', key,
' because it does not extend res://addons/gut/test.gd'))
# 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
func _parse_script(test_script):
var inner_classes = []
var scripts_found = []
var loaded = load(test_script.path)
if(_does_inherit_from_test(loaded)):
_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])
if(_does_inherit_from_test(loaded_inner)):
var ts = TestScript.new(_utils, _lgr)
ts.path = test_script.path
ts.inner_class_name = inner_classes[i]
_populate_tests(ts)
scripts.append(ts)
scripts_found.append(test_script.path + '[' + inner_classes[i] +']')
return scripts_found
# -----------------
# Public
# -----------------
func add_script(path):
# print('Adding ', path)
# SHORTCIRCUIT
if(has_script(path)):
return []
# SHORTCIRCUIT
if(!FileAccess.file_exists(path)):
_lgr.error('Could not find script: ' + path)
return
var ts = TestScript.new(_utils, _lgr)
ts.path = path
scripts.append(ts)
return _parse_script(ts)
func clear():
scripts.clear()
func has_script(path):
var found = false
var idx = 0
while(idx < scripts.size() and !found):
if(scripts[idx].get_full_name() == path):
found = true
else:
idx += 1
return found
func export_tests(path):
var success = true
var f = ConfigFile.new()
for i in range(scripts.size()):
scripts[i].export_to(f, str('TestScript-', i))
var result = f.save(path)
if(result != OK):
_lgr.error(str('Could not save exported tests to [', path, ']. Error code: ', result))
success = false
return success
func import_tests(path):
var success = false
var f = ConfigFile.new()
var result = f.load(path)
if(result != OK):
_lgr.error(str('Could not load exported tests from [', path, ']. Error code: ', result))
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
func get_script_named(name):
return _utils.search_array(scripts, 'get_filename_and_inner', name)
func get_test_named(script_name, test_name):
var s = get_script_named(script_name)
if(s != null):
return s.get_test_named(test_name)
else:
return null
func to_s():
var to_return = ''
for i in range(scripts.size()):
to_return += scripts[i].to_s() + "\n"
return to_return
# ---------------------
# Accessors
# ---------------------
func get_logger():
return _lgr
func set_logger(logger):
_lgr = logger
func get_test_prefix():
return _test_prefix
func set_test_prefix(test_prefix):
_test_prefix = test_prefix
func get_test_class_prefix():
return _test_class_prefix
func set_test_class_prefix(test_class_prefix):
_test_class_prefix = test_class_prefix