diff --git a/setup/tests_summary/odoo/addons/tests_summary b/setup/tests_summary/odoo/addons/tests_summary new file mode 120000 index 00000000000..3f4bd4a8519 --- /dev/null +++ b/setup/tests_summary/odoo/addons/tests_summary @@ -0,0 +1 @@ +../../../../tests_summary \ No newline at end of file diff --git a/setup/tests_summary/setup.py b/setup/tests_summary/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/tests_summary/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/tests_summary/README.rst b/tests_summary/README.rst new file mode 100644 index 00000000000..3c405eaf61e --- /dev/null +++ b/tests_summary/README.rst @@ -0,0 +1,94 @@ +============ +Test Summary +============ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:126364c9ac7a093bc9b07176ff3b3cf0026146b57743ec5486d79788f96cd1e7 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github + :target: https://github.com/OCA/server-tools/tree/14.0/tests_summary + :alt: OCA/server-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-tools-14-0/server-tools-14-0-tests_summary + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Add this module to your ``server_wide_modules`` list in your odoo.conf +file to enable the tests summary feature. + +At the end of the tests, a summary will be displayed in the logs for +quick review of the test failures grouped by module. + +The summary will look like this: + +|Tests Summary Tests Summary| + +.. |Tests Summary Tests Summary| image:: https://raw.githubusercontent.com/OCA/server-tools/14.0/tests_summary/static/description/tests_summary.png + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Akretion + +Contributors +------------ + +- Florian Mounier florian.mounier@akretion.com + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-paradoxxxzero| image:: https://github.com/paradoxxxzero.png?size=40px + :target: https://github.com/paradoxxxzero + :alt: paradoxxxzero + +Current `maintainer `__: + +|maintainer-paradoxxxzero| + +This module is part of the `OCA/server-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/tests_summary/__init__.py b/tests_summary/__init__.py new file mode 100644 index 00000000000..de0f59b2825 --- /dev/null +++ b/tests_summary/__init__.py @@ -0,0 +1,165 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from unittest.mock import patch +from collections import defaultdict +import logging +import re +import os + +from odoo.tools import config +from odoo.service.server import preload_registries +from odoo.modules.registry import Registry + + +class Colorize: + colors = ("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white") + + def __init__(self, logger): + self.active = os.name == "posix" and all( + isinstance(handler, logging.StreamHandler) + and hasattr(handler.stream, "fileno") + and os.isatty(handler.stream.fileno()) + for handler in logger.handlers + ) + + def bold(self, text): + if not self.active: + return text + return f"\033[1m{text}\033[0m" + + def color(self, text, color, bold=False): + if not self.active: + return text + return ( + f"\033[{30 + self.colors.index(color)}{';1' if bold else ''}m{text}\033[0m" + ) + + def __getattr__(self, attr): + if attr in self.colors: + return lambda text, bold=False: self.color(text, attr, bold) + return super().__getattr__(attr) + + +def maybe_pluralize(n, singular, plural=None): + return f"{n} {singular if n == 1 else (plural or singular + 's')}" + + +module_re = re.compile(r"odoo\.addons\.(\w+)") + + +def get_module(test): + if hasattr(test, "test_module"): + return test.test_module + for key in ("description", "test_description", "test_id"): + if hasattr(test, key): + match = module_re.search(getattr(test, key)) + if match: + return match.group(1) + return "unknown (%s)" % repr(test) + + +def get_test_name(test): + if hasattr(test, "_testMethodName"): + name = "" + if hasattr(test, "test_class"): + name += test.test_class + "." + name += test._testMethodName + return name + for key in ("description", "test_description", "test_id"): + if getattr(test, key, False): + return getattr(test, key) + return "unknown (%s)" % repr(test) + + +def preload_registries_and_display_test_results(dbnames): + _logger = logging.getLogger(__name__) + rc = preload_registries(dbnames) + c = Colorize(_logger) + types = { + "errors": "error", + "failures": "failure", + "skipped": "skip", + "expectedFailures": "expected failure", + "unexpectedSuccesses": "unexpected success", + } + colors = { + "errors": "red", + "failures": "red", + "skipped": "yellow", + "expectedFailures": "green", + "unexpectedSuccesses": "cyan", + } + for db in dbnames: + report = Registry.registries[db]._assertion_report + + modules_infos = defaultdict(list) + for type_ in types: + for test_info in getattr(report, type_): + test, info = ( + test_info + if isinstance(test_info, tuple) and len(test_info) == 2 + else (test_info, "Success") + ) + modules_infos[get_module(test)].append( + {"type": type_, "info": info, "test": test} + ) + + message = "\n\n" + c.bold(f"Database {db}: ") + f"{report.testsRun} tests run" + for type_, type_name in types.items(): + if getattr(report, type_): + message += ", " + c.color( + maybe_pluralize(len(getattr(report, type_)), type_name), + colors[type_], + True, + ) + + if len(modules_infos): + message += f" in {maybe_pluralize(len(modules_infos), 'module')}." + + message += "\n\n" + + for module, infos in modules_infos.items(): + message += c.black("+" + "-" * (len(module) + 2) + "+", True) + "\n" + message += c.black("| ", True) + c.bold(module) + c.black(" |", True) + "\n" + message += c.black("+" + "-" * (len(module) + 2) + "+", True) + "\n" + for type_, type_name in types.items(): + type_infos = [info for info in infos if info["type"] == type_] + if type_infos: + message += ( + c.color( + maybe_pluralize(len(type_infos), type_name) + ":", + colors[type_], + True, + ) + + "\n" + ) + for info in type_infos: + message += ( + c.black( + " - " + + ( + (info["test"].__module__.split(".")[-1] + ":") + if getattr(info["test"], "__module__", False) + else "" + ), + True, + ) + + c.bold(get_test_name(info["test"]) + ":") + + "\n" + ) + message += info["info"].rstrip("\n") + "\n\n" + + getattr(_logger, "error" if report.errors or report.failures else "info")( + message + ) + + return rc + + +if config["test_enable"]: + patch( + "odoo.service.server.preload_registries", + preload_registries_and_display_test_results, + ).start() diff --git a/tests_summary/__manifest__.py b/tests_summary/__manifest__.py new file mode 100644 index 00000000000..c7cc18930d1 --- /dev/null +++ b/tests_summary/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Test Summary", + "version": "14.0.1.0.0", + "author": "Akretion,Odoo Community Association (OCA)", + "summary": "Odoo patch to display a summary of failed tests at the end " + "of odoo startup", + "website": "https://github.com/OCA/server-tools", + "maintainers": ["paradoxxxzero"], + "license": "AGPL-3", + "category": "Generic Modules", + "depends": [], + "data": [], +} diff --git a/tests_summary/readme/CONTRIBUTORS.md b/tests_summary/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..328a37da87c --- /dev/null +++ b/tests_summary/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Florian Mounier diff --git a/tests_summary/readme/DESCRIPTION.md b/tests_summary/readme/DESCRIPTION.md new file mode 100644 index 00000000000..239274b39c4 --- /dev/null +++ b/tests_summary/readme/DESCRIPTION.md @@ -0,0 +1,9 @@ +Add this module to your `server_wide_modules` list in your odoo.conf file to enable the +tests summary feature. + +At the end of the tests, a summary will be displayed in the logs for quick review of the +test failures grouped by module. + +The summary will look like this: + +![Tests Summary Tests Summary](../static/description/tests_summary.png) diff --git a/tests_summary/static/description/index.html b/tests_summary/static/description/index.html new file mode 100644 index 00000000000..80006641c27 --- /dev/null +++ b/tests_summary/static/description/index.html @@ -0,0 +1,430 @@ + + + + + +Test Summary + + + +
+

Test Summary

+ + +

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

+

Add this module to your server_wide_modules list in your odoo.conf +file to enable the tests summary feature.

+

At the end of the tests, a summary will be displayed in the logs for +quick review of the test failures grouped by module.

+

The summary will look like this:

+

Tests Summary Tests Summary

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

paradoxxxzero

+

This module is part of the OCA/server-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/tests_summary/static/description/tests_summary.png b/tests_summary/static/description/tests_summary.png new file mode 100644 index 00000000000..1c0cd5b993d Binary files /dev/null and b/tests_summary/static/description/tests_summary.png differ diff --git a/tests_summary/tests/__init__.py b/tests_summary/tests/__init__.py new file mode 100644 index 00000000000..31880f0f705 --- /dev/null +++ b/tests_summary/tests/__init__.py @@ -0,0 +1 @@ +from . import test_tests_summary diff --git a/tests_summary/tests/test_tests_summary.py b/tests_summary/tests/test_tests_summary.py new file mode 100644 index 00000000000..f5e04b5889f --- /dev/null +++ b/tests_summary/tests/test_tests_summary.py @@ -0,0 +1,30 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import os +from unittest import expectedFailure + +from odoo.tests.common import SavepointCase + +if os.getenv("RUN_TESTS_SUMMARY_TESTS"): + + class TestTestsSummary(SavepointCase): + def test_tests_summary_ok(self): + self.assertTrue(True) + + def test_tests_summary_fail(self): + self.assertTrue(False) + + def test_tests_summary_error(self): + raise Exception("Error") + + def test_tests_summary_skip(self): + self.skipTest("Skip") + + @expectedFailure + def test_tests_summary_expected_failure(self): + raise Exception("XFail") + + @expectedFailure + def test_tests_summary_unexpected_success(self): + self.assertTrue(True)