diff --git a/cmd/package b/cmd/package
index d134896d3..5f24273ac 100755
--- a/cmd/package
+++ b/cmd/package
@@ -32,6 +32,9 @@ cp cmd/install $MAKE_SRC_DIR/DEBIAN/postinst
mkdir -p $MAKE_SRC_DIR/usr/local/testrun/cmd
cp cmd/{prepare,build} $MAKE_SRC_DIR/usr/local/testrun/cmd
+# Copy resources
+cp -r resources $MAKE_SRC_DIR/usr/local/testrun/
+
# Create local folder
mkdir -p $MAKE_SRC_DIR/usr/local/testrun/local
cp local/system.json.example $MAKE_SRC_DIR/usr/local/testrun/local/system.json.example
diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py
index da26e0700..02c9d65a9 100644
--- a/framework/python/src/common/testreport.py
+++ b/framework/python/src/common/testreport.py
@@ -1,185 +1,559 @@
-# Copyright 2023 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Store previous test run information."""
-
-import os
-from datetime import datetime
-from weasyprint import HTML
-from io import BytesIO
-
-DATE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
-DEVICES_DIR = '/usr/local/testrun/local/devices'
-
-class TestReport():
- """Represents a previous Testrun report."""
-
- def __init__(self,
- status='Non-Compliant',
- started=None,
- finished=None,
- total_tests=0
- ):
- self._device = {}
- self._status: str = status
- self._started = started
- self._finished = finished
- self._total_tests = total_tests
- self._results = []
- self._report = ''
-
- def get_status(self):
- return self._status
-
- def get_started(self):
- return self._started
-
- def get_finished(self):
- return self._finished
-
- def get_duration_seconds(self):
- diff = self._finished - self._started
- return diff.total_seconds()
-
- def get_duration(self):
- return str(datetime.timedelta(seconds=self.get_duration_seconds()))
-
- def add_test(self, test):
- self._results.append(test)
-
- def get_report_url(self):
- return self._report
-
- def to_json(self):
- report_json = {}
- report_json['device'] = self._device
- report_json['status'] = self._status
- report_json['started'] = self._started.strftime(DATE_TIME_FORMAT)
- report_json['finished'] = self._finished.strftime(DATE_TIME_FORMAT)
- report_json['tests'] = {'total': self._total_tests,
- 'results': self._results}
- report_json['report'] = self._report
- return report_json
-
- def from_json(self, json_file):
-
- self._device['mac_addr'] = json_file['device']['mac_addr']
- self._device['manufacturer'] = json_file['device']['manufacturer']
- self._device['model'] = json_file['device']['model']
-
- if 'firmware' in json_file['device']:
- self._device['firmware'] = json_file['device']['firmware']
-
- self._status = json_file['status']
- self._started = datetime.strptime(json_file['started'], DATE_TIME_FORMAT)
- self._finished = datetime.strptime(json_file['finished'], DATE_TIME_FORMAT)
- self._total_tests = json_file['tests']['total']
-
- if 'report' in json_file:
- self._report = json_file['report']
-
- # Loop through test results
- for test_result in json_file['tests']['results']:
- self.add_test(test_result)
-
- return self
-
- # Create a pdf file in memory and return the bytes
- def to_pdf(self):
- # Resolve the data as html first
- report_html = self.to_html()
-
- # Convert HTML to PDF in memory using weasyprint
- pdf_bytes = BytesIO()
- HTML(string=report_html).write_pdf(pdf_bytes)
- return pdf_bytes
-
- def to_html(self):
- json_data = self.to_json()
- return f'''
-
-
- {self.generate_header()}
-
- '
+ page += self.generate_header(json_data)
+ if page_num == 1:
+ page += self.generate_summary(json_data)
+ page += self.generate_results(json_data, page_num)
+ page += self.generate_footer(page_num,max_page,version)
+ page += '
'
+ if page_num < max_page:
+ page += '
+

+
+ '''
+ # Add the device information
+ manufacturer = json_data['device']['manufacturer'] if 'manufacturer' in json_data['device'] else 'Undefined'
+ model = json_data['device']['model'] if 'model' in json_data['device'] else 'Undefined'
+ fw = json_data['device']['firmware'] if 'firmware' in json_data['device'] else 'Undefined'
+ mac = json_data['device']['mac_addr'] if 'mac_addr' in json_data['device'] else 'Undefined'
+
+ summary += self.generate_device_summary_label('Manufacturer',manufacturer)
+ summary += self.generate_device_summary_label('Model',model)
+ summary += self.generate_device_summary_label('Firmware',fw)
+ summary += self.generate_device_summary_label('MAC Address',mac,trailing_space=False)
+
+ # Add the result summary
+ summary += self.generate_result_summary(json_data)
+
+ summary += '\n
'
+ return summary
+
+ def generate_result_summary(self,json_data):
+ if json_data['status'] == 'Compliant':
+ result_summary = ''''''
+ else:
+ result_summary = '''
'''
+ result_summary += self.generate_result_summary_item('Test status', 'Complete')
+ result_summary += self.generate_result_summary_item('Test result', json_data['status'], style='color: white; font-size:24px; font-weight: 700;')
+ result_summary += self.generate_result_summary_item('Started', json_data['started'])
+
+ # Convert the timestamp strings to datetime objects
+ start_time = datetime.strptime(json_data['started'], "%Y-%m-%d %H:%M:%S")
+ end_time = datetime.strptime(json_data['finished'], "%Y-%m-%d %H:%M:%S")
+ # Calculate the duration
+ duration = end_time - start_time
+ result_summary += self.generate_result_summary_item('Duration',str(duration))
+
+ result_summary += '\n
'
+ return result_summary
+
+ def generate_result_summary_item(self, key, value, style=None):
+ summary_item = f'''
{key}
'''
+ if style is not None:
+ summary_item += f'''
{value}
'''
+ else:
+ summary_item += f'''
{value}
'''
+ return summary_item
+
+ def generate_device_summary_label(self, key, value, trailing_space=True):
+ label = f'''
+
{key}
+
{value}
+ '''
+ if trailing_space:
+ label += '''
'''
+ return label
+
+ def generate_head(self):
+ return f'''
+
+
+
+
Testrun Report
+
+
+ '''
+
+ def generate_css(self):
+ return '''
+ /* Set some global variables */
+ :root {
+ --header-height: .75in;
+ --header-width: 8.5in;
+ --header-pos-x: 0in;
+ --header-pos-y: 0in;
+ --summary-width: 8.5in;
+ --summary-height: 2.8in;
+ --vertical-line-height: calc(var(--summary-height)-.2in);
+ --vertical-line-pos-x: 25%;
+ }
+
+ @font-face {
+ font-family: 'Google Sans';
+ font-style: normal;
+ src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJllpyk.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ }
+
+ /* Define some common body formatting*/
+ body {
+ font-family: 'Google Sans', sans-serif;
+ margin: 0px;
+ padding: 0px;
+ }
+
+ /* Use this for various section breaks*/
+ .gradient-line {
+ position: relative;
+ background-image: linear-gradient(to right, red, blue, green, yellow, orange);
+ height: 1px;
+ /* Adjust the height as needed */
+ width: 100%;
+ /* To span the entire width */
+ display: block;
+ /* Ensures it's a block-level element */
+ }
+
+ /* Sets proper page size during print to pdf for weasyprint */
+ @page {
+ size: Letter;
+ width: 8.5in;
+ height: 11in;
+ }
+
+ .page {
+ position: relative;
+ margin: 0 20px;
+ width: 8.5in;
+ height: 11in;
+ }
+
+ /* Define the header related css elements*/
+ .header {
+ position: relative;
+ }
+
+ .header-text {
+ margin: 0 0 8px 0;
+ font-size: 20px;
+ font-weight: 400;
+ }
+
+ .header-title {
+ margin: 0px;
+ font-size: 48px;
+ font-weight: 700;
+ }
+
+ /* Define the summary related css elements*/
+ .summary-content {
+ position: relative;
+ width: var(--summary-width);
+ height: var(--summary-height);
+ margin-top: 19px;
+ margin-bottom: 19px;
+ }
+
+ .summary-item-label {
+ position: relative;
+ font-size: 12px;
+ font-weight: 500;
+ color: #5F6368;
+ }
+
+ .summary-item-value {
+ position: relative;
+ font-size: 20px;
+ font-weight: 400;
+ color: #202124;
+ }
+
+ .summary-item-space {
+ position: relative;
+ padding-bottom: 15px;
+ margin: 0;
+ }
+
+ .summary-vertical-line {
+ width: 1px;
+ height: var(--vertical-line-height);
+ background-color: #80868B;
+ position: absolute;
+ top: .3in;
+ bottom: .1in;
+ left: 3in;
+ }
+
+ /* CSS for the color box */
+ .summary-color-box {
+ position: absolute;
+ right: 0in;
+ top: .3in;
+ width: 2.6in;
+ height: 226px;
+ }
+
+ .summary-box-compliant {
+ background-color: rgb(24, 128, 56);
+ }
+
+ .summary-box-non-compliant {
+ background-color: #b31412;
+ }
+
+ .summary-box-label {
+ font-size: 14px;
+ margin-top: 5px;
+ color: #DADCE0;
+ position: relative;
+ top: 10px;
+ left: 10px;
+ font-weight: 500;
+ }
+
+ .summary-box-value {
+ font-size: 18px;
+ margin: 0 0 10px 0;
+ color: #ffffff;
+ position: relative;
+ top: 10px;
+ left: 10px;
+ }
+
+ .result-list-title {
+ font-size: 24px;
+ }
+
+ .result-list {
+ position: relative;
+ margin-top: .2in;
+ font-size: 18px;
+ }
+
+ .result-line {
+ border: 1px solid #D3D3D3;
+ /* Light Gray border*/
+ height: .4in;
+ width: 8.5in;
+ }
+
+ .result-line-result {
+ border-top: 0px;
+ }
+
+ .result-list-header-label {
+ font-weight: 500;
+ position: absolute;
+ font-size: 12px;
+ font-weight: bold;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ }
+
+ .result-test-label {
+ position: absolute;
+ font-size: 12px;
+ margin-top: 12px;
+ max-width: 300px;
+ font-weight: normal;
+ align-items: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+ .result-test-description {
+ max-width: 380px;
+ }
+
+ .result-test-result-non-compliant {
+ background-color: #FCE8E6;
+ color: #C5221F;
+ left: 7.02in;
+ }
+
+ .result-test-result {
+ position: absolute;
+ font-size: 12px;
+ width: fit-content;
+ height: 12px;
+ margin-top: 8px;
+ padding: 4px 4px 7px 5px;
+ border-radius: 2px;
+ }
+
+ .result-test-result-compliant {
+ background-color: #E6F4EA;
+ color: #137333;
+ left: 7.16in;
+ }
+
+ .result-test-result-skipped {
+ background-color: #e3e3e3;
+ color: #393939;
+ left: 7.2in;
+ }
+
+ /* CSS for the footer */
+ .footer {
+ position: absolute;
+ height: 30px;
+ width: 8.5in;
+ bottom: 0in;
+ }
+
+ .footer-label {
+ position: absolute;
+ top: 20px;
+ font-size: 12px;
+ }
+
+ @media print {
+ @page {
+ size: Letter;
+ width: 8.5in;
+ height: 11in;
+ }
+ }'''
\ No newline at end of file
diff --git a/modules/test/base/python/src/test_module.py b/modules/test/base/python/src/test_module.py
index c36ba0adf..47ec7b6c1 100644
--- a/modules/test/base/python/src/test_module.py
+++ b/modules/test/base/python/src/test_module.py
@@ -99,7 +99,8 @@ def run_tests(self):
LOGGER.info(f'Test {test["name"]} not implemented. Skipping')
result = None
else:
- LOGGER.debug(f'Test {test["name"]} is disabled. Skipping')
+ LOGGER.debug(f'Test {test["name"]} is disabled')
+
if result is not None:
if isinstance(result, bool):
test['result'] = 'Compliant' if result else 'Non-Compliant'
@@ -118,7 +119,7 @@ def run_tests(self):
duration = datetime.fromisoformat(test['end']) - datetime.fromisoformat(
test['start'])
test['duration'] = str(duration)
-
+
json_results = json.dumps({'results': tests}, indent=2)
self._write_results(json_results)
diff --git a/modules/test/baseline/README.md b/modules/test/baseline/README.md
index 96a700572..c7c83f72e 100644
--- a/modules/test/baseline/README.md
+++ b/modules/test/baseline/README.md
@@ -17,5 +17,5 @@ Within the ```python/src``` directory, the below tests are executed.
| ID | Description | Expected behavior | Required result
|---|---|---|---|
| baseline.compliant | Simulate a compliant test | A compliant test result is generated | Required |
-| baseline.informational | Simulate an informational test | An informational test result is generated | Informational |
+| baseline.skipped | Simulate an skipped test | An skipped test result is generated | Skipped |
| baseline.non-compliant | Simulate a non-compliant test | A non-compliant test result is generated | Required |
\ No newline at end of file
diff --git a/modules/test/baseline/conf/module_config.json b/modules/test/baseline/conf/module_config.json
index cc78ce0a0..bdaff49c4 100644
--- a/modules/test/baseline/conf/module_config.json
+++ b/modules/test/baseline/conf/module_config.json
@@ -25,10 +25,10 @@
"required_result": "Recommended"
},
{
- "name": "baseline.informational",
- "test_description": "Simulate an informational test",
- "expected_behavior": "An informational test result is generated",
- "required_result": "Informational"
+ "name": "baseline.skipped",
+ "test_description": "Simulate a skipped test",
+ "expected_behavior": "A skipped test result is generated",
+ "required_result": "Skipped"
}
]
}
diff --git a/modules/test/baseline/python/src/baseline_module.py b/modules/test/baseline/python/src/baseline_module.py
index 111db708e..38da718de 100644
--- a/modules/test/baseline/python/src/baseline_module.py
+++ b/modules/test/baseline/python/src/baseline_module.py
@@ -37,7 +37,7 @@ def _baseline_non_compliant(self):
LOGGER.info('Baseline non-compliant test finished')
return False, 'Baseline non-compliant test ran successfully'
- def _baseline_informational(self):
- LOGGER.info('Running baseline informational test')
- LOGGER.info('Baseline informational test finished')
- return None, 'Baseline informational test ran successfully'
+ def _baseline_skipped(self):
+ LOGGER.info('Running baseline skipped test')
+ LOGGER.info('Baseline skipped test finished')
+ return None, 'Baseline skipped test ran successfully'
diff --git a/resources/report/Google Sans.woff2 b/resources/report/Google Sans.woff2
new file mode 100644
index 000000000..8fdba9873
Binary files /dev/null and b/resources/report/Google Sans.woff2 differ
diff --git a/resources/report/testrun.png b/resources/report/testrun.png
new file mode 100644
index 000000000..9d0610adf
Binary files /dev/null and b/resources/report/testrun.png differ