summaryrefslogtreecommitdiff
path: root/addons/gdUnit4/src/monitor
diff options
context:
space:
mode:
Diffstat (limited to 'addons/gdUnit4/src/monitor')
-rw-r--r--addons/gdUnit4/src/monitor/ErrorLogEntry.gd41
-rw-r--r--addons/gdUnit4/src/monitor/ErrorLogEntry.gd.uid1
-rw-r--r--addons/gdUnit4/src/monitor/GdUnitMonitor.gd24
-rw-r--r--addons/gdUnit4/src/monitor/GdUnitMonitor.gd.uid1
-rw-r--r--addons/gdUnit4/src/monitor/GdUnitOrphanNodeInfo.gd70
-rw-r--r--addons/gdUnit4/src/monitor/GdUnitOrphanNodeInfo.gd.uid1
-rw-r--r--addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd221
-rw-r--r--addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd.uid1
-rw-r--r--addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd122
-rw-r--r--addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd.uid1
10 files changed, 483 insertions, 0 deletions
diff --git a/addons/gdUnit4/src/monitor/ErrorLogEntry.gd b/addons/gdUnit4/src/monitor/ErrorLogEntry.gd
new file mode 100644
index 0000000..ebbc93f
--- /dev/null
+++ b/addons/gdUnit4/src/monitor/ErrorLogEntry.gd
@@ -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 _details: String
+
+
+func _init(type: TYPE, line: int, message: String, details: String) -> void:
+ _type = type
+ _line = line
+ _message = message
+ _details = details
+
+
+func _to_string() -> String:
+ return _message
+
+
+static func of_push_warning(line: int, message: String, stack_trace: PackedStringArray) -> ErrorLogEntry:
+ return ErrorLogEntry.new(TYPE.PUSH_WARNING, line, message, "\n".join(stack_trace))
+
+
+static func of_push_error(line: int, message: String, stack_trace: PackedStringArray) -> ErrorLogEntry:
+ return ErrorLogEntry.new(TYPE.PUSH_ERROR, line, message, "\n".join(stack_trace))
+
+
+static func of_script_error(line: int, message: String, stack_trace: PackedStringArray) -> ErrorLogEntry:
+ return ErrorLogEntry.new(TYPE.SCRIPT_ERROR, line, message, "\n".join(stack_trace))
diff --git a/addons/gdUnit4/src/monitor/ErrorLogEntry.gd.uid b/addons/gdUnit4/src/monitor/ErrorLogEntry.gd.uid
new file mode 100644
index 0000000..4025709
--- /dev/null
+++ b/addons/gdUnit4/src/monitor/ErrorLogEntry.gd.uid
@@ -0,0 +1 @@
+uid://ca534u63pypt7
diff --git a/addons/gdUnit4/src/monitor/GdUnitMonitor.gd b/addons/gdUnit4/src/monitor/GdUnitMonitor.gd
new file mode 100644
index 0000000..b6429ca
--- /dev/null
+++ b/addons/gdUnit4/src/monitor/GdUnitMonitor.gd
@@ -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
diff --git a/addons/gdUnit4/src/monitor/GdUnitMonitor.gd.uid b/addons/gdUnit4/src/monitor/GdUnitMonitor.gd.uid
new file mode 100644
index 0000000..b4875fd
--- /dev/null
+++ b/addons/gdUnit4/src/monitor/GdUnitMonitor.gd.uid
@@ -0,0 +1 @@
+uid://bd5amxrxcckjv
diff --git a/addons/gdUnit4/src/monitor/GdUnitOrphanNodeInfo.gd b/addons/gdUnit4/src/monitor/GdUnitOrphanNodeInfo.gd
new file mode 100644
index 0000000..c195760
--- /dev/null
+++ b/addons/gdUnit4/src/monitor/GdUnitOrphanNodeInfo.gd
@@ -0,0 +1,70 @@
+class_name GdUnitOrphanNodeInfo
+extends RefCounted
+
+enum GdUnitOrphanType {
+ member,
+ variable,
+ unknown
+}
+
+
+var _id: int
+var _orphan_type: GdUnitOrphanType
+var _type: String
+var _name: String
+var _script_ref: String
+var _func_ref: String
+var _next: GdUnitOrphanNodeInfo
+
+const text_color := Color.ANTIQUE_WHITE
+const function_color := Color.SKY_BLUE
+const member_variable_color := Color.SALMON
+const engine_type_color := Color.LIGHT_GREEN
+const script_path_color := Color.CORNFLOWER_BLUE
+
+
+func _init(orphan_type: GdUnitOrphanType, id: int, type: String, name: String, script_ref: String, func_ref: String = "") -> void:
+ _orphan_type = orphan_type
+ _id = id
+ _type = type
+ _name = name
+ _script_ref = script_ref
+ _func_ref = func_ref
+
+
+func as_trace(info: GdUnitOrphanNodeInfo, show_orphan_id := true) -> String:
+ var trace := ""
+ if show_orphan_id:
+ trace += "• <%s> Id:%s\n" % [
+ _colored(info._type, engine_type_color),
+ _colored(info._id, engine_type_color)]
+ match info._orphan_type:
+ GdUnitOrphanType.member:
+ return trace + " at %s script: %s" % [
+ _colored(info._name, member_variable_color),
+ _colored(info._script_ref, script_path_color)
+ ] + sub_info(info._next)
+ GdUnitOrphanType.variable:
+ return trace + " at %s script: %s.%s()" % [
+ _colored(info._name, member_variable_color),
+ _colored(info._script_ref, script_path_color),
+ _colored(info._func_ref, function_color),
+ ]
+ GdUnitOrphanType.unknown:
+ return trace + " %s" % [
+ _colored(info._name, member_variable_color)
+ ]
+
+ _:
+ return trace + " No details available"
+
+
+func sub_info(next: GdUnitOrphanNodeInfo) -> String:
+ if next == null:
+ return ""
+
+ return "\n" + as_trace(next, false)
+
+
+static func _colored(value: Variant, color: Color) -> String:
+ return "[color=%s]%s[/color]" % [color.to_html(), value]
diff --git a/addons/gdUnit4/src/monitor/GdUnitOrphanNodeInfo.gd.uid b/addons/gdUnit4/src/monitor/GdUnitOrphanNodeInfo.gd.uid
new file mode 100644
index 0000000..a74b4e2
--- /dev/null
+++ b/addons/gdUnit4/src/monitor/GdUnitOrphanNodeInfo.gd.uid
@@ -0,0 +1 @@
+uid://lc78iyt2b13e
diff --git a/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd b/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd
new file mode 100644
index 0000000..05f11cd
--- /dev/null
+++ b/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd
@@ -0,0 +1,221 @@
+class_name GdUnitOrphanNodesMonitor
+extends GdUnitMonitor
+
+const excluded_frame_files: PackedStringArray = [
+ "GdUnitOrphanNodesMonitor",
+ "GdUnitExecutionContext",
+ "_TestCase",
+ "IGdUnitExecutionStage",
+ "GdUnitTestCaseSingleTestStage",
+ "GdUnitTestCaseSingleExecutionStage",
+ "GdUnitTestCaseExecutionStage",
+ "GdUnitTestSuiteExecutionStage",
+ "GdUnitTestSuiteExecutor"
+]
+
+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
+ for orphan_id in _get_orphan_node_ids():
+ var orphan_to_find := instance_from_id(orphan_id)
+ _collect_orphan_info(orphan_to_find)
+
+
+func _collect_orphan_info(orphan_to_find: Object) -> void:
+ if orphan_to_find == null:
+ return
+
+ var orphan_node := _find_orphan_on_backtraces(orphan_to_find)
+ if orphan_node:
+ _collected_orphan_infos.append(orphan_node)
+ return
+
+ if Engine.has_meta("GdUnitSceneRunner"):
+ var current_scene_runner:GdUnitSceneRunner = Engine.get_meta("GdUnitSceneRunner")
+ if is_instance_valid(current_scene_runner):
+ orphan_node = _find_orphan_at_node(orphan_to_find, current_scene_runner.scene())
+ if orphan_node:
+ _collected_orphan_infos.append(orphan_node)
+ return
+
+ # not able to find the orphan node via backtrace loaded nodeds
+ var message := "No details found. Verify called functions manually."
+ if not EngineDebugger.is_active():
+ message = "No details available. [color=yellow]Run tests in debug mode to collect details.[/color]"
+
+ _collected_orphan_infos.append(GdUnitOrphanNodeInfo.new(
+ GdUnitOrphanNodeInfo.GdUnitOrphanType.unknown,
+ orphan_to_find.get_instance_id(),
+ orphan_to_find.get_class(),
+ message,
+ ""))
+
+
+func _find_orphan_at_node(orphan_to_find: 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():
+ var property_name: String = property["name"]
+ var property_type: int = property["type"]
+ # Is untyped or type object
+ if property_type in [TYPE_NIL, TYPE_OBJECT]:
+ var property_instance: Variant = node.get(property_name)
+ @warning_ignore("unsafe_cast")
+ var property_as_node := property_instance as Node if property_instance != null else null
+ if property_as_node == null:
+ continue
+ if property_as_node == orphan_to_find:
+ return GdUnitOrphanNodeInfo.new(
+ GdUnitOrphanNodeInfo.GdUnitOrphanType.member,
+ orphan_to_find.get_instance_id(),
+ orphan_to_find.get_class(),
+ property_name,
+ script.resource_path)
+
+ # Search on node childs
+ var orphan_node_info := _find_orphan_at_node(orphan_to_find, property_as_node)
+ if orphan_node_info:
+ orphan_node_info._next = GdUnitOrphanNodeInfo.new(
+ GdUnitOrphanNodeInfo.GdUnitOrphanType.member,
+ orphan_to_find.get_instance_id(),
+ orphan_to_find.get_class(),
+ property_name,
+ script.resource_path)
+ return orphan_node_info
+
+ # Second over all children
+ for child_node in node.get_children():
+ var orphan_node_info := _find_orphan_at_node(orphan_to_find, child_node)
+ if orphan_node_info:
+ return orphan_node_info
+ return null
+
+
+func _is_frame_file_excluded(frame_file: String) -> bool:
+ for file in excluded_frame_files:
+ if frame_file.contains(file):
+ return true
+ return false
+
+
+func _find_orphan_on_backtraces(orphan_to_find: Object) -> GdUnitOrphanNodeInfo:
+ for script_backtrace in Engine.capture_script_backtraces(true):
+ for frame in script_backtrace.get_frame_count():
+ var frame_file := script_backtrace.get_frame_file(frame)
+ if _is_frame_file_excluded(frame_file):
+ continue
+
+ # Scan function variables
+ for l_index in script_backtrace.get_local_variable_count(frame):
+ var variable_instance: Variant = script_backtrace.get_local_variable_value(frame, l_index)
+ var variable_name := script_backtrace.get_local_variable_name(frame, l_index)
+ if typeof(variable_instance) in [TYPE_NIL, TYPE_OBJECT]:
+ @warning_ignore("unsafe_cast")
+ var node := variable_instance as Node
+ if node == null:
+ continue
+ if variable_instance == orphan_to_find:
+ return GdUnitOrphanNodeInfo.new(
+ GdUnitOrphanNodeInfo.GdUnitOrphanType.variable,
+ orphan_to_find.get_instance_id(),
+ orphan_to_find.get_class(),
+ variable_name,
+ script_backtrace.get_frame_file(frame),
+ script_backtrace.get_frame_function(frame))
+ else:
+ var orphan_node_info := _find_orphan_at_node(orphan_to_find, node)
+ if orphan_node_info:
+ return orphan_node_info
+
+ # Scan class members
+ for m_index in script_backtrace.get_member_variable_count(frame):
+ var member_instance: Variant = script_backtrace.get_member_variable_value(frame, m_index)
+ var member_name := script_backtrace.get_member_variable_name(frame, m_index)
+ if typeof(member_instance) in [TYPE_NIL, TYPE_OBJECT]:
+ @warning_ignore("unsafe_cast")
+ var node := member_instance as Node
+ if node == null:
+ continue
+ if member_instance == orphan_to_find:
+ return GdUnitOrphanNodeInfo.new(
+ GdUnitOrphanNodeInfo.GdUnitOrphanType.member,
+ orphan_to_find.get_instance_id(),
+ orphan_to_find.get_class(),
+ member_name,
+ script_backtrace.get_frame_file(frame))
+ else:
+ var orphan_node_info := _find_orphan_at_node(orphan_to_find, node)
+ if orphan_node_info:
+ return orphan_node_info
+ return null
+
+
+static func _get_orphan_node_ids() -> Array[int]:
+ @warning_ignore("unsafe_property_access", "unsafe_method_access")
+ return Engine.get_main_loop().root.get_orphan_node_ids()
diff --git a/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd.uid b/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd.uid
new file mode 100644
index 0000000..42c79c7
--- /dev/null
+++ b/addons/gdUnit4/src/monitor/GdUnitOrphanNodesMonitor.gd.uid
@@ -0,0 +1 @@
+uid://ckqlijgdcnfno
diff --git a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd
new file mode 100644
index 0000000..b1b28d7
--- /dev/null
+++ b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd
@@ -0,0 +1,122 @@
+class_name GodotGdErrorMonitor
+extends GdUnitMonitor
+
+
+var _logger: GdUnitLogger
+
+
+class GdUnitLogger extends Logger:
+ var _entries: Array[ErrorLogEntry] = []
+ var _line_number: int
+ 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:
+ match error_type:
+ ErrorType.ERROR_TYPE_WARNING:
+ if _is_report_push_errors:
+ var stack_trace := _build_stack_trace(script_backtraces)
+ _entries.append(ErrorLogEntry.of_push_warning(_line_number, message, stack_trace))
+
+ ErrorType.ERROR_TYPE_ERROR:
+ if _is_report_push_errors:
+ var stack_trace := _build_stack_trace(script_backtraces)
+ _entries.append(ErrorLogEntry.of_push_error(_line_number, message, stack_trace))
+
+ ErrorType.ERROR_TYPE_SCRIPT:
+ if _is_report_script_errors:
+ var stack_trace := _build_stack_trace(script_backtraces)
+ _entries.append(ErrorLogEntry.of_script_error(_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 _build_stack_trace(script_backtraces: Array[ScriptBacktrace]) -> PackedStringArray:
+ for sb in script_backtraces:
+ for frame in sb.get_frame_count():
+ # Find start of test stack
+ if sb.get_frame_file(frame) == "res://addons/gdUnit4/src/core/_TestCase.gd":
+ var stack_trace := PackedStringArray()
+ for test_case_frame in range(0, frame):
+ _line_number = sb.get_frame_line(test_case_frame)
+ stack_trace.append(" at %s:%s" % [sb.get_frame_file(test_case_frame), sb.get_frame_line(test_case_frame)])
+ return stack_trace
+ # if no stack trace collected, we in an await function call
+ var sb := script_backtraces[0]
+ return [" at %s:%s" % [sb.get_frame_file(0), sb.get_frame_line(0)]]
+
+
+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 %s
+ %s""".dedent().trim_prefix("\n") % [
+ GdAssertMessages._error("Godot Runtime Error !"),
+ GdAssertMessages._error("Error:"),
+ GdAssertMessages._colored_value(errorLog._message),
+ GdAssertMessages._colored(errorLog._details, GdAssertMessages.VALUE_COLOR)]
+ return GdUnitReport.new().create(GdUnitReport.ABORT, errorLog._line, failure)
+
+
+func clear_logs() -> void:
+ log_entries().clear()
diff --git a/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd.uid b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd.uid
new file mode 100644
index 0000000..1b1e96b
--- /dev/null
+++ b/addons/gdUnit4/src/monitor/GodotGdErrorMonitor.gd.uid
@@ -0,0 +1 @@
+uid://bhessnp2uk5no