Replace the broken test suite with working GdUnit4 tests
The previous tests never compiled: they used GUT's assert functions, Godot 3 APIs and reassigned autoload functions, so no test ran. - 90 tests: unit tests for the playlist, songs, player scoring, time formatting, search and the server client, plus integration tests that run MainWindow and SyncWindow against a fake server - Keybind tests check that Alt+<number> and Ctrl+Alt+<number> keep following the player rows while players are added and removed - run_tests.sh finds Godot and runs the suite headless (~10 seconds) - CI runs the tests before the build job in gitea-build.yaml; the separate test.yml used a GitHub action that does not exist - GdUnit4 is committed instead of ignored, since the project enables it, and .gitea/ is no longer ignored - Exported builds leave out tests/ and addons/gdUnit4/
This commit is contained in:
1 parent
87ff3d899b
commit
91adf52cf4
559 files changed
+32474
-2100
No files matched your search
@@ -0,0 +1,41 @@
|
||||
extends RefCounted
|
||||
class_name ErrorLogEntry
|
||||
|
||||
|
||||
enum TYPE {
|
||||
SCRIPT_ERROR,
|
||||
PUSH_ERROR,
|
||||
PUSH_WARNING
|
||||
}
|
||||
|
||||
|
||||
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
|
||||
|
||||
var _type: TYPE
|
||||
var _line: int
|
||||
var _message: String
|
||||
var _stack_trace: GdUnitStackTrace
|
||||
|
||||
|
||||
func _init(type: TYPE, line: int, message: String, stack_trace: GdUnitStackTrace) -> void:
|
||||
_type = type
|
||||
_line = line
|
||||
_message = message
|
||||
_stack_trace = stack_trace
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
return _message
|
||||
|
||||
|
||||
static func of_push_warning(line: int, message: String, stack_trace: GdUnitStackTrace) -> ErrorLogEntry:
|
||||
return ErrorLogEntry.new(TYPE.PUSH_WARNING, line, message, stack_trace)
|
||||
|
||||
|
||||
static func of_push_error(line: int, message: String, stack_trace: GdUnitStackTrace) -> ErrorLogEntry:
|
||||
return ErrorLogEntry.new(TYPE.PUSH_ERROR, line, message, stack_trace)
|
||||
|
||||
|
||||
static func of_script_error(line: int, message: String, stack_trace: GdUnitStackTrace) -> ErrorLogEntry:
|
||||
return ErrorLogEntry.new(TYPE.SCRIPT_ERROR, line, message, stack_trace)
|
||||
@@ -0,0 +1 @@
|
||||
uid://uhn1hlopwbko
|
||||
@@ -0,0 +1,24 @@
|
||||
# GdUnit Monitoring Base Class
|
||||
class_name GdUnitMonitor
|
||||
extends RefCounted
|
||||
|
||||
var _id :String
|
||||
|
||||
# constructs new Monitor with given id
|
||||
func _init(p_id :String) -> void:
|
||||
_id = p_id
|
||||
|
||||
|
||||
# Returns the id of the monitor to uniqe identify
|
||||
func id() -> String:
|
||||
return _id
|
||||
|
||||
|
||||
# starts monitoring
|
||||
func start() -> void:
|
||||
pass
|
||||
|
||||
|
||||
# stops monitoring
|
||||
func stop() -> void:
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
uid://dpkbuqctenj0b
|
||||
@@ -0,0 +1,13 @@
|
||||
class_name GdUnitOrphanNodeInfo
|
||||
extends RefCounted
|
||||
|
||||
|
||||
var _id: int
|
||||
var _type: String
|
||||
var _stack_element: GdUnitStackTraceElement
|
||||
|
||||
|
||||
func _init(id: int, type: String, stack_element: GdUnitStackTraceElement) -> void:
|
||||
_id = id
|
||||
_type = type
|
||||
_stack_element = stack_element
|
||||
@@ -0,0 +1 @@
|
||||
uid://lk3vup8rxrfh
|
||||
@@ -0,0 +1,237 @@
|
||||
class_name GdUnitOrphanNodesMonitor
|
||||
extends GdUnitMonitor
|
||||
|
||||
|
||||
var _child_monitors: Array[GdUnitOrphanNodesMonitor] = []
|
||||
var _orphan_detection_enabled :bool
|
||||
var _initial_orphans: Array[int] = []
|
||||
var _orphan_ids_at_start: Array[int] = []
|
||||
var _orphan_ids_at_stop: Array[int] = []
|
||||
var _collected_orphan_infos: Array[GdUnitOrphanNodeInfo] = []
|
||||
|
||||
|
||||
func _init(name: String) -> void:
|
||||
super("OrphanNodesMonitor:" + name)
|
||||
_orphan_detection_enabled = GdUnitSettings.is_verbose_orphans()
|
||||
_initial_orphans = _get_orphan_node_ids()
|
||||
|
||||
|
||||
func add_child_monitor(monitor: GdUnitOrphanNodesMonitor) -> void:
|
||||
if not _orphan_detection_enabled:
|
||||
return
|
||||
_child_monitors.append(monitor)
|
||||
|
||||
|
||||
func start() -> void:
|
||||
if not _orphan_detection_enabled:
|
||||
return
|
||||
_collected_orphan_infos.clear()
|
||||
# Collect current orphan id's to be filtered out at `stop`
|
||||
_orphan_ids_at_start = _get_orphan_node_ids()
|
||||
|
||||
|
||||
func stop() -> void:
|
||||
if not _orphan_detection_enabled:
|
||||
return
|
||||
# Collect only new detected orphan id's, we want only to collect orphans between start and stop time
|
||||
_orphan_ids_at_stop = _get_orphan_node_ids().filter(func(element: int) -> bool:
|
||||
# Excluding sub monitores orphans
|
||||
if _collect_child_orphan_ids().has(element):
|
||||
return false
|
||||
# Excluding orphans at start
|
||||
return not _orphan_ids_at_start.has(element) and not _initial_orphans.has(element)
|
||||
)
|
||||
|
||||
|
||||
func _collect_child_orphan_ids() -> Array[int]:
|
||||
var collected_ids: Array[int] = []
|
||||
for child_monitor in _child_monitors:
|
||||
collected_ids.append_array(child_monitor._orphan_ids_at_stop)
|
||||
collected_ids.append_array(child_monitor._collect_child_orphan_ids())
|
||||
return collected_ids
|
||||
|
||||
|
||||
func detected_orphans() -> Array[GdUnitOrphanNodeInfo]:
|
||||
if not _orphan_detection_enabled:
|
||||
return []
|
||||
return _collected_orphan_infos.filter(func(info: GdUnitOrphanNodeInfo) -> bool:
|
||||
return info._id in _orphan_ids_at_stop
|
||||
)
|
||||
|
||||
|
||||
func orphans_count() -> int:
|
||||
if not _orphan_detection_enabled:
|
||||
return 0
|
||||
return _orphan_ids_at_stop.size()
|
||||
|
||||
|
||||
func collect() -> void:
|
||||
if not _orphan_detection_enabled:
|
||||
return
|
||||
|
||||
stop()
|
||||
if _orphan_ids_at_stop.is_empty():
|
||||
return
|
||||
|
||||
var script_backtraces := Engine.capture_script_backtraces(true)
|
||||
for orphan_id in _orphan_ids_at_stop:
|
||||
var orphan_node := instance_from_id(orphan_id)
|
||||
_collect_orphan_info(orphan_node, script_backtraces)
|
||||
|
||||
|
||||
func _collect_orphan_info(orphan_node: Object, script_backtraces: Array[ScriptBacktrace]) -> void:
|
||||
if orphan_node == null:
|
||||
return
|
||||
|
||||
var orphan_info := _find_orphan_on_backtraces(orphan_node, script_backtraces)
|
||||
if orphan_info:
|
||||
_collected_orphan_infos.append(orphan_info)
|
||||
return
|
||||
|
||||
if Engine.has_meta("GdUnitSceneRunner"):
|
||||
var current_scene_runner:GdUnitSceneRunner = Engine.get_meta("GdUnitSceneRunner")
|
||||
if is_instance_valid(current_scene_runner):
|
||||
orphan_info = _find_orphan_at_node(orphan_node, current_scene_runner.scene())
|
||||
if orphan_info:
|
||||
_collected_orphan_infos.append(orphan_info)
|
||||
return
|
||||
|
||||
_collected_orphan_infos.append(
|
||||
GdUnitOrphanNodeInfo.new(
|
||||
orphan_node.get_instance_id(),
|
||||
orphan_node.get_class(),
|
||||
null)
|
||||
)
|
||||
|
||||
|
||||
func _find_orphan_at_node(orphan_node: Object, node: Node) -> GdUnitOrphanNodeInfo:
|
||||
var script: Script = node.get_script()
|
||||
if script is not GDScript:
|
||||
return null
|
||||
|
||||
# First search over all properties
|
||||
for property in script.get_script_property_list():
|
||||
# We lookup only over user script variables
|
||||
var property_usage: int = property["usage"]
|
||||
if property_usage != PROPERTY_USAGE_SCRIPT_VARIABLE:
|
||||
continue
|
||||
|
||||
var property_type: int = property["type"]
|
||||
# Is untyped or type object
|
||||
if property_type in [TYPE_NIL, TYPE_OBJECT]:
|
||||
var property_name: String = property["name"]
|
||||
var property_instance: Variant = node.get(property_name)
|
||||
@warning_ignore("unsafe_cast")
|
||||
var property_as_node := property_instance as Node
|
||||
if property_as_node == null:
|
||||
continue
|
||||
# If node match the curren property object
|
||||
if property_as_node == orphan_node:
|
||||
var property_class: String = property["class_name"]
|
||||
var source_line := _find_line_for_property(script, "", property_name)
|
||||
return GdUnitOrphanNodeInfo.new(
|
||||
orphan_node.get_instance_id(),
|
||||
property_class,
|
||||
GdUnitStackTraceElement.new(
|
||||
script.resource_path,
|
||||
source_line,
|
||||
property_name)
|
||||
)
|
||||
|
||||
# Otherwise we need to search on child node script properties
|
||||
var orphan_info := _find_orphan_at_node(orphan_node, property_as_node)
|
||||
if orphan_info:
|
||||
return orphan_info
|
||||
|
||||
# Second over all children
|
||||
for child_node in node.get_children():
|
||||
var orphan_info := _find_orphan_at_node(orphan_node, child_node)
|
||||
if orphan_info:
|
||||
return orphan_info
|
||||
return null
|
||||
|
||||
|
||||
func _find_orphan_on_backtraces(orphan_node: Object, script_backtraces: Array[ScriptBacktrace]) -> GdUnitOrphanNodeInfo:
|
||||
for script_backtrace in script_backtraces:
|
||||
for frame in script_backtrace.get_frame_count():
|
||||
var frame_file := script_backtrace.get_frame_file(frame)
|
||||
if GdUnitStackTrace.filter_sources(frame_file):
|
||||
continue
|
||||
|
||||
# Scan function variables
|
||||
for l_index in script_backtrace.get_local_variable_count(frame):
|
||||
var variable: Variant = script_backtrace.get_local_variable_value(frame, l_index)
|
||||
if typeof(variable) in [TYPE_NIL, TYPE_OBJECT]:
|
||||
@warning_ignore("unsafe_cast")
|
||||
var node := variable as Node
|
||||
if node == null:
|
||||
continue
|
||||
if variable == orphan_node:
|
||||
var variable_name := script_backtrace.get_local_variable_name(frame, l_index)
|
||||
var source_script := script_backtrace.get_frame_file(frame)
|
||||
var source_function := script_backtrace.get_frame_function(frame)
|
||||
var script: Script = load(source_script)
|
||||
var source_line := _find_line_for_property(script, source_function, variable_name)
|
||||
return GdUnitOrphanNodeInfo.new(
|
||||
orphan_node.get_instance_id(),
|
||||
orphan_node.get_class(),
|
||||
GdUnitStackTraceElement.new(source_script, source_line, variable_name)
|
||||
)
|
||||
else:
|
||||
var orphan_info := _find_orphan_at_node(orphan_node, node)
|
||||
if orphan_info:
|
||||
return orphan_info
|
||||
|
||||
# Scan class members
|
||||
for m_index in script_backtrace.get_member_variable_count(frame):
|
||||
var member: Variant = script_backtrace.get_member_variable_value(frame, m_index)
|
||||
if typeof(member) in [TYPE_NIL, TYPE_OBJECT]:
|
||||
@warning_ignore("unsafe_cast")
|
||||
var node := member as Node
|
||||
if node == null:
|
||||
continue
|
||||
if member == orphan_node:
|
||||
var member_name := script_backtrace.get_member_variable_name(frame, m_index)
|
||||
return GdUnitOrphanNodeInfo.new(
|
||||
orphan_node.get_instance_id(),
|
||||
orphan_node.get_class(),
|
||||
GdUnitStackTraceElement.new(
|
||||
script_backtrace.get_frame_file(frame),
|
||||
script_backtrace.get_frame_line(frame),
|
||||
member_name))
|
||||
else:
|
||||
var orphan_info := _find_orphan_at_node(orphan_node, node)
|
||||
if orphan_info:
|
||||
return orphan_info
|
||||
return null
|
||||
|
||||
|
||||
func _find_line_for_property(script: Script, func_name: String, property_name: String) -> int:
|
||||
if script == null or not script.has_source_code():
|
||||
return -1
|
||||
var lines := script.get_source_code().split("\n")
|
||||
var func_start_index := 0
|
||||
for index in range(0, lines.size()):
|
||||
var line := lines[index]
|
||||
if not func_name.is_empty():
|
||||
if line.begins_with("func") and line.contains(func_name):
|
||||
func_start_index = index + 1
|
||||
break;
|
||||
|
||||
for index in range(func_start_index, lines.size()):
|
||||
var line := lines[index]
|
||||
if line.contains(property_name):
|
||||
return index + 1
|
||||
if line.begins_with("func"):
|
||||
break
|
||||
return -1
|
||||
|
||||
|
||||
static func _get_orphan_node_ids() -> Array[int]:
|
||||
@warning_ignore("unsafe_property_access", "unsafe_method_access")
|
||||
var ids: Array[int] = Engine.get_main_loop().root.get_orphan_node_ids()
|
||||
# a node already queued for deletion is guaranteed to be freed, it is never a leak
|
||||
return ids.filter(func(node_id: int) -> bool:
|
||||
var node := instance_from_id(node_id) as Node
|
||||
return node == null or not node.is_queued_for_deletion()
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dle7kfwopyykt
|
||||
@@ -0,0 +1,113 @@
|
||||
class_name GodotGdErrorMonitor
|
||||
extends GdUnitMonitor
|
||||
|
||||
|
||||
var _logger: GdUnitLogger
|
||||
|
||||
|
||||
class GdUnitLogger extends Logger:
|
||||
var _entries: Array[ErrorLogEntry] = []
|
||||
var _is_report_push_errors: bool
|
||||
var _is_report_script_errors: bool
|
||||
|
||||
|
||||
func _init(is_report_push_errors: bool, is_report_script_errors: bool) -> void:
|
||||
_is_report_push_errors = is_report_push_errors
|
||||
_is_report_script_errors = is_report_script_errors
|
||||
OS.add_logger(self)
|
||||
|
||||
|
||||
func entries() -> Array[ErrorLogEntry]:
|
||||
return _entries
|
||||
|
||||
func erase_log_entry(log_entry: ErrorLogEntry) -> void:
|
||||
for entry in _entries:
|
||||
if entry._type == log_entry._type and entry._message == log_entry._message:
|
||||
_entries.erase(entry)
|
||||
return
|
||||
|
||||
|
||||
func _log_error(
|
||||
_function: String,
|
||||
_file: String,
|
||||
_line: int,
|
||||
message: String,
|
||||
_rationale: String,
|
||||
_editor_notify: bool,
|
||||
error_type: int,
|
||||
script_backtraces: Array[ScriptBacktrace]
|
||||
) -> void:
|
||||
|
||||
var stack_trace := GdUnitStackTrace.from_script_backtraces(script_backtraces)
|
||||
if stack_trace.get_frames().size() == 0:
|
||||
stack_trace = GdUnitStackTrace.new([
|
||||
GdUnitStackTraceElement.new(_file, _line, _function)
|
||||
])
|
||||
|
||||
match error_type:
|
||||
ErrorType.ERROR_TYPE_WARNING:
|
||||
if _is_report_push_errors:
|
||||
_entries.append(ErrorLogEntry.of_push_warning(stack_trace.get_line_number(), message, stack_trace))
|
||||
|
||||
ErrorType.ERROR_TYPE_ERROR:
|
||||
if _is_report_push_errors:
|
||||
_entries.append(ErrorLogEntry.of_push_error(stack_trace.get_line_number(), message, stack_trace))
|
||||
|
||||
ErrorType.ERROR_TYPE_SCRIPT:
|
||||
if _is_report_script_errors:
|
||||
_entries.append(ErrorLogEntry.of_script_error(stack_trace.get_line_number(), message, stack_trace))
|
||||
|
||||
ErrorType.ERROR_TYPE_SHADER:
|
||||
pass
|
||||
_:
|
||||
prints("Unknwon log type", message)
|
||||
|
||||
func _log_message(_message: String, _error: bool) -> void:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
super("GdUnitLoggerMonitor")
|
||||
_logger = GdUnitLogger.new(GdUnitSettings.is_report_push_errors(), GdUnitSettings.is_report_script_errors())
|
||||
|
||||
|
||||
func start() -> void:
|
||||
clear_logs()
|
||||
|
||||
|
||||
func stop() -> void:
|
||||
pass
|
||||
|
||||
|
||||
func log_entries() -> Array[ErrorLogEntry]:
|
||||
return _logger.entries()
|
||||
|
||||
|
||||
func erase_log_entry(log_entry: ErrorLogEntry) -> void:
|
||||
_logger.erase_log_entry(log_entry)
|
||||
|
||||
|
||||
func to_reports() -> Array[GdUnitReport]:
|
||||
var reports_: Array[GdUnitReport] = []
|
||||
|
||||
reports_.assign(log_entries().map(_to_report))
|
||||
|
||||
return reports_
|
||||
|
||||
|
||||
static func _to_report(errorLog: ErrorLogEntry) -> GdUnitReport:
|
||||
|
||||
var failure := """
|
||||
%s
|
||||
%s""".dedent().trim_prefix("\n") % [
|
||||
GdAssertMessages._error("Godot Runtime Error !"),
|
||||
GdAssertMessages._colored_value(errorLog._message)]
|
||||
var error := GdUnitError.new(failure, errorLog._line, errorLog._stack_trace)
|
||||
return GdUnitReport.new().from_error(GdUnitReport.ABORT, error)
|
||||
|
||||
|
||||
func clear_logs() -> void:
|
||||
log_entries().clear()
|
||||
@@ -0,0 +1 @@
|
||||
uid://be25rtwmwty6w
|
||||
Reference in new issue
Block a user