Skip to content

Commit

Permalink
GD-544: Add Spy/Mock support for Callable
Browse files Browse the repository at this point in the history
# Why
see #544

# What
- cleanup parser
  - removed `extract_source_code`, it was only used in tests
- add support to spy on `Callable`
  • Loading branch information
MikeSchulze committed Jul 12, 2024
1 parent d7b8e42 commit 1116c6a
Show file tree
Hide file tree
Showing 16 changed files with 578 additions and 84 deletions.
9 changes: 4 additions & 5 deletions addons/gdUnit4/src/core/GdFunctionDoubler.gd
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,13 @@ func _init(push_errors :bool = false) -> void:


@warning_ignore("unused_parameter")
func get_template(return_type :Variant, is_vararg :bool) -> String:
push_error("Must be implemented!")
func get_template(return_type: GdFunctionDescriptor, is_callable: bool) -> String:
assert(false, "'get_template' must be implemented!")
return ""

func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray:
func double(func_descriptor: GdFunctionDescriptor, is_callable: bool = false) -> PackedStringArray:
var func_signature := func_descriptor.typeless()
var is_static := func_descriptor.is_static()
var is_vararg := func_descriptor.is_vararg()
var is_coroutine := func_descriptor.is_coroutine()
var func_name := func_descriptor.name()
var args := func_descriptor.args()
Expand All @@ -145,7 +144,7 @@ func double(func_descriptor :GdFunctionDescriptor) -> PackedStringArray:
double_src += '@warning_ignore("shadowed_variable")\n'
double_src += func_signature
# fix to unix format, this is need when the template is edited under windows than the template is stored with \r\n
var func_template := get_template(func_descriptor.return_type(), is_vararg).replace("\r\n", "\n")
var func_template := get_template(func_descriptor, is_callable).replace("\r\n", "\n")
double_src += func_template\
.replace("$(arguments)", ", ".join(arg_names))\
.replace("$(varargs)", ", ".join(vararg_names))\
Expand Down
1 change: 1 addition & 0 deletions addons/gdUnit4/src/core/GdObjects.gd
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ static func default_value_by_type(type :int) -> Variant:
TYPE_NODE_PATH: return NodePath()
TYPE_RID: return RID()
TYPE_OBJECT: return null
TYPE_CALLABLE: return Callable()
TYPE_ARRAY: return []
TYPE_DICTIONARY: return {}
TYPE_PACKED_BYTE_ARRAY: return PackedByteArray()
Expand Down
4 changes: 2 additions & 2 deletions addons/gdUnit4/src/core/GdUnitClassDoubler.gd
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ static func double_functions(instance :Object, clazz_name :String, clazz_path :P
continue
if functions.has(func_descriptor.name()) or exclude_override_functions.has(func_descriptor.name()):
continue
doubled_source += func_doubler.double(func_descriptor)
doubled_source += func_doubler.double(func_descriptor, instance is CallableDoubler)
functions.append(func_descriptor.name())
class_descriptor = class_descriptor.parent()

Expand All @@ -103,7 +103,7 @@ static func double_functions(instance :Object, clazz_name :String, clazz_path :P
#prints("no virtual func implemented",clazz_name, func_descriptor.name() )
continue
functions.append(func_descriptor.name())
doubled_source.append_array(func_doubler.double(func_descriptor))
doubled_source.append_array(func_doubler.double(func_descriptor, instance is CallableDoubler))
return doubled_source


Expand Down
61 changes: 20 additions & 41 deletions addons/gdUnit4/src/core/parse/GdScriptParser.gd
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var TOKEN_CLASS_NAME := Token.new("class_name")
var TOKEN_INNER_CLASS := Token.new("class")
var TOKEN_EXTENDS := Token.new("extends")
var TOKEN_ENUM := Token.new("enum")
var TOKEN_FUNCTION_STATIC_DECLARATION := Token.new("staticfunc")
var TOKEN_FUNCTION_STATIC_DECLARATION := Token.new("static func")
var TOKEN_FUNCTION_DECLARATION := Token.new("func")
var TOKEN_FUNCTION := Token.new(".")
var TOKEN_FUNCTION_RETURN_TYPE := Token.new("->")
Expand Down Expand Up @@ -64,9 +64,8 @@ var TOKENS :Array[Token] = [
OPERATOR_REMAINDER,
]

var _regex_clazz_name :RegEx
var _regex_clazz_name := GdUnitTools.to_regex("(class) ([a-zA-Z0-9_]+) (extends[a-zA-Z]+:)|(class) ([a-zA-Z0-9_]+)")
var _regex_strip_comments := GdUnitTools.to_regex("^([^#\"']|'[^']*'|\"[^\"]*\")*\\K#.*")
var _base_clazz :String
var _scanned_inner_classes := PackedStringArray()
var _script_constants := {}

Expand Down Expand Up @@ -287,9 +286,6 @@ class TokenInnerClass extends Token:
return "TokenInnerClass{%s}" % [_clazz_name]


func _init() -> void:
_regex_clazz_name = GdUnitTools.to_regex("(class)([a-zA-Z0-9]+)(extends[a-zA-Z]+:)|(class)([a-zA-Z0-9]+)(:)")


func get_token(input :String, current_index :int) -> Token:
for t in TOKENS:
Expand Down Expand Up @@ -565,7 +561,7 @@ func _parse_end_function(input: String, remove_trailing_char := false) -> String

func extract_inner_class(source_rows: PackedStringArray, clazz_name :String) -> PackedStringArray:
for row_index in source_rows.size():
var input := GdScriptParser.clean_up_row(source_rows[row_index])
var input := source_rows[row_index]
var token := next_token(input, 0)
if token.is_inner_class():
if token.is_class_name(clazz_name):
Expand All @@ -574,21 +570,6 @@ func extract_inner_class(source_rows: PackedStringArray, clazz_name :String) ->
return PackedStringArray()


func extract_source_code(script_path :PackedStringArray) -> PackedStringArray:
if script_path.is_empty():
push_error("Invalid script path '%s'" % script_path)
return PackedStringArray()
#load the source code
var resource_path := script_path[0]
var script :GDScript = load(resource_path)
var source_code := load_source_code(script, script_path)
var base_script := script.get_base_script()
if base_script:
_base_clazz = GdObjects.extract_class_name_from_class_path([base_script.resource_path])
source_code += load_source_code(base_script, script_path)
return source_code


func extract_func_signature(rows :PackedStringArray, index :int) -> String:
var signature := ""

Expand All @@ -605,13 +586,15 @@ func extract_func_signature(rows :PackedStringArray, index :int) -> String:


func load_source_code(script :GDScript, script_path :PackedStringArray) -> PackedStringArray:
var map := script.get_script_constant_map()
for key :String in map.keys():
var value :Variant = map.get(key)
_script_constants = script.get_script_constant_map()
for key :String in _script_constants.keys():
var value :Variant = _script_constants.get(key)
if value is GDScript:
var class_path := GdObjects.extract_class_path(value)
if class_path.size() > 1:
_scanned_inner_classes.append(class_path[1])
# do not add at twice
if not _scanned_inner_classes.has(class_path[1]):
_scanned_inner_classes.append(class_path[1])

var source_code := GdScriptParser.to_unix_format(script.source_code)
var source_rows := source_code.split("\n")
Expand Down Expand Up @@ -640,8 +623,7 @@ func get_class_name(script :GDScript) -> String:
return GdObjects.to_pascal_case(script.resource_path.get_basename().get_file())


func parse_func_name(row :String) -> String:
var input := GdScriptParser.clean_up_row(row)
func parse_func_name(input :String) -> String:
var current_index := 0
var token := next_token(input, current_index)
current_index += token._consumed
Expand All @@ -656,11 +638,10 @@ func parse_func_name(row :String) -> String:
func parse_functions(rows :PackedStringArray, clazz_name :String, clazz_path :PackedStringArray, included_functions := PackedStringArray()) -> Array[GdFunctionDescriptor]:
var func_descriptors :Array[GdFunctionDescriptor] = []
for rowIndex in rows.size():
var row := rows[rowIndex]
var input := rows[rowIndex]
# step over inner class functions
if row.begins_with("\t"):
if input.begins_with("\t"):
continue
var input := GdScriptParser.clean_up_row(row)
# skip comments and empty lines
if input.begins_with("#") or input.length() == 0:
continue
Expand All @@ -677,11 +658,10 @@ func parse_functions(rows :PackedStringArray, clazz_name :String, clazz_path :Pa
func is_func_coroutine(rows :PackedStringArray, index :int) -> bool:
var is_coroutine := false
for rowIndex in range( index+1, rows.size()):
var row := rows[rowIndex]
is_coroutine = row.contains("await")
var input := rows[rowIndex]
is_coroutine = input.contains("await")
if is_coroutine:
return true
var input := GdScriptParser.clean_up_row(row)
var token := next_token(input, 0)
if token == TOKEN_FUNCTION_STATIC_DECLARATION or token == TOKEN_FUNCTION_DECLARATION:
break
Expand Down Expand Up @@ -742,8 +722,7 @@ func is_virtual_func(clazz_name :String, clazz_path :PackedStringArray, func_nam


func is_static_func(func_signature :String) -> bool:
var input := GdScriptParser.clean_up_row(func_signature)
var token := next_token(input, 0)
var token := next_token(func_signature, 0)
return token == TOKEN_FUNCTION_STATIC_DECLARATION


Expand Down Expand Up @@ -786,7 +765,6 @@ func _patch_inner_class_names(clazz :String, clazz_name :String) -> String:

func extract_functions(script :GDScript, clazz_name :String, clazz_path :PackedStringArray) -> Array[GdFunctionDescriptor]:
var source_code := load_source_code(script, clazz_path)
_script_constants = script.get_script_constant_map()
return parse_functions(source_code, clazz_name, clazz_path)


Expand All @@ -799,9 +777,10 @@ func parse(clazz_name :String, clazz_path :PackedStringArray) -> GdUnitResult:
var gd_class := GdClassDescriptor.new(clazz_name, is_inner_class_, function_descriptors)
# iterate over class dependencies
script = script.get_base_script()
while script != null:
clazz_name = GdObjects.extract_class_name_from_class_path([script.resource_path])
function_descriptors = extract_functions(script, clazz_name, clazz_path)
gd_class.set_parent_clazz(GdClassDescriptor.new(clazz_name, is_inner_class_, function_descriptors))
while script != null and not is_inner_class_:
var sub_class_path := [script.resource_path]
clazz_name = GdObjects.extract_class_name_from_class_path(sub_class_path)
function_descriptors = extract_functions(script, clazz_name, sub_class_path)
gd_class.set_parent_clazz(GdClassDescriptor.new(clazz_name, false, function_descriptors))
script = script.get_base_script()
return GdUnitResult.success(gd_class)
Loading

0 comments on commit 1116c6a

Please sign in to comment.