setting up GDUnit
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 3m40s
Some checks failed
Create tag and build when new code gets to main / Export (push) Failing after 3m40s
This commit is contained in:
188
addons/gdUnit4/src/core/parse/GdFunctionParameterSetResolver.gd
Normal file
188
addons/gdUnit4/src/core/parse/GdFunctionParameterSetResolver.gd
Normal file
@@ -0,0 +1,188 @@
|
||||
class_name GdFunctionParameterSetResolver
|
||||
extends RefCounted
|
||||
|
||||
const CLASS_TEMPLATE = """
|
||||
class_name _ParameterExtractor extends '${clazz_path}'
|
||||
|
||||
func __extract_test_parameters() -> Array:
|
||||
return ${test_params}
|
||||
|
||||
"""
|
||||
|
||||
const EXCLUDE_PROPERTIES_TO_COPY = [
|
||||
"script",
|
||||
"type",
|
||||
"Node",
|
||||
"_import_path"]
|
||||
|
||||
|
||||
var _fd: GdFunctionDescriptor
|
||||
var _static_sets_by_index := {}
|
||||
var _is_static := true
|
||||
|
||||
func _init(fd: GdFunctionDescriptor) -> void:
|
||||
_fd = fd
|
||||
|
||||
|
||||
func resolve_test_cases(script: GDScript) -> Array[GdUnitTestCase]:
|
||||
if not is_parameterized():
|
||||
return [GdUnitTestCase.from(script.resource_path, _fd.source_path(), _fd.line_number(), _fd.name())]
|
||||
return extract_test_cases_by_reflection(script)
|
||||
|
||||
|
||||
func is_parameterized() -> bool:
|
||||
return _fd.is_parameterized()
|
||||
|
||||
|
||||
func is_parameter_sets_static() -> bool:
|
||||
return _is_static
|
||||
|
||||
|
||||
func is_parameter_set_static(index: int) -> bool:
|
||||
return _is_static and _static_sets_by_index.get(index, false)
|
||||
|
||||
|
||||
# validates the given arguments are complete and matches to required input fields of the test function
|
||||
func validate(input_value_set: Array) -> String:
|
||||
var input_arguments := _fd.args()
|
||||
# check given parameter set with test case arguments
|
||||
var expected_arg_count := input_arguments.size() - 1
|
||||
for input_values :Variant in input_value_set:
|
||||
var parameter_set_index := input_value_set.find(input_values)
|
||||
if input_values is Array:
|
||||
var arr_values: Array = input_values
|
||||
var current_arg_count := arr_values.size()
|
||||
if current_arg_count != expected_arg_count:
|
||||
return "\n The parameter set at index [%d] does not match the expected input parameters!\n The test case requires [%d] input parameters, but the set contains [%d]" % [parameter_set_index, expected_arg_count, current_arg_count]
|
||||
var error := validate_parameter_types(input_arguments, arr_values, parameter_set_index)
|
||||
if not error.is_empty():
|
||||
return error
|
||||
else:
|
||||
return "\n The parameter set at index [%d] does not match the expected input parameters!\n Expecting an array of input values." % parameter_set_index
|
||||
return ""
|
||||
|
||||
|
||||
static func validate_parameter_types(input_arguments: Array, input_values: Array, parameter_set_index: int) -> String:
|
||||
for i in input_arguments.size():
|
||||
var input_param: GdFunctionArgument = input_arguments[i]
|
||||
# only check the test input arguments
|
||||
if input_param.is_parameter_set():
|
||||
continue
|
||||
var input_param_type := input_param.type()
|
||||
var input_value :Variant = input_values[i]
|
||||
var input_value_type := typeof(input_value)
|
||||
# input parameter is not typed or is Variant we skip the type test
|
||||
if input_param_type == TYPE_NIL or input_param_type == GdObjects.TYPE_VARIANT:
|
||||
continue
|
||||
# is input type enum allow int values
|
||||
if input_param_type == GdObjects.TYPE_VARIANT and input_value_type == TYPE_INT:
|
||||
continue
|
||||
# allow only equal types and object == null
|
||||
if input_param_type == TYPE_OBJECT and input_value_type == TYPE_NIL:
|
||||
continue
|
||||
if input_param_type != input_value_type:
|
||||
return "\n The parameter set at index [%d] does not match the expected input parameters!\n The value '%s' does not match the required input parameter <%s>." % [parameter_set_index, input_value, input_param]
|
||||
return ""
|
||||
|
||||
|
||||
func extract_test_cases_by_reflection(script: GDScript) -> Array[GdUnitTestCase]:
|
||||
var source: Node = script.new()
|
||||
source.queue_free()
|
||||
|
||||
var fa := GdFunctionArgument.get_parameter_set(_fd.args())
|
||||
var parameter_sets := fa.parameter_sets()
|
||||
# if no parameter set detected we need to resolve it by using reflection
|
||||
if parameter_sets.size() == 0:
|
||||
_is_static = false
|
||||
return _extract_test_cases_by_reflection(source, script)
|
||||
else:
|
||||
var test_cases: Array[GdUnitTestCase] = []
|
||||
var property_names := _extract_property_names(source)
|
||||
for parameter_set_index in parameter_sets.size():
|
||||
var parameter_set := parameter_sets[parameter_set_index]
|
||||
_static_sets_by_index[parameter_set_index] = _is_static_parameter_set(parameter_set, property_names)
|
||||
@warning_ignore("return_value_discarded")
|
||||
test_cases.append(GdUnitTestCase.from(script.resource_path, _fd.source_path(), _fd.line_number(), _fd.name(), parameter_set_index, parameter_set))
|
||||
parameter_set_index += 1
|
||||
return test_cases
|
||||
|
||||
|
||||
func _extract_property_names(source: Node) -> PackedStringArray:
|
||||
return source.get_property_list()\
|
||||
.map(func(property :Dictionary) -> String: return property["name"])\
|
||||
.filter(func(property :String) -> bool: return !EXCLUDE_PROPERTIES_TO_COPY.has(property))
|
||||
|
||||
|
||||
# tests if the test property set contains an property reference by name, if not the parameter set holds only static values
|
||||
func _is_static_parameter_set(parameters :String, property_names :PackedStringArray) -> bool:
|
||||
for property_name in property_names:
|
||||
if parameters.contains(property_name):
|
||||
_is_static = false
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func _extract_test_cases_by_reflection(source: Node, script: GDScript) -> Array[GdUnitTestCase]:
|
||||
var parameter_sets := load_parameter_sets(source)
|
||||
var test_cases: Array[GdUnitTestCase] = []
|
||||
for index in parameter_sets.size():
|
||||
var parameter_set := str(parameter_sets[index])
|
||||
@warning_ignore("return_value_discarded")
|
||||
test_cases.append(GdUnitTestCase.from(script.resource_path, _fd.source_path(), _fd.line_number(), _fd.name(), index, parameter_set))
|
||||
return test_cases
|
||||
|
||||
|
||||
# extracts the arguments from the given test case, using kind of reflection solution
|
||||
# to restore the parameters from a string representation to real instance type
|
||||
func load_parameter_sets(source: Node) -> Array:
|
||||
var source_script: GDScript = source.get_script()
|
||||
var parameter_arg := GdFunctionArgument.get_parameter_set(_fd.args())
|
||||
var source_code := CLASS_TEMPLATE \
|
||||
.replace("${clazz_path}", source_script.resource_path) \
|
||||
.replace("${test_params}", parameter_arg.value_as_string())
|
||||
var script := GDScript.new()
|
||||
script.source_code = source_code
|
||||
# enable this lines only for debuging
|
||||
#script.resource_path = GdUnitFileAccess.create_temp_dir("parameter_extract") + "/%s__.gd" % test_case.get_name()
|
||||
#DirAccess.remove_absolute(script.resource_path)
|
||||
#ResourceSaver.save(script, script.resource_path)
|
||||
var result := script.reload()
|
||||
if result != OK:
|
||||
push_error("Extracting test parameters failed! Script loading error: %s" % result)
|
||||
return []
|
||||
var instance: Node = script.new()
|
||||
GdFunctionParameterSetResolver.copy_properties(source, instance)
|
||||
instance.queue_free()
|
||||
var parameter_sets: Array = instance.call("__extract_test_parameters")
|
||||
return fixure_typed_parameters(parameter_sets, _fd.args())
|
||||
|
||||
|
||||
func fixure_typed_parameters(parameter_sets: Array, arg_descriptors: Array[GdFunctionArgument]) -> Array:
|
||||
for parameter_set_index in parameter_sets.size():
|
||||
var parameter_set: Array = parameter_sets[parameter_set_index]
|
||||
# run over all function arguments
|
||||
for parameter_index in parameter_set.size():
|
||||
var parameter :Variant = parameter_set[parameter_index]
|
||||
var arg_descriptor: GdFunctionArgument = arg_descriptors[parameter_index]
|
||||
if parameter is Array:
|
||||
var as_array: Array = parameter
|
||||
# we need to convert the untyped array to the expected typed version
|
||||
if arg_descriptor.is_typed_array():
|
||||
parameter_set[parameter_index] = Array(as_array, arg_descriptor.type_hint(), "", null)
|
||||
return parameter_sets
|
||||
|
||||
|
||||
static func copy_properties(source: Object, dest: Object) -> void:
|
||||
for property in source.get_property_list():
|
||||
var property_name :String = property["name"]
|
||||
var property_value :Variant = source.get(property_name)
|
||||
if EXCLUDE_PROPERTIES_TO_COPY.has(property_name):
|
||||
continue
|
||||
#if dest.get(property_name) == null:
|
||||
# prints("|%s|" % property_name, source.get(property_name))
|
||||
|
||||
# check for invalid name property
|
||||
if property_name == "name" and property_value == "":
|
||||
dest.set(property_name, "<empty>");
|
||||
continue
|
||||
dest.set(property_name, property_value)
|
||||
Reference in New Issue
Block a user