summaryrefslogtreecommitdiffstats
path: root/anta/reporter/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'anta/reporter/__init__.py')
-rw-r--r--anta/reporter/__init__.py195
1 files changed, 100 insertions, 95 deletions
diff --git a/anta/reporter/__init__.py b/anta/reporter/__init__.py
index dda9d9c..3e068f5 100644
--- a/anta/reporter/__init__.py
+++ b/anta/reporter/__init__.py
@@ -1,23 +1,25 @@
# Copyright (c) 2023-2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
-"""
-Report management for ANTA.
-"""
+"""Report management for ANTA."""
+
# pylint: disable = too-few-public-methods
from __future__ import annotations
import logging
-import os.path
-import pathlib
-from typing import Any, Optional
+from typing import TYPE_CHECKING, Any
from jinja2 import Template
from rich.table import Table
from anta import RICH_COLOR_PALETTE, RICH_COLOR_THEME
-from anta.custom_types import TestStatus
-from anta.result_manager import ResultManager
+
+if TYPE_CHECKING:
+ import pathlib
+
+ from anta.custom_types import TestStatus
+ from anta.result_manager import ResultManager
+ from anta.result_manager.models import TestResult
logger = logging.getLogger(__name__)
@@ -25,33 +27,37 @@ logger = logging.getLogger(__name__)
class ReportTable:
"""TableReport Generate a Table based on TestResult."""
- def _split_list_to_txt_list(self, usr_list: list[str], delimiter: Optional[str] = None) -> str:
- """
- Split list to multi-lines string
+ def _split_list_to_txt_list(self, usr_list: list[str], delimiter: str | None = None) -> str:
+ """Split list to multi-lines string.
Args:
+ ----
usr_list (list[str]): List of string to concatenate
delimiter (str, optional): A delimiter to use to start string. Defaults to None.
- Returns:
+ Returns
+ -------
str: Multi-lines string
+
"""
if delimiter is not None:
return "\n".join(f"{delimiter} {line}" for line in usr_list)
return "\n".join(f"{line}" for line in usr_list)
def _build_headers(self, headers: list[str], table: Table) -> Table:
- """
- Create headers for a table.
+ """Create headers for a table.
First key is considered as header and is colored using RICH_COLOR_PALETTE.HEADER
Args:
- headers (list[str]): List of headers
- table (Table): A rich Table instance
+ ----
+ headers: List of headers.
+ table: A rich Table instance.
+
+ Returns
+ -------
+ A rich `Table` instance with headers.
- Returns:
- Table: A rich Table instance with headers
"""
for idx, header in enumerate(headers):
if idx == 0:
@@ -64,72 +70,69 @@ class ReportTable:
return table
def _color_result(self, status: TestStatus) -> str:
- """
- Return a colored string based on the status value.
+ """Return a colored string based on the status value.
Args:
- status (TestStatus): status value to color
+ ----
+ status (TestStatus): status value to color.
- Returns:
+ Returns
+ -------
str: the colored string
+
"""
color = RICH_COLOR_THEME.get(status, "")
return f"[{color}]{status}" if color != "" else str(status)
- def report_all(
- self,
- result_manager: ResultManager,
- host: Optional[str] = None,
- testcase: Optional[str] = None,
- title: str = "All tests results",
- ) -> Table:
- """
- Create a table report with all tests for one or all devices.
+ def report_all(self, manager: ResultManager, title: str = "All tests results") -> Table:
+ """Create a table report with all tests for one or all devices.
Create table with full output: Host / Test / Status / Message
Args:
- result_manager (ResultManager): A manager with a list of tests.
- host (str, optional): IP Address of a host to search for. Defaults to None.
- testcase (str, optional): A test name to search for. Defaults to None.
- title (str, optional): Title for the report. Defaults to 'All tests results'.
+ ----
+ manager: A ResultManager instance.
+ title: Title for the report. Defaults to 'All tests results'.
+
+ Returns
+ -------
+ A fully populated rich `Table`
- Returns:
- Table: A fully populated rich Table
"""
table = Table(title=title, show_lines=True)
headers = ["Device", "Test Name", "Test Status", "Message(s)", "Test description", "Test category"]
table = self._build_headers(headers=headers, table=table)
- for result in result_manager.get_results():
- # pylint: disable=R0916
- if (host is None and testcase is None) or (host is not None and str(result.name) == host) or (testcase is not None and testcase == str(result.test)):
- state = self._color_result(result.result)
- message = self._split_list_to_txt_list(result.messages) if len(result.messages) > 0 else ""
- categories = ", ".join(result.categories)
- table.add_row(str(result.name), result.test, state, message, result.description, categories)
+ def add_line(result: TestResult) -> None:
+ state = self._color_result(result.result)
+ message = self._split_list_to_txt_list(result.messages) if len(result.messages) > 0 else ""
+ categories = ", ".join(result.categories)
+ table.add_row(str(result.name), result.test, state, message, result.description, categories)
+
+ for result in manager.results:
+ add_line(result)
return table
def report_summary_tests(
self,
- result_manager: ResultManager,
- testcase: Optional[str] = None,
- title: str = "Summary per test case",
+ manager: ResultManager,
+ tests: list[str] | None = None,
+ title: str = "Summary per test",
) -> Table:
- """
- Create a table report with result agregated per test.
+ """Create a table report with result aggregated per test.
- Create table with full output: Test / Number of success / Number of failure / Number of error / List of nodes in error or failure
+ Create table with full output: Test | Number of success | Number of failure | Number of error | List of nodes in error or failure
Args:
- result_manager (ResultManager): A manager with a list of tests.
- testcase (str, optional): A test name to search for. Defaults to None.
- title (str, optional): Title for the report. Defaults to 'All tests results'.
-
- Returns:
- Table: A fully populated rich Table
+ ----
+ manager: A ResultManager instance.
+ tests: List of test names to include. None to select all tests.
+ title: Title of the report.
+
+ Returns
+ -------
+ A fully populated rich `Table`.
"""
- # sourcery skip: class-extract-method
table = Table(title=title, show_lines=True)
headers = [
"Test Case",
@@ -140,16 +143,16 @@ class ReportTable:
"List of failed or error nodes",
]
table = self._build_headers(headers=headers, table=table)
- for testcase_read in result_manager.get_testcases():
- if testcase is None or str(testcase_read) == testcase:
- results = result_manager.get_result_by_test(testcase_read)
+ for test in manager.get_tests():
+ if tests is None or test in tests:
+ results = manager.filter_by_tests({test}).results
nb_failure = len([result for result in results if result.result == "failure"])
nb_error = len([result for result in results if result.result == "error"])
- list_failure = [str(result.name) for result in results if result.result in ["failure", "error"]]
+ list_failure = [result.name for result in results if result.result in ["failure", "error"]]
nb_success = len([result for result in results if result.result == "success"])
nb_skipped = len([result for result in results if result.result == "skipped"])
table.add_row(
- testcase_read,
+ test,
str(nb_success),
str(nb_skipped),
str(nb_failure),
@@ -158,24 +161,25 @@ class ReportTable:
)
return table
- def report_summary_hosts(
+ def report_summary_devices(
self,
- result_manager: ResultManager,
- host: Optional[str] = None,
- title: str = "Summary per host",
+ manager: ResultManager,
+ devices: list[str] | None = None,
+ title: str = "Summary per device",
) -> Table:
- """
- Create a table report with result agregated per host.
+ """Create a table report with result aggregated per device.
- Create table with full output: Host / Number of success / Number of failure / Number of error / List of nodes in error or failure
+ Create table with full output: Host | Number of success | Number of failure | Number of error | List of nodes in error or failure
Args:
- result_manager (ResultManager): A manager with a list of tests.
- host (str, optional): IP Address of a host to search for. Defaults to None.
- title (str, optional): Title for the report. Defaults to 'All tests results'.
-
- Returns:
- Table: A fully populated rich Table
+ ----
+ manager: A ResultManager instance.
+ devices: List of device names to include. None to select all devices.
+ title: Title of the report.
+
+ Returns
+ -------
+ A fully populated rich `Table`.
"""
table = Table(title=title, show_lines=True)
headers = [
@@ -187,18 +191,16 @@ class ReportTable:
"List of failed or error test cases",
]
table = self._build_headers(headers=headers, table=table)
- for host_read in result_manager.get_hosts():
- if host is None or str(host_read) == host:
- results = result_manager.get_result_by_host(host_read)
- logger.debug("data to use for computation")
- logger.debug(f"{host}: {results}")
+ for device in manager.get_devices():
+ if devices is None or device in devices:
+ results = manager.filter_by_devices({device}).results
nb_failure = len([result for result in results if result.result == "failure"])
nb_error = len([result for result in results if result.result == "error"])
- list_failure = [str(result.test) for result in results if result.result in ["failure", "error"]]
+ list_failure = [result.test for result in results if result.result in ["failure", "error"]]
nb_success = len([result for result in results if result.result == "success"])
nb_skipped = len([result for result in results if result.result == "skipped"])
table.add_row(
- str(host_read),
+ device,
str(nb_success),
str(nb_skipped),
str(nb_failure),
@@ -212,20 +214,20 @@ class ReportJinja:
"""Report builder based on a Jinja2 template."""
def __init__(self, template_path: pathlib.Path) -> None:
- if os.path.isfile(template_path):
+ """Create a ReportJinja instance."""
+ if template_path.is_file():
self.tempalte_path = template_path
else:
- raise FileNotFoundError(f"template file is not found: {template_path}")
+ msg = f"template file is not found: {template_path}"
+ raise FileNotFoundError(msg)
- def render(self, data: list[dict[str, Any]], trim_blocks: bool = True, lstrip_blocks: bool = True) -> str:
- """
- Build a report based on a Jinja2 template
+ def render(self, data: list[dict[str, Any]], *, trim_blocks: bool = True, lstrip_blocks: bool = True) -> str:
+ """Build a report based on a Jinja2 template.
Report is built based on a J2 template provided by user.
Data structure sent to template is:
- >>> data = ResultManager.get_json_results()
- >>> print(data)
+ >>> print(ResultManager.json)
[
{
name: ...,
@@ -238,14 +240,17 @@ class ReportJinja:
]
Args:
- data (list[dict[str, Any]]): List of results from ResultManager.get_results
- trim_blocks (bool, optional): enable trim_blocks for J2 rendering. Defaults to True.
- lstrip_blocks (bool, optional): enable lstrip_blocks for J2 rendering. Defaults to True.
+ ----
+ data: List of results from ResultManager.results
+ trim_blocks: enable trim_blocks for J2 rendering.
+ lstrip_blocks: enable lstrip_blocks for J2 rendering.
+
+ Returns
+ -------
+ Rendered template
- Returns:
- str: rendered template
"""
- with open(self.tempalte_path, encoding="utf-8") as file_:
+ with self.tempalte_path.open(encoding="utf-8") as file_:
template = Template(file_.read(), trim_blocks=trim_blocks, lstrip_blocks=lstrip_blocks)
return template.render({"data": data})