Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 34 additions & 15 deletions framework/python/src/common/testreport.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
from datetime import datetime
from weasyprint import HTML
from io import BytesIO
from common import util
from common import util, logger
from common.statuses import TestrunStatus
import base64
import os
from test_orc.test_case import TestCase
from jinja2 import Environment, FileSystemLoader
from jinja2 import Environment, FileSystemLoader, BaseLoader
from collections import OrderedDict
import re
from bs4 import BeautifulSoup
Expand All @@ -34,6 +34,8 @@
TEST_REPORT_STYLES = 'test_report_styles.css'
TEST_REPORT_TEMPLATE = 'test_report_template.html'

LOGGER = logger.get_logger('REPORT')

# Locate parent directory
current_dir = os.path.dirname(os.path.realpath(__file__))

Expand Down Expand Up @@ -65,12 +67,16 @@ def __init__(self,
self._total_tests = total_tests
self._results = []
self._module_reports = []
self._module_templates = []
self._report_url = ''
self._cur_page = 0

def add_module_reports(self, module_reports):
self._module_reports = module_reports

def add_module_templates(self, module_templates):
self._module_templates = module_templates

def get_status(self):
return self._status

Expand Down Expand Up @@ -243,16 +249,20 @@ def to_html(self):
optional_steps_to_resolve = self._get_optional_steps_to_resolve(json_data)

module_reports = self._get_module_pages()
env_module = Environment(loader=BaseLoader())
pages_num = self._pages_num(json_data)
total_pages = pages_num + len(module_reports) + 1
if len(steps_to_resolve) > 0:
total_pages += 1
if (len(optional_steps_to_resolve) > 0
and json_data['device']['test_pack'] == 'Pilot Assessment'
):
total_pages += 1

return template.render(styles=styles,
module_templates = [
env_module.from_string(s).render(
json_data=json_data,
device=json_data['device'],
logo=logo,
icon_qualification=icon_qualification,
icon_pilot=icon_pilot,
version=self._version,
) for s in self._module_templates
]

return self._add_page_counter(template.render(styles=styles,
logo=logo,
icon_qualification=icon_qualification,
icon_pilot=icon_pilot,
Expand All @@ -269,10 +279,19 @@ def to_html(self):
optional_steps_to_resolve=optional_steps_to_resolve,
module_reports=module_reports,
pages_num=pages_num,
total_pages=total_pages,
tests_first_page=TESTS_FIRST_PAGE,
tests_per_page=TESTS_PER_PAGE,
)
module_templates=module_templates
))

def _add_page_counter(self, html):
# Add page nums and total page
soup = BeautifulSoup(html, features='html5lib')
page_index_divs = soup.find_all('div', class_='page-index')
total_pages = len(page_index_divs)
for index, div in enumerate(page_index_divs):
div.string = f'Page {index+1}/{total_pages}'
return soup.prettify()

def _pages_num(self, json_data):

Expand Down Expand Up @@ -391,8 +410,8 @@ def _get_module_pages(self):
if el.name == 'h1':
current_size += 40 + h1_padding
# Calculating the height of paired tables
elif (el.name == 'div'
and el['style'] == 'display:flex;justify-content:space-between;'):
elif (el.name == 'div' and el.has_attr('id')
and el['id'] == 'paired'):
tables = el.findChildren('table', recursive=True)
current_size = max(
map(lambda t: len(
Expand Down
14 changes: 12 additions & 2 deletions framework/python/src/core/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ def __init__(self, root_dir):
# All historical reports
self._module_reports = []

# Module report templates
self._module_templates = []

# Parameters specified when starting Testrun
self._runtime_params = []

Expand Down Expand Up @@ -398,6 +401,9 @@ def get_test_results(self):
def get_module_reports(self):
return self._module_reports

def get_module_templates(self):
return self._module_templates

def get_report_tests(self):
"""Returns the current test results in JSON-friendly format
(in Python dictionary)"""
Expand Down Expand Up @@ -465,6 +471,9 @@ def set_test_result_error(self, result):
def add_module_report(self, module_report):
self._module_reports.append(module_report)

def add_module_template(self, module_template):
self._module_templates.append(module_template)

def get_all_reports(self):

reports = []
Expand Down Expand Up @@ -658,7 +667,7 @@ def _remove_invalid_questions(self, questions):
valid_questions.append(question)

else:
LOGGER.debug(f'Removed unrecognised question: {question["question"]}')
LOGGER.debug(f'Removed unrecognised question: {question["question"]}') # pylint: disable=W1405
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the pylint disable necessary? We were never notified of a pylint issue before.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw this error when running pylint.
Screenshot from 2024-12-16 12-44-37


# Return the list of valid questions
return valid_questions
Expand Down Expand Up @@ -704,7 +713,7 @@ def validate_profile_json(self, profile_json):
question.get('question'))

if format_q is None:
LOGGER.error(f'Unrecognised question: {question.get("question")}')
LOGGER.error(f'Unrecognised question: {question.get("question")}') # pylint: disable=W1405
# Just ignore additional questions
continue

Expand Down Expand Up @@ -789,6 +798,7 @@ def reset(self):
self._report_url = None
self._total_tests = 0
self._module_reports = []
self._module_templates = []
self._results = []
self._started = None
self._finished = None
Expand Down
12 changes: 11 additions & 1 deletion framework/python/src/test_orc/test_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def run_test_modules(self):
generated_report_json = self._generate_report()
report.from_json(generated_report_json)
report.add_module_reports(self.get_session().get_module_reports())
report.add_module_templates(self.get_session().get_module_templates())
device.add_report(report)

self._write_reports(report)
Expand Down Expand Up @@ -549,8 +550,17 @@ def _run_test_module(self, module):
self.get_session().add_module_report(module_report)
except (FileNotFoundError, PermissionError):
LOGGER.debug("Test module did not produce a html module report")
# Get the Jinja report
jinja_file = f"{module.container_runtime_dir}/{module.name}_report.j2.html"
try:
with open(jinja_file, "r", encoding="utf-8") as f:
module_template = f.read()
LOGGER.debug(f"Adding module template for module {module.name}")
self.get_session().add_module_template(module_template)
except (FileNotFoundError, PermissionError):
LOGGER.debug("Test module did not produce a module template")

LOGGER.info(f"Test module {module.name} has finished")
# LOGGER.info(f"Test module {module.name} has finished")

def _get_container_logs(self, log_stream):
"""Resolve all current log data in the containers log_stream
Expand Down
8 changes: 8 additions & 0 deletions modules/test/base/base.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,13 @@ COPY --from=builder /usr/local/etc/oui.txt /usr/local/etc/oui.txt
# Activate the virtual environment by setting the PATH
ENV PATH="/opt/venv/bin:$PATH"

# Common resource folder
ENV REPORT_TEMPLATE_PATH=/testrun/resources
# Jinja base template
ENV BASE_TEMPLATE_FILE=module_report_base.jinja2

# Copy base template
COPY resources/report/$BASE_TEMPLATE_FILE $REPORT_TEMPLATE_PATH/

# Start the test module
ENTRYPOINT [ "/testrun/bin/start" ]
5 changes: 4 additions & 1 deletion modules/test/base/python/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ protobuf==5.28.0
# User defined packages
grpcio==1.67.1
grpcio-tools==1.67.1
netifaces==0.11.0
netifaces==0.11.0

# Requirements for reports generation
Jinja2==3.1.4
8 changes: 5 additions & 3 deletions modules/test/base/python/src/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ def __init__(self,
self._ipv6_subnet = os.environ.get('IPV6_SUBNET', '')
self._dev_iface_mac = os.environ.get('DEV_IFACE_MAC', '')
self._device_test_pack = json.loads(os.environ.get('DEVICE_TEST_PACK', ''))
self._report_template_folder = os.environ.get('REPORT_TEMPLATE_PATH')
self._base_template_file=os.environ.get('BASE_TEMPLATE_FILE')
self._add_logger(log_name=log_name)
self._config = self._read_config(
conf_file=conf_file if conf_file is not None else CONF_FILE)
Expand Down Expand Up @@ -137,14 +139,14 @@ def run_tests(self):
else:
result = getattr(self, test_method_name)()
except Exception as e: # pylint: disable=W0718
LOGGER.error(f'An error occurred whilst running {test["name"]}')
LOGGER.error(f'An error occurred whilst running {test["name"]}') # pylint: disable=W1405
LOGGER.error(e)
traceback.print_exc()
else:
LOGGER.error(f'Test {test["name"]} has not been implemented')
LOGGER.error(f'Test {test["name"]} has not been implemented') # pylint: disable=W1405
result = TestResult.ERROR, 'This test could not be found'
else:
LOGGER.debug(f'Test {test["name"]} is disabled')
LOGGER.debug(f'Test {test["name"]} is disabled') # pylint: disable=W1405
result = (TestResult.DISABLED,
'This test did not run because it is disabled')

Expand Down
5 changes: 4 additions & 1 deletion modules/test/ntp/ntp.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ COPY $MODULE_DIR/conf /testrun/conf
COPY $MODULE_DIR/bin /testrun/bin

# Copy over all python files
COPY $MODULE_DIR/python /testrun/python
COPY $MODULE_DIR/python /testrun/python

# Copy Jinja template
COPY $MODULE_DIR/resources/report_template.jinja2 $REPORT_TEMPLATE_PATH/
Loading
Loading