blob: 4989738e337af0ded061e5ed3cc5e314fe354f83 [file] [log] [blame]
# Copyright 2024 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
#
# http://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.
"""Utilities for unit tests."""
import datetime
# Matches the entire XML output. Captures all <testcase> tags except for the
# last closing </testcase> in a single group.
OUTPUT_STRING = r"""<\?xml version="1.0"\?>
<testsuites name="" tests="%(tests)d" failures="%(failures)d" errors="%(errors)d" time="%(run_time).3f" timestamp="%(start_time)s">
<testsuite name="%(suite_name)s" tests="%(tests)d" failures="%(failures)d" errors="%(errors)d" time="%(run_time).3f" timestamp="%(start_time)s">
( <testcase [.\S\s]*)
</testcase>
</testsuite>
</testsuites>
"""
# Matches a single <testcase> tag and its contents, without the closing
# </testcase>, which we use as a separator to split multiple <testcase> tags.
TESTCASE_STRING_WITH_PROPERTIES = r""" <testcase name="%(test_name)s" status="%(status)s" result="%(result)s" time="%(run_time).3f" classname="%(class_name)s" timestamp="%(start_time)s">
<properties>
%(properties)s
</properties>%(message)s"""
# Matches a single <testcase> tag and its contents, without the closing
# </testcase>, which we use as a separator to split multiple <testcase> tags.
TESTCASE_STRING_WITH_ERRORS = r""" <testcase name="%(test_name)s" status="%(status)s" result="%(result)s" time="%(run_time).3f" classname="%(class_name)s" timestamp="%(start_time)s">
<error message="%(message)s" type="%(error_type)s"><\!\[CDATA\[%(error_msg)s\]\]></error>"""
# Matches a single <testcase> tag and its contents, without the closing
# </testcase>, which we use as a separator to split multiple <testcase> tags.
TESTCASE_STRING = r""" <testcase name="%(test_name)s" status="%(status)s" result="%(result)s" time="%(run_time).3f" classname="%(class_name)s" timestamp="%(start_time)s">"""
def iso_timestamp(timestamp: float) -> str:
"""Makes timestamp in iso format for unit tests.
Args:
timestamp: Time value in float.
Returns:
Formatted time according to ISO.
"""
return datetime.datetime.fromtimestamp(
timestamp, tz=datetime.timezone.utc
).isoformat()
def make_summary(
test_date: str | None = None,
duration: str | None = None,
total_runs: int = 0,
total_successful_runs: int = 0,
report_info: dict[str, str] | None = None,
) -> str:
"""Makes test summary produced by `test_reporter` for unit tests.
Args:
test_date: Test time for running regression tests.
duration: The duration for running regression tests.
total_runs: The number of runs for regression tests.
total_successful_runs: The number of successful runs for regression tests.
report_info: Versions and device info displayed in generated txt report.
Versions includes GHA, GMSCore, hub and device firmware. Device info is in
<model, type, protocol> format, e.g. <X123123, LIGHT, Matter>.
Returns:
Test summary.
"""
dut = report_info.get('dut', 'n/a') if report_info else 'n/a'
gha = report_info.get('gha_version', 'n/a') if report_info else 'n/a'
test_date = test_date or 'n/a'
duration = duration or 'n/a'
gms_core = (
report_info.get('gms_core_version', 'n/a') if report_info else 'n/a'
)
hub = report_info.get('hub_version', 'n/a') if report_info else 'n/a'
device = report_info.get('device_firmware', 'n/a') if report_info else 'n/a'
success_rate = round(100.0 * float(total_successful_runs) / float(total_runs))
rows: list[list[str]] = []
rows.append(['Summary', '', 'Version Info', ''])
rows.append(['DUT:', dut, 'GHA', gha])
rows.append(['Test Time:', test_date, 'GMSCore', gms_core])
rows.append(['Duration:', duration, 'Hub', hub])
rows.append(['Number of runs:', str(total_runs), 'Device', device])
rows.append([
'Success Rate:',
f'{success_rate}%({total_successful_runs}/{total_runs})',
])
summary = []
data_indents = (
max(max(map(len, report_info.values())) + 2, 25) if report_info else 25
)
for row in rows:
for i, element in enumerate(row):
if i % 2 == 0:
# Writes header.
summary.append(element.ljust(25))
else:
# Writes data, add 2 extra spaces to separate data and next header.
summary.append(element.ljust(data_indents))
summary.append('\n')
return ''.join(summary)
def make_test_case_result(
total_runs: int,
res_of_test_commission: list[str] | None,
res_of_test_decommission: list[str] | None,
) -> str:
"""Makes test case result produced by `test_reporter` for unit tests.
Args:
total_runs: Total cycles for regression tests.
res_of_test_commission: Elements in list should be `PASS`, `FAIL` or `N/A`,
which indicates the test result for each test case `test_commission`.
res_of_test_decommission: Elements in list should be `PASS`, `FAIL` or
`N/A`, which indicates the test result for each test case
`test_decommission`.
Returns:
Test case result.
"""
test_case_result = ['Test Case/Test Run'.ljust(25)]
for i in range(total_runs):
test_case_result.append(f'#{i + 1}'.ljust(8))
if res_of_test_commission:
test_case_result.append('\n')
test_case_result.append('Commission to GHA'.ljust(25))
for res in res_of_test_commission:
test_case_result.append(res.ljust(8))
if res_of_test_decommission:
test_case_result.append('\n')
test_case_result.append('Removing from GHA'.ljust(25))
for res in res_of_test_decommission:
test_case_result.append(res.ljust(8))
return ''.join(test_case_result)