From bb17d116c9f2003409c54bbf9c0267d7693f9e89 Mon Sep 17 00:00:00 2001 From: SmartDever02 Date: Mon, 3 Nov 2025 17:41:55 +0100 Subject: [PATCH 1/3] Support for Custom Test Report Formats --- src/tox/config/cli/parser.py | 2 +- src/tox/journal/__init__.py | 48 +++++++++- src/tox/plugin/manager.py | 20 ++++ src/tox/plugin/spec.py | 11 +++ src/tox/report/__init__.py | 4 + src/tox/report/config.py | 32 +++++++ src/tox/report/formatter.py | 81 ++++++++++++++++ src/tox/report/formatters/__init__.py | 12 +++ src/tox/report/formatters/json.py | 37 ++++++++ src/tox/report/formatters/xml.py | 128 ++++++++++++++++++++++++++ src/tox/session/cmd/run/common.py | 2 +- 11 files changed, 370 insertions(+), 7 deletions(-) create mode 100644 src/tox/report/__init__.py create mode 100644 src/tox/report/config.py create mode 100644 src/tox/report/formatter.py create mode 100644 src/tox/report/formatters/__init__.py create mode 100644 src/tox/report/formatters/json.py create mode 100644 src/tox/report/formatters/xml.py diff --git a/src/tox/config/cli/parser.py b/src/tox/config/cli/parser.py index e739c27355..8c387c752e 100644 --- a/src/tox/config/cli/parser.py +++ b/src/tox/config/cli/parser.py @@ -200,7 +200,7 @@ def _add_provision_arguments(self, sub_parser: ToxParser) -> None: # noqa: PLR6 metavar="path", of_type=Path, default=None, - help="write a JSON file with detailed information about all commands and results involved", + help="write a test report file with detailed information about all commands and results involved (format determined by report_format config or defaults to JSON)", ) class SeedAction(Action): diff --git a/src/tox/journal/__init__.py b/src/tox/journal/__init__.py index 4f054afd67..67a794ac39 100644 --- a/src/tox/journal/__init__.py +++ b/src/tox/journal/__init__.py @@ -1,20 +1,58 @@ -"""This module handles collecting and persisting in json format a tox session.""" +"""This module handles collecting and persisting test reports in various formats.""" from __future__ import annotations -import json import locale from pathlib import Path +from typing import TYPE_CHECKING from .env import EnvJournal from .main import Journal +if TYPE_CHECKING: + from tox.config.main import Config -def write_journal(path: Path | None, journal: Journal) -> None: + +def write_journal(path: Path | None, journal: Journal, config: Config | None = None) -> None: + """ + Write journal to file using the configured format. + + :param path: path to write the report to + :param journal: the journal containing test results + :param config: optional config to determine format (if None, uses JSON default) + """ if path is None: return - with Path(path).open("w", encoding=locale.getpreferredencoding(do_setlocale=False)) as file_handler: - json.dump(journal.content, file_handler, indent=2, ensure_ascii=False) + + # Determine format from config or default to JSON + report_format: str | None = None + if config is not None: + try: + report_format = config.core["report_format"] + except KeyError: + report_format = None + + # If no format specified, default to JSON (backward compatibility) + if report_format is None: + report_format = "json" + + # Get formatter from registry + from tox.report.formatter import REGISTER # noqa: PLC0415 + + formatter = REGISTER.get(report_format) + if formatter is None: + # Fallback to JSON if format not found + from tox.report.formatters import JsonFormatter # noqa: PLC0415 + + formatter = JsonFormatter() + + # Ensure output path has correct extension if it doesn't match formatter + output_path = Path(path) + if not output_path.suffix or output_path.suffix != formatter.file_extension: + output_path = output_path.with_suffix(formatter.file_extension) + + # Format and write + formatter.format(journal, output_path) __all__ = ( diff --git a/src/tox/plugin/manager.py b/src/tox/plugin/manager.py index 323d5ae413..5905119db3 100644 --- a/src/tox/plugin/manager.py +++ b/src/tox/plugin/manager.py @@ -26,6 +26,7 @@ from tox.config.cli.parser import ToxParser from tox.config.sets import ConfigSet, EnvConfigSet from tox.execute import Outcome + from tox.report.formatter import ReportFormatterRegister from tox.session.state import State from tox.tox_env.api import ToxEnv @@ -52,6 +53,8 @@ def _register_plugins(self, inline: ModuleType | None) -> None: if inline is not None: self.manager.register(inline) self._load_external_plugins() + from tox.report import config as report_config # noqa: PLC0415 + internal_plugins = ( loader_api, provision, @@ -70,6 +73,7 @@ def _register_plugins(self, inline: ModuleType | None) -> None: parallel, sequential, package_api, + report_config, ) for plugin in internal_plugins: self.manager.register(plugin) @@ -111,12 +115,28 @@ def tox_on_install(self, tox_env: ToxEnv, arguments: Any, section: str, of_type: def tox_env_teardown(self, tox_env: ToxEnv) -> None: self.manager.hook.tox_env_teardown(tox_env=tox_env) + def tox_register_report_formatter(self, register: ReportFormatterRegister) -> None: + self.manager.hook.tox_register_report_formatter(register=register) + + def _register_builtin_report_formatters(self) -> None: + """Register built-in report formatters.""" + from tox.report.formatter import REGISTER # noqa: PLC0415 + from tox.report.formatters import JsonFormatter, XmlFormatter # noqa: PLC0415 + + # Register built-in formatters + REGISTER.register(JsonFormatter()) + REGISTER.register(XmlFormatter()) + + # Allow plugins to register additional formatters + self.manager.hook.tox_register_report_formatter(register=REGISTER) + def load_plugins(self, path: Path) -> None: for _plugin in self.manager.get_plugins(): # make sure we start with a clean state, repeated in memory run self.manager.unregister(_plugin) inline = _load_inline(path) self._register_plugins(inline) REGISTER._register_tox_env_types(self) # noqa: SLF001 + self._register_builtin_report_formatters() def _load_inline(path: Path) -> ModuleType | None: # used to be able to unregister plugin tests diff --git a/src/tox/plugin/spec.py b/src/tox/plugin/spec.py index 3570033924..2f8269bd25 100644 --- a/src/tox/plugin/spec.py +++ b/src/tox/plugin/spec.py @@ -12,6 +12,7 @@ from tox.config.cli.parser import ToxParser from tox.config.sets import ConfigSet, EnvConfigSet from tox.execute import Outcome + from tox.report.formatter import ReportFormatterRegister from tox.session.state import State from tox.tox_env.api import ToxEnv from tox.tox_env.register import ToxEnvRegister @@ -122,6 +123,15 @@ def tox_env_teardown(tox_env: ToxEnv) -> None: """ +@_spec +def tox_register_report_formatter(register: ReportFormatterRegister) -> None: + """ + Register a custom test report formatter. + + :param register: a object that can be used to register new report formatters + """ + + __all__ = [ "NAME", "tox_add_core_config", @@ -133,4 +143,5 @@ def tox_env_teardown(tox_env: ToxEnv) -> None: "tox_extend_envs", "tox_on_install", "tox_register_tox_env", + "tox_register_report_formatter", ] diff --git a/src/tox/report/__init__.py b/src/tox/report/__init__.py new file mode 100644 index 0000000000..45c6b918ab --- /dev/null +++ b/src/tox/report/__init__.py @@ -0,0 +1,4 @@ +"""Report formatting system for tox.""" + +__all__ = () + diff --git a/src/tox/report/config.py b/src/tox/report/config.py new file mode 100644 index 0000000000..77e13a392d --- /dev/null +++ b/src/tox/report/config.py @@ -0,0 +1,32 @@ +"""Report format configuration.""" + +from __future__ import annotations + +from tox.plugin import impl +from tox.report.formatter import REGISTER + + +@impl +def tox_add_core_config(core_conf, state): # noqa: ARG001 + """Add report_format configuration to core config.""" + core_conf.add_config( + keys=["report_format"], + of_type=str | None, + default=None, + desc="Format for test reports (e.g., 'json', 'xml'). If None, uses default JSON format.", + ) + + +@impl +def tox_add_env_config(env_conf, state): # noqa: ARG001 + """Add report_format configuration to environment config (inherits from core if not set).""" + env_conf.add_config( + keys=["report_format"], + of_type=str | None, + default=lambda conf, env_name: conf.core["report_format"], + desc="Format for test reports for this environment (inherits from core config if not set).", + ) + + +__all__ = () + diff --git a/src/tox/report/formatter.py b/src/tox/report/formatter.py new file mode 100644 index 0000000000..6f4e7c965d --- /dev/null +++ b/src/tox/report/formatter.py @@ -0,0 +1,81 @@ +"""Report formatter interface and registry.""" + +from __future__ import annotations + +import abc +from typing import TYPE_CHECKING, Any + +from tox.journal.main import Journal + +if TYPE_CHECKING: + from pathlib import Path + + +class ReportFormatter(abc.ABC): + """Base class for test report formatters.""" + + @property + @abc.abstractmethod + def name(self) -> str: + """Return the name/identifier of this formatter (e.g., 'xml', 'json').""" + + @property + @abc.abstractmethod + def file_extension(self) -> str: + """Return the file extension for this format (e.g., '.xml', '.json').""" + + @abc.abstractmethod + def format(self, journal: Journal, output_path: Path | None = None) -> str | None: + """ + Format the journal content and optionally write to file. + + :param journal: the journal containing test results + :param output_path: optional path to write the formatted output to + :return: the formatted content as string, or None if written to file + """ + raise NotImplementedError + + +class ReportFormatterRegister: + """Registry for report formatters.""" + + def __init__(self) -> None: + self._formatters: dict[str, ReportFormatter] = {} + + def register(self, formatter: ReportFormatter) -> None: + """ + Register a report formatter. + + :param formatter: the formatter to register + """ + if formatter.name in self._formatters: + msg = f"formatter with name '{formatter.name}' already registered" + raise ValueError(msg) + self._formatters[formatter.name] = formatter + + def get(self, name: str) -> ReportFormatter | None: + """ + Get a formatter by name. + + :param name: the formatter name + :return: the formatter or None if not found + """ + return self._formatters.get(name) + + def list_formatters(self) -> list[str]: + """ + List all registered formatter names. + + :return: list of formatter names + """ + return sorted(self._formatters.keys()) + + +REGISTER = ReportFormatterRegister() + +__all__ = ( + "ReportFormatter", + "ReportFormatterRegister", + "REGISTER", +) + diff --git a/src/tox/report/formatters/__init__.py b/src/tox/report/formatters/__init__.py new file mode 100644 index 0000000000..c5f63ad839 --- /dev/null +++ b/src/tox/report/formatters/__init__.py @@ -0,0 +1,12 @@ +"""Built-in report formatters.""" + +from __future__ import annotations + +from tox.report.formatters.json import JsonFormatter +from tox.report.formatters.xml import XmlFormatter + +__all__ = ( + "JsonFormatter", + "XmlFormatter", +) + diff --git a/src/tox/report/formatters/json.py b/src/tox/report/formatters/json.py new file mode 100644 index 0000000000..cbdd2263a5 --- /dev/null +++ b/src/tox/report/formatters/json.py @@ -0,0 +1,37 @@ +"""JSON report formatter.""" + +from __future__ import annotations + +import json +import locale +from pathlib import Path + +from tox.journal.main import Journal +from tox.report.formatter import ReportFormatter + + +class JsonFormatter(ReportFormatter): + """JSON format report formatter.""" + + @property + def name(self) -> str: + return "json" + + @property + def file_extension(self) -> str: + return ".json" + + def format(self, journal: Journal, output_path: Path | None = None) -> str | None: + content = journal.content + json_content = json.dumps(content, indent=2, ensure_ascii=False) + + if output_path is not None: + with Path(output_path).open("w", encoding=locale.getpreferredencoding(do_setlocale=False)) as file_handler: + file_handler.write(json_content) + return None + + return json_content + + +__all__ = ("JsonFormatter",) + diff --git a/src/tox/report/formatters/xml.py b/src/tox/report/formatters/xml.py new file mode 100644 index 0000000000..63077650be --- /dev/null +++ b/src/tox/report/formatters/xml.py @@ -0,0 +1,128 @@ +"""XML report formatter (JUnit XML style).""" + +from __future__ import annotations + +import locale +from pathlib import Path +from xml.etree import ElementTree + +from tox.journal.main import Journal +from tox.report.formatter import ReportFormatter + + +class XmlFormatter(ReportFormatter): + """JUnit XML format report formatter.""" + + @property + def name(self) -> str: + return "xml" + + @property + def file_extension(self) -> str: + return ".xml" + + def format(self, journal: Journal, output_path: Path | None = None) -> str | None: + content = journal.content + + # Create root testsuites element + testsuites = ElementTree.Element("testsuites") + + # Add metadata + if "toxversion" in content: + testsuites.set("toxversion", str(content["toxversion"])) + if "platform" in content: + testsuites.set("platform", str(content["platform"])) + if "host" in content: + testsuites.set("host", str(content["host"])) + + total_tests = 0 + total_failures = 0 + total_errors = 0 + total_time = 0.0 + + # Process each test environment + testenvs = content.get("testenvs", {}) + for env_name, env_data in testenvs.items(): + testsuite = ElementTree.SubElement(testsuites, "testsuite") + testsuite.set("name", env_name) + + env_tests = 0 + env_failures = 0 + env_errors = 0 + env_time = 0.0 + + # Process test results from journal + test_results = env_data.get("test", []) + setup_results = env_data.get("setup", []) + + # Process setup commands + for setup in setup_results: + testcase = ElementTree.SubElement(testsuite, "testcase") + testcase.set("classname", env_name) + testcase.set("name", f"setup:{setup.get('run_id', 'unknown')}") + elapsed = float(setup.get("elapsed", 0.0)) + testcase.set("time", f"{elapsed:.3f}") + env_time += elapsed + env_tests += 1 + + if setup.get("retcode", 0) != 0: + failure = ElementTree.SubElement(testcase, "failure") + failure.set("message", f"Setup command failed with exit code {setup.get('retcode')}") + failure.text = setup.get("err", "") + env_errors += 1 + + # Process test commands + for test in test_results: + testcase = ElementTree.SubElement(testsuite, "testcase") + testcase.set("classname", env_name) + testcase.set("name", test.get("command", test.get("run_id", "unknown"))) + elapsed = float(test.get("elapsed", 0.0)) + testcase.set("time", f"{elapsed:.3f}") + env_time += elapsed + env_tests += 1 + + retcode = test.get("retcode", 0) + if retcode != 0: + failure = ElementTree.SubElement(testcase, "failure") + failure.set("message", f"Test command failed with exit code {retcode}") + failure.text = test.get("err", test.get("output", "")) + env_failures += 1 + + # Check result from journal + result = env_data.get("result", {}) + if not result.get("success", True) and env_failures == 0: + # If marked as failed but no failures tracked, add error + env_errors += 1 + + testsuite.set("tests", str(env_tests)) + testsuite.set("failures", str(env_failures)) + testsuite.set("errors", str(env_errors)) + testsuite.set("time", f"{env_time:.3f}") + + total_tests += env_tests + total_failures += env_failures + total_errors += env_errors + total_time += env_time + + testsuites.set("tests", str(total_tests)) + testsuites.set("failures", str(total_failures)) + testsuites.set("errors", str(total_errors)) + testsuites.set("time", f"{total_time:.3f}") + + # Convert to XML string + try: + ElementTree.indent(testsuites, space=" ") # Python 3.9+ + except AttributeError: + pass # ElementTree.indent not available in older Python versions + xml_content = ElementTree.tostring(testsuites, encoding="unicode", xml_declaration=True) + + if output_path is not None: + with Path(output_path).open("w", encoding=locale.getpreferredencoding(do_setlocale=False)) as file_handler: + file_handler.write(xml_content) + return None + + return xml_content + + +__all__ = ("XmlFormatter",) + diff --git a/src/tox/session/cmd/run/common.py b/src/tox/session/cmd/run/common.py index 29c9777346..e52513ccbd 100644 --- a/src/tox/session/cmd/run/common.py +++ b/src/tox/session/cmd/run/common.py @@ -204,7 +204,7 @@ def execute(state: State, max_workers: int | None, has_spinner: bool, live: bool name_to_run = {r.name: r for r in results} ordered_results: list[ToxEnvRunResult] = [name_to_run[env] for env in to_run_list] # write the journal - write_journal(getattr(state.conf.options, "result_json", None), state._journal) # noqa: SLF001 + write_journal(getattr(state.conf.options, "result_json", None), state._journal, state.conf) # noqa: SLF001 # report the outcome exit_code = report( state.conf.options.start, From 768850db91cd37d7ef3da906957c6ca0d0327e7d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:45:03 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/tox/journal/__init__.py | 1 - src/tox/plugin/spec.py | 2 +- src/tox/report/__init__.py | 3 ++- src/tox/report/config.py | 6 ++---- src/tox/report/formatter.py | 9 ++++----- src/tox/report/formatters/__init__.py | 1 - src/tox/report/formatters/json.py | 6 ++++-- src/tox/report/formatters/xml.py | 24 +++++++++++++----------- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/tox/journal/__init__.py b/src/tox/journal/__init__.py index 67a794ac39..a237017123 100644 --- a/src/tox/journal/__init__.py +++ b/src/tox/journal/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations -import locale from pathlib import Path from typing import TYPE_CHECKING diff --git a/src/tox/plugin/spec.py b/src/tox/plugin/spec.py index 2f8269bd25..850699047d 100644 --- a/src/tox/plugin/spec.py +++ b/src/tox/plugin/spec.py @@ -142,6 +142,6 @@ def tox_register_report_formatter(register: ReportFormatterRegister) -> None: "tox_env_teardown", "tox_extend_envs", "tox_on_install", - "tox_register_tox_env", "tox_register_report_formatter", + "tox_register_tox_env", ] diff --git a/src/tox/report/__init__.py b/src/tox/report/__init__.py index 45c6b918ab..fc7fc78720 100644 --- a/src/tox/report/__init__.py +++ b/src/tox/report/__init__.py @@ -1,4 +1,5 @@ """Report formatting system for tox.""" -__all__ = () +from __future__ import annotations +__all__ = () diff --git a/src/tox/report/config.py b/src/tox/report/config.py index 77e13a392d..e15a32a3ea 100644 --- a/src/tox/report/config.py +++ b/src/tox/report/config.py @@ -3,11 +3,10 @@ from __future__ import annotations from tox.plugin import impl -from tox.report.formatter import REGISTER @impl -def tox_add_core_config(core_conf, state): # noqa: ARG001 +def tox_add_core_config(core_conf, state) -> None: # noqa: ARG001 """Add report_format configuration to core config.""" core_conf.add_config( keys=["report_format"], @@ -18,7 +17,7 @@ def tox_add_core_config(core_conf, state): # noqa: ARG001 @impl -def tox_add_env_config(env_conf, state): # noqa: ARG001 +def tox_add_env_config(env_conf, state) -> None: # noqa: ARG001 """Add report_format configuration to environment config (inherits from core if not set).""" env_conf.add_config( keys=["report_format"], @@ -29,4 +28,3 @@ def tox_add_env_config(env_conf, state): # noqa: ARG001 __all__ = () - diff --git a/src/tox/report/formatter.py b/src/tox/report/formatter.py index 6f4e7c965d..a34a5021df 100644 --- a/src/tox/report/formatter.py +++ b/src/tox/report/formatter.py @@ -3,13 +3,13 @@ from __future__ import annotations import abc -from typing import TYPE_CHECKING, Any - -from tox.journal.main import Journal +from typing import TYPE_CHECKING if TYPE_CHECKING: from pathlib import Path + from tox.journal.main import Journal + class ReportFormatter(abc.ABC): """Base class for test report formatters.""" @@ -74,8 +74,7 @@ def list_formatters(self) -> list[str]: REGISTER = ReportFormatterRegister() __all__ = ( + "REGISTER", "ReportFormatter", "ReportFormatterRegister", - "REGISTER", ) - diff --git a/src/tox/report/formatters/__init__.py b/src/tox/report/formatters/__init__.py index c5f63ad839..e3c867aa8b 100644 --- a/src/tox/report/formatters/__init__.py +++ b/src/tox/report/formatters/__init__.py @@ -9,4 +9,3 @@ "JsonFormatter", "XmlFormatter", ) - diff --git a/src/tox/report/formatters/json.py b/src/tox/report/formatters/json.py index cbdd2263a5..5431eac60e 100644 --- a/src/tox/report/formatters/json.py +++ b/src/tox/report/formatters/json.py @@ -5,10 +5,13 @@ import json import locale from pathlib import Path +from typing import TYPE_CHECKING -from tox.journal.main import Journal from tox.report.formatter import ReportFormatter +if TYPE_CHECKING: + from tox.journal.main import Journal + class JsonFormatter(ReportFormatter): """JSON format report formatter.""" @@ -34,4 +37,3 @@ def format(self, journal: Journal, output_path: Path | None = None) -> str | Non __all__ = ("JsonFormatter",) - diff --git a/src/tox/report/formatters/xml.py b/src/tox/report/formatters/xml.py index 63077650be..138beed46e 100644 --- a/src/tox/report/formatters/xml.py +++ b/src/tox/report/formatters/xml.py @@ -4,11 +4,14 @@ import locale from pathlib import Path -from xml.etree import ElementTree +from typing import TYPE_CHECKING +from xml.etree import ElementTree as ET -from tox.journal.main import Journal from tox.report.formatter import ReportFormatter +if TYPE_CHECKING: + from tox.journal.main import Journal + class XmlFormatter(ReportFormatter): """JUnit XML format report formatter.""" @@ -25,7 +28,7 @@ def format(self, journal: Journal, output_path: Path | None = None) -> str | Non content = journal.content # Create root testsuites element - testsuites = ElementTree.Element("testsuites") + testsuites = ET.Element("testsuites") # Add metadata if "toxversion" in content: @@ -43,7 +46,7 @@ def format(self, journal: Journal, output_path: Path | None = None) -> str | Non # Process each test environment testenvs = content.get("testenvs", {}) for env_name, env_data in testenvs.items(): - testsuite = ElementTree.SubElement(testsuites, "testsuite") + testsuite = ET.SubElement(testsuites, "testsuite") testsuite.set("name", env_name) env_tests = 0 @@ -57,7 +60,7 @@ def format(self, journal: Journal, output_path: Path | None = None) -> str | Non # Process setup commands for setup in setup_results: - testcase = ElementTree.SubElement(testsuite, "testcase") + testcase = ET.SubElement(testsuite, "testcase") testcase.set("classname", env_name) testcase.set("name", f"setup:{setup.get('run_id', 'unknown')}") elapsed = float(setup.get("elapsed", 0.0)) @@ -66,14 +69,14 @@ def format(self, journal: Journal, output_path: Path | None = None) -> str | Non env_tests += 1 if setup.get("retcode", 0) != 0: - failure = ElementTree.SubElement(testcase, "failure") + failure = ET.SubElement(testcase, "failure") failure.set("message", f"Setup command failed with exit code {setup.get('retcode')}") failure.text = setup.get("err", "") env_errors += 1 # Process test commands for test in test_results: - testcase = ElementTree.SubElement(testsuite, "testcase") + testcase = ET.SubElement(testsuite, "testcase") testcase.set("classname", env_name) testcase.set("name", test.get("command", test.get("run_id", "unknown"))) elapsed = float(test.get("elapsed", 0.0)) @@ -83,7 +86,7 @@ def format(self, journal: Journal, output_path: Path | None = None) -> str | Non retcode = test.get("retcode", 0) if retcode != 0: - failure = ElementTree.SubElement(testcase, "failure") + failure = ET.SubElement(testcase, "failure") failure.set("message", f"Test command failed with exit code {retcode}") failure.text = test.get("err", test.get("output", "")) env_failures += 1 @@ -111,10 +114,10 @@ def format(self, journal: Journal, output_path: Path | None = None) -> str | Non # Convert to XML string try: - ElementTree.indent(testsuites, space=" ") # Python 3.9+ + ET.indent(testsuites, space=" ") # Python 3.9+ except AttributeError: pass # ElementTree.indent not available in older Python versions - xml_content = ElementTree.tostring(testsuites, encoding="unicode", xml_declaration=True) + xml_content = ET.tostring(testsuites, encoding="unicode", xml_declaration=True) if output_path is not None: with Path(output_path).open("w", encoding=locale.getpreferredencoding(do_setlocale=False)) as file_handler: @@ -125,4 +128,3 @@ def format(self, journal: Journal, output_path: Path | None = None) -> str | Non __all__ = ("XmlFormatter",) - From 5a230776048b90dc9db2bdc1803a3cd224bd1bad Mon Sep 17 00:00:00 2001 From: SmartDever02 Date: Mon, 3 Nov 2025 17:49:15 +0100 Subject: [PATCH 3/3] Fixed review issues --- src/tox/config/cli/parser.py | 5 ++++- src/tox/report/config.py | 12 +++++++++--- src/tox/report/formatters/json.py | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/tox/config/cli/parser.py b/src/tox/config/cli/parser.py index 8c387c752e..fc17706f66 100644 --- a/src/tox/config/cli/parser.py +++ b/src/tox/config/cli/parser.py @@ -200,7 +200,10 @@ def _add_provision_arguments(self, sub_parser: ToxParser) -> None: # noqa: PLR6 metavar="path", of_type=Path, default=None, - help="write a test report file with detailed information about all commands and results involved (format determined by report_format config or defaults to JSON)", + help=( + "write a test report file with detailed information about all commands and results involved " + "(format determined by report_format config or defaults to JSON)" + ), ) class SeedAction(Action): diff --git a/src/tox/report/config.py b/src/tox/report/config.py index e15a32a3ea..e58c46c917 100644 --- a/src/tox/report/config.py +++ b/src/tox/report/config.py @@ -2,11 +2,17 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from tox.plugin import impl +if TYPE_CHECKING: + from tox.config.sets import ConfigSet, EnvConfigSet + from tox.session.state import State + @impl -def tox_add_core_config(core_conf, state) -> None: # noqa: ARG001 +def tox_add_core_config(core_conf: ConfigSet, state: State) -> None: # noqa: ARG001 """Add report_format configuration to core config.""" core_conf.add_config( keys=["report_format"], @@ -17,12 +23,12 @@ def tox_add_core_config(core_conf, state) -> None: # noqa: ARG001 @impl -def tox_add_env_config(env_conf, state) -> None: # noqa: ARG001 +def tox_add_env_config(env_conf: EnvConfigSet, state: State) -> None: # noqa: ARG001 """Add report_format configuration to environment config (inherits from core if not set).""" env_conf.add_config( keys=["report_format"], of_type=str | None, - default=lambda conf, env_name: conf.core["report_format"], + default=lambda conf, _env_name: conf.core["report_format"], desc="Format for test reports for this environment (inherits from core config if not set).", ) diff --git a/src/tox/report/formatters/json.py b/src/tox/report/formatters/json.py index 5431eac60e..b8c7122ca6 100644 --- a/src/tox/report/formatters/json.py +++ b/src/tox/report/formatters/json.py @@ -24,7 +24,7 @@ def name(self) -> str: def file_extension(self) -> str: return ".json" - def format(self, journal: Journal, output_path: Path | None = None) -> str | None: + def format(self, journal: Journal, output_path: Path | None = None) -> str | None: # noqa: PLR6301 content = journal.content json_content = json.dumps(content, indent=2, ensure_ascii=False)