diff options
Diffstat (limited to 'addons/gdUnit4/src/core/hooks')
10 files changed, 405 insertions, 0 deletions
diff --git a/addons/gdUnit4/src/core/hooks/GdUnitBaseReporterTestSessionHook.gd b/addons/gdUnit4/src/core/hooks/GdUnitBaseReporterTestSessionHook.gd new file mode 100644 index 0000000..0f87ad1 --- /dev/null +++ b/addons/gdUnit4/src/core/hooks/GdUnitBaseReporterTestSessionHook.gd @@ -0,0 +1,78 @@ +class_name GdUnitBaseReporterTestSessionHook +extends GdUnitTestSessionHook + + +var test_session: GdUnitTestSession: + get: + return test_session + set(value): + # disconnect first possible connected listener + if test_session != null: + test_session.test_event.disconnect(_on_test_event) + # add listening to current session + test_session = value + if test_session != null: + test_session.test_event.connect(_on_test_event) + + +var _report_summary: GdUnitReportSummary +var _reporter: GdUnitTestReporter +var _report_writer: GdUnitReportWriter +var _report_converter: Callable + +func _init(report_writer: GdUnitReportWriter, hook_name: String, hook_description: String, report_converter: Callable) -> void: + super(hook_name, hook_description) + _reporter = GdUnitTestReporter.new() + _report_writer = report_writer + _report_converter = report_converter + + +func startup(session: GdUnitTestSession) -> GdUnitResult: + test_session = session + _report_summary = GdUnitReportSummary.new(_report_converter) + _reporter.init_summary() + + return GdUnitResult.success() + + +func shutdown(session: GdUnitTestSession) -> GdUnitResult: + var report_path := _report_writer.write(session.report_path, _report_summary) + session.send_message("Open {0} Report at: file://{1}".format([_report_writer.output_format(), report_path])) + + return GdUnitResult.success() + + +func _on_test_event(event: GdUnitEvent) -> void: + match event.type(): + GdUnitEvent.TESTSUITE_BEFORE: + _reporter.init_statistics() + _report_summary.add_testsuite_report(event.resource_path(), event.suite_name(), event.total_count()) + GdUnitEvent.TESTSUITE_AFTER: + var statistics := _reporter.build_test_suite_statisitcs(event) + _report_summary.update_testsuite_counters( + event.resource_path(), + _reporter.error_count(statistics), + _reporter.failed_count(statistics), + _reporter.orphan_nodes(statistics), + _reporter.skipped_count(statistics), + _reporter.flaky_count(statistics), + event.elapsed_time()) + _report_summary.add_testsuite_reports( + event.resource_path(), + event.reports() + ) + GdUnitEvent.TESTCASE_BEFORE: + var test := test_session.find_test_by_id(event.guid()) + _report_summary.add_testcase(test.source_file, test.suite_name, test.display_name) + GdUnitEvent.TESTCASE_AFTER: + _reporter.add_test_statistics(event) + var test := test_session.find_test_by_id(event.guid()) + _report_summary.set_counters(test.source_file, + test.display_name, + event.error_count(), + event.failed_count(), + event.orphan_nodes(), + event.is_skipped(), + event.is_flaky(), + event.elapsed_time()) + _report_summary.add_reports(test.source_file, test.display_name, event.reports()) diff --git a/addons/gdUnit4/src/core/hooks/GdUnitBaseReporterTestSessionHook.gd.uid b/addons/gdUnit4/src/core/hooks/GdUnitBaseReporterTestSessionHook.gd.uid new file mode 100644 index 0000000..94a488c --- /dev/null +++ b/addons/gdUnit4/src/core/hooks/GdUnitBaseReporterTestSessionHook.gd.uid @@ -0,0 +1 @@ +uid://dba8xduvhy4hd diff --git a/addons/gdUnit4/src/core/hooks/GdUnitHtmlReporterTestSessionHook.gd b/addons/gdUnit4/src/core/hooks/GdUnitHtmlReporterTestSessionHook.gd new file mode 100644 index 0000000..4b8f390 --- /dev/null +++ b/addons/gdUnit4/src/core/hooks/GdUnitHtmlReporterTestSessionHook.gd @@ -0,0 +1,9 @@ +class_name GdUnitHtmlReporterTestSessionHook +extends GdUnitBaseReporterTestSessionHook + +const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd") + + +func _init() -> void: + super(GdUnitHtmlReportWriter.new(), "GdUnitHtmlTestReporter", "The Html test reporting hook.", GdUnitTools.richtext_normalize) + set_meta("SYSTEM_HOOK", true) diff --git a/addons/gdUnit4/src/core/hooks/GdUnitHtmlReporterTestSessionHook.gd.uid b/addons/gdUnit4/src/core/hooks/GdUnitHtmlReporterTestSessionHook.gd.uid new file mode 100644 index 0000000..6af94d5 --- /dev/null +++ b/addons/gdUnit4/src/core/hooks/GdUnitHtmlReporterTestSessionHook.gd.uid @@ -0,0 +1 @@ +uid://dsvmfyc8bxff8 diff --git a/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHook.gd b/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHook.gd new file mode 100644 index 0000000..23850e4 --- /dev/null +++ b/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHook.gd @@ -0,0 +1,111 @@ +## @since GdUnit4 5.1.0 +## +## Base class for creating custom test session hooks in GdUnit4.[br] +## [br] +## [i]Test session hooks allow users to extend the GdUnit4 test framework by providing +## custom functionality that runs at specific points during the test execution lifecycle. +## This base class defines the interface that all test session hooks must implement.[/i] +## [br] +## [br] +## [b][u]Usage[/u][/b][br] +## 1. Create a new class that extends GdUnitTestSessionHook[br] +## 2. Override the required methods (startup, shutdown)[br] +## 3. Register your hook with the test engine (using the GdUnit4 settings dialog)[br] +## [br] +## [b][u]Example[/u][/b] +## [codeblock] +## class_name MyCustomTestHook +## extends GdUnitTestSessionHook +## +## func _init(): +## super("MyHook", "This is a description") +## +## func startup(session: GdUnitTestSession) -> GdUnitResult: +## session.send_message("Custom hook initialized") +## # Initialize resources, setup test environment, etc. +## return GdUnitResult.success() +## +## func shutdown(session: GdUnitTestSession) -> GdUnitResult: +## session.send_message("Custom hook cleanup completed") +## # Cleanup resources, generate reports, etc. +## return GdUnitResult.success() +## [/codeblock] +## +## [b][u]Hook Lifecycle[/u][/b][br] +## 1. [i][b]Registration[/b][/i]: Hooks are registered with the test engine via settings dialog[br] +## 2. [i][b]Priority Sorting[/b][/i]: Hooks are sorted by priority[br] +## 3. [i][b]Startup[/b][/i]: startup() is called before test execution begins, if it returns an error is shown in the console[br] +## 4. [i][b]Test Execution[/b][/i]: Tests run normally (only if all hooks started successfully)[br] +## 5. [i][b]Shutdown[/b][/i]: shutdown() is called after all tests complete, regardless of startup success[br] +## [br] +## [b][u]Priority System[/u][/b][br] +## The priority system allows controlling the execution order of multiple hooks.[br] +## - The order can be changed in the GdUnit4 settings dialog.[br] +## - The priority of system hooks cannot be changed and they cannot be deleted.[br] +## [br] +## [b][u]Session Access[/u][/b][br] +## +## Both [i]startup()[/i] and [i]shutdown()[/i] methods receive a [GdUnitTestSession] parameter that provides:[br] +## - Access to test cases being executed[br] +## - Event emission capabilities for test progress tracking[br] +## - Message sending functionality for logging and communication[br] +class_name GdUnitTestSessionHook +extends RefCounted + + +## The display name of this hook. +var name: String: + get: + return name + + +## A detailed description of what this hook does. +var description: String: + get: + return description + + +## Initializes a new test session hook. +## +## [param _name] The display name for this hook +## [param _description] A detailed description of the hook's functionality +func _init(_name: String, _description: String) -> void: + self.name = _name + self.description = _description + + +## Called when the test session starts up, before any tests are executed.[br] +## [br] +## [color=yellow][i]This method should be overridden to implement custom initialization logic[/i][/color][br] +## [br] +## such as:[br] +## - Setting up test databases or external services[br] +## - Initializing mock objects or test fixtures[br] +## - Configuring logging or reporting systems[br] +## - Preparing the test environment[br] +## - Subscribing to test events via the session[br] +## [br] +## [param session] The test session instance providing access to test data and communication[br] +## [b]return:[/b] [code]GdUnitResult.success()[/code] if initialization succeeds, or [code]GdUnitResult.error("error")[/code] with +## an error message if initialization fails. +func startup(_session: GdUnitTestSession) -> GdUnitResult: + return GdUnitResult.error("%s:startup is not implemented" % get_script().resource_path) + + +## Called when the test session shuts down, after all tests have completed.[br] +## [br] +## [color=yellow][i]This method should be overridden to implement custom cleanup logic[/i][/color][br] +## [br] +## such as:[br] +## - Cleaning up test databases or external services[br] +## - Generating test reports or artifacts[br] +## - Releasing resources allocated during startup[br] +## - Performing final validation or assertions[br] +## - Processing collected test events and data[br] +## [br] +## [param session] The test session instance providing access to test results and communication[br] +## [b]return:[/b] [code]GdUnitResult.success()[/code] if cleanup succeeds, or [code]GdUnitResult.error("error")[/code] with +## an error message if cleanup fails. Cleanup errors are typically logged +## but don't prevent the test engine from shutting down. +func shutdown(_session: GdUnitTestSession) -> GdUnitResult: + return GdUnitResult.error("%s:shutdown is not implemented" % get_script().resource_path) diff --git a/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHook.gd.uid b/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHook.gd.uid new file mode 100644 index 0000000..719a67e --- /dev/null +++ b/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHook.gd.uid @@ -0,0 +1 @@ +uid://cd4t5sc3jv2i7 diff --git a/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHookService.gd b/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHookService.gd new file mode 100644 index 0000000..5a9bdcb --- /dev/null +++ b/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHookService.gd @@ -0,0 +1,191 @@ +class_name GdUnitTestSessionHookService +extends Object + + +var enigne_hooks: Array[GdUnitTestSessionHook] = []: + get: + return enigne_hooks + set(value): + enigne_hooks.append(value) + + +var _save_settings: bool = false + + +static func instance() -> GdUnitTestSessionHookService: + return GdUnitSingleton.instance("GdUnitTestSessionHookService", func()->GdUnitTestSessionHookService: + GdUnitSignals.instance().gdunit_message.emit("Installing GdUnit4 session system hooks.") + var service := GdUnitTestSessionHookService.new() + # Register default system hooks here + service._save_settings = false + service.register(GdUnitHtmlReporterTestSessionHook.new()) + service.register(GdUnitXMLReporterTestSessionHook.new()) + service.load_hook_settings() + service._save_settings = true + return service + ) + + +static func contains_hook(current: GdUnitTestSessionHook, other: GdUnitTestSessionHook) -> bool: + return current.get_script().resource_path == other.get_script().resource_path + + +func find_custom(hook: GdUnitTestSessionHook) -> int: + for index in enigne_hooks.size(): + if contains_hook.call(enigne_hooks[index], hook): + return index + return -1 + + +func load_hook(hook_resourc_path: String) -> GdUnitResult: + if !FileAccess.file_exists(hook_resourc_path): + return GdUnitResult.error("The hook '%s' not exists." % hook_resourc_path) + var script: GDScript = load(hook_resourc_path) + if script.get_base_script() != GdUnitTestSessionHook: + return GdUnitResult.error("The hook '%s' must inhertit from 'GdUnitTestSessionHook'." % hook_resourc_path) + + return GdUnitResult.success(script.new()) + + +func enable_hook(hook: GdUnitTestSessionHook, enabled: bool) -> void: + _enable_hook(hook, enabled) + GdUnitSignals.instance().gdunit_message.emit("Session hook '{name}' {enabled}.".format({ + "name": hook.name, + "enabled": "enabled" if enabled else "disabled"}) + ) + save_hock_setttings() + + +func register(hook: GdUnitTestSessionHook, enabled: bool = true) -> GdUnitResult: + if find_custom(hook) != -1: + return GdUnitResult.error("A hook instance of '%s' is already registered." % hook.get_script().resource_path) + + _enable_hook(hook, enabled) + enigne_hooks.append(hook) + save_hock_setttings() + GdUnitSignals.instance().gdunit_message.emit("Session hook '%s' installed." % hook.name) + + return GdUnitResult.success() + + +func unregister(hook: GdUnitTestSessionHook) -> GdUnitResult: + var hook_index := find_custom(hook) + if hook_index == -1: + return GdUnitResult.error("The hook instance of '%s' is NOT registered." % hook.get_script().resource_path) + + enigne_hooks.remove_at(hook_index) + save_hock_setttings() + return GdUnitResult.success() + + +func move_before(hook: GdUnitTestSessionHook, before: GdUnitTestSessionHook) -> void: + var before_index := find_custom(before) + var hook_index := find_custom(hook) + + # Verify the hook to move is behind the hook to be moved + if before_index >= hook_index: + return + + enigne_hooks.remove_at(hook_index) + enigne_hooks.insert(before_index, hook) + save_hock_setttings() + + +func move_after(hook: GdUnitTestSessionHook, after: GdUnitTestSessionHook) -> void: + var after_index := find_custom(after) + var hook_index := find_custom(hook) + + # Verify the hook to move is before the hook to be moved + if after_index <= hook_index: + return + + enigne_hooks.remove_at(hook_index) + enigne_hooks.insert(after_index, hook) + save_hock_setttings() + + +func execute_startup(session: GdUnitTestSession) -> GdUnitResult: + return await execute("startup", session) + + +func execute_shutdown(session: GdUnitTestSession) -> GdUnitResult: + return await execute("shutdown", session, true) + + +func execute(hook_func: String, session: GdUnitTestSession, reverse := false) -> GdUnitResult: + var failed_hook_calls: Array[GdUnitResult] = [] + + for hook_index in enigne_hooks.size(): + var index := enigne_hooks.size()-hook_index-1 if reverse else hook_index + var hook: = enigne_hooks[index] + if not is_enabled(hook): + continue + if OS.is_stdout_verbose(): + GdUnitSignals.instance().gdunit_message.emit("Session hook '%s' > %s()" % [hook.name, hook_func]) + var result: GdUnitResult = await hook.call(hook_func, session) + if result == null: + failed_hook_calls.push_back(GdUnitResult.error("Result is null! Check '%s'" % hook.get_script().resource_path)) + elif result.is_error(): + failed_hook_calls.push_back(result) + + if failed_hook_calls.is_empty(): + return GdUnitResult.success() + + var errors := failed_hook_calls.map(func(result: GdUnitResult) -> String: + return "Hook call '%s' failed with error: '%s'" % [hook_func, result.error_message()] + ) + return GdUnitResult.error( "\n".join(errors)) + + +func save_hock_setttings() -> void: + if not _save_settings: + return + + var hooks_to_save: Dictionary[String, bool] = {} + for hook in enigne_hooks: + var enabled: bool = hook.get_meta("enabled") + hooks_to_save[hook.get_script().resource_path] = enabled + + GdUnitSettings.set_session_hooks(hooks_to_save) + + +func load_hook_settings() -> void: + var hooks_resource_paths := GdUnitSettings.get_session_hooks() + if hooks_resource_paths.is_empty(): + return + + for hock_path: String in hooks_resource_paths.keys(): + var enabled := hooks_resource_paths[hock_path] + + # Do not reinstall already installed hooks + var existing_hook: GdUnitTestSessionHook = enigne_hooks.filter(func(element: GdUnitTestSessionHook) -> bool: + return element.get_script().resource_path == hock_path + ).front() + # Applay enabled settings + if existing_hook != null: + _enable_hook(existing_hook, enabled) + continue + + # Load additional hooks + var result := load_hook(hock_path) + if result.is_error(): + push_error(result.error_message()) + continue + + GdUnitSignals.instance().gdunit_message.emit("Installing GdUnit4 session hooks.") + var hook: GdUnitTestSessionHook = result.value() + + result = register(hook, enabled) + if result.is_error(): + push_error(result.error_message()) + continue + + +static func is_enabled(hook: GdUnitTestSessionHook) -> bool: + if hook.has_meta("enabled"): + return hook.get_meta("enabled") + return true + + +func _enable_hook(hook: GdUnitTestSessionHook, enabled: bool) -> void: + hook.set_meta("enabled", enabled) diff --git a/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHookService.gd.uid b/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHookService.gd.uid new file mode 100644 index 0000000..690538d --- /dev/null +++ b/addons/gdUnit4/src/core/hooks/GdUnitTestSessionHookService.gd.uid @@ -0,0 +1 @@ +uid://bp5t2wb0depb6 diff --git a/addons/gdUnit4/src/core/hooks/GdUnitXMLReporterTestSessionHook.gd b/addons/gdUnit4/src/core/hooks/GdUnitXMLReporterTestSessionHook.gd new file mode 100644 index 0000000..94caef5 --- /dev/null +++ b/addons/gdUnit4/src/core/hooks/GdUnitXMLReporterTestSessionHook.gd @@ -0,0 +1,11 @@ +class_name GdUnitXMLReporterTestSessionHook +extends GdUnitBaseReporterTestSessionHook + + +func _init() -> void: + super(JUnitXmlReportWriter.new(), "GdUnitXMLTestReporter", "The JUnit XML test reporting hook.", convert_report_message) + set_meta("SYSTEM_HOOK", true) + + +func convert_report_message(value: String) -> String: + return value diff --git a/addons/gdUnit4/src/core/hooks/GdUnitXMLReporterTestSessionHook.gd.uid b/addons/gdUnit4/src/core/hooks/GdUnitXMLReporterTestSessionHook.gd.uid new file mode 100644 index 0000000..42d6bff --- /dev/null +++ b/addons/gdUnit4/src/core/hooks/GdUnitXMLReporterTestSessionHook.gd.uid @@ -0,0 +1 @@ +uid://btuob2k5s80l |
