Get versions of GHA and GMSCore in generated report PiperOrigin-RevId: 601627538
diff --git a/ui_automator/test_reporter.py b/ui_automator/test_reporter.py index ab906c8..194b26c 100644 --- a/ui_automator/test_reporter.py +++ b/ui_automator/test_reporter.py
@@ -20,10 +20,12 @@ """ import collections +from collections.abc import Mapping import io import logging import os import time +from typing import TypedDict, NotRequired import unittest from absl.testing import xml_reporter @@ -34,7 +36,7 @@ 'Commission to GHA': 'test_commission', 'Removing from GHA': 'test_decommission', }) - +_NA = 'n/a' _SUMMARY_COL_INDENTS = 25 _TEST_CASE_TITLE_INDENTS = 25 _TEST_RESULT_INDENTS = 8 @@ -49,6 +51,15 @@ ) +class ReportInfo(TypedDict): + """Type guard for summary of running unit tests.""" + gha_version: NotRequired[str | None] + gms_core_version: NotRequired[str | None] + hub_version: NotRequired[str | None] + device_firmware: NotRequired[str | None] + dut: NotRequired[str | None] + + # pylint: disable=protected-access class _TestCaseResult(xml_reporter._TestCaseResult): """Private helper for TestResult.""" @@ -189,7 +200,17 @@ self._logger = logger @classmethod - def write_summary_in_txt(cls) -> None: + def write_summary_in_txt( + cls, + report_info: ReportInfo | None = None, + ) -> None: + """Saves summary to a txt file. + + Args: + 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>. + """ if not cls._instance: return @@ -260,13 +281,34 @@ success_rate = round( 100.0 * float(total_successful_runs) / float(total_runs) ) + report_info = report_info or {} # TODO(b/317837867): Replace all placeholders with real values. rows: list[list[str]] = [] rows.append(['Summary', '', 'Version Info', '']) - rows.append(['DUT:', 'placeholder', 'GHA', 'placeholder']) - rows.append(['Test Time:', test_date, 'GMSCore', 'placeholder']) - rows.append(['Duration:', duration, 'Hub', 'placeholder']) - rows.append(['Number of runs:', str(total_runs), 'Device', 'placeholder']) + rows.append([ + 'DUT:', + report_info.get('dut', _NA), + 'GHA', + report_info.get('gha_version', _NA), + ]) + rows.append([ + 'Test Time:', + test_date, + 'GMSCore', + report_info.get('gms_core_version', _NA), + ]) + rows.append([ + 'Duration:', + duration, + 'Hub', + report_info.get('hub_version', _NA), + ]) + rows.append([ + 'Number of runs:', + str(total_runs), + 'Device', + report_info.get('device_firmware', _NA), + ]) rows.append([ 'Success Rate:', f'{success_rate}%({total_successful_runs}/{total_runs})', @@ -332,11 +374,18 @@ self._xml_file_path = xml_file_path or default_xml_file_path super().__init__(xml_stream=open(self._xml_file_path, 'w'), *args, **kwargs) - def run(self, suite: unittest.TestSuite) -> None: + def run( + self, + suite: unittest.TestSuite, + report_info: Mapping[str, str | None] | None = None, + ) -> None: """Runs tests and generates reports in XML and TXT. Args: suite: TestSuite should be run for regression testing. + 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>. """ try: super().run(suite) @@ -346,7 +395,7 @@ TestResult.writeAllResultsToXml() finally: self._logger.info(f'Xml file saved to {self._xml_file_path}.') - TestResult.write_summary_in_txt() + TestResult.write_summary_in_txt(report_info=report_info) def _makeResult(self): return self._TEST_RESULT_CLASS(
diff --git a/ui_automator/test_reporter_test.py b/ui_automator/test_reporter_test.py index 0470fde..3cc6678 100644 --- a/ui_automator/test_reporter_test.py +++ b/ui_automator/test_reporter_test.py
@@ -187,8 +187,8 @@ @mock.patch('builtins.open', autospec=True) def test_write_summary_in_txt_saves_summary_to_a_file(self, mock_open): - fake_stream = io.StringIO() - mock_open.return_value = fake_stream + txt_stream = io.StringIO() + mock_open.return_value = txt_stream start_time = 100 end_time = 200 result = self._make_result(start_time, end_time, 6) @@ -197,12 +197,20 @@ # 5 is the number explicitly add in _make_result. run_time = end_time - start_time + 5 now = time.localtime() + fake_report_info = { + 'gha_version': '1', + 'gms_core_version': '2', + 'hub_version': '1', + 'device_firmware': '1', + 'dut': '1', + } expected_summary = re.escape( unit_test_utils.make_summary( test_date=time.strftime('%Y/%m/%d', now), duration=test_reporter.duration_formatter(run_time), total_runs=3, total_successful_runs=2, + **fake_report_info, ) ) res_of_test_commission = ['FAIL', 'PASS', 'PASS'] @@ -248,17 +256,17 @@ result.stopTestRun() result.printErrors() - with mock.patch.object(fake_stream, 'close'): + with mock.patch.object(txt_stream, 'close'): with mock.patch.object(time, 'localtime', return_value=now): - test_reporter.TestResult.write_summary_in_txt() + test_reporter.TestResult.write_summary_in_txt(fake_report_info) mock_open.assert_called_once_with( f"summary_{time.strftime('%Y%m%d%H%M%S', now)}.txt", 'w', encoding='utf-8', ) - self.assertRegex(fake_stream.getvalue(), expected_summary) - self.assertRegex(fake_stream.getvalue(), expected_test_case_result) + self.assertRegex(txt_stream.getvalue(), expected_summary) + self.assertRegex(txt_stream.getvalue(), expected_test_case_result) @mock.patch.object( xml_reporter.TextAndXMLTestRunner,
diff --git a/ui_automator/ui_automator.py b/ui_automator/ui_automator.py index fdd6c51..181e8ed 100644 --- a/ui_automator/ui_automator.py +++ b/ui_automator/ui_automator.py
@@ -17,6 +17,7 @@ A python controller that can trigger mobly UI automator snippet to achieve some automated UI operations on Android phones. """ +import collections from concurrent import futures import enum import functools @@ -375,11 +376,32 @@ runner = test_reporter.TestRunner(logger=self._logger) try: - runner.run(suite) + runner.run(suite=suite, report_info=self.get_report_info()) finally: self._is_reg_test_finished = True executor.shutdown(wait=False) + def get_report_info(self) -> test_reporter.ReportInfo: + """Gets report info for regression tests. + + Returns: + 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>. + """ + temp_info = collections.defaultdict() + if self._connected_device: + temp_info['gha_version'] = ad.get_apk_version( + self._connected_device, 'com.google.android.apps.chromecast.app' + ) + temp_info['gms_core_version'] = ad.get_apk_version( + self._connected_device, 'com.google.android.gms' + ) + + report_info: test_reporter.ReportInfo = {**temp_info} + + return report_info + def _get_mbs_apk_path(self) -> str: return os.path.join( os.path.dirname(os.path.abspath(__file__)),
diff --git a/ui_automator/ui_automator_test.py b/ui_automator/ui_automator_test.py index 3d506d1..fa0d1f7 100644 --- a/ui_automator/ui_automator_test.py +++ b/ui_automator/ui_automator_test.py
@@ -650,9 +650,6 @@ mock_commission_device.assert_not_called() mock_run_regression_tests.assert_not_called() - @flagsaver.flagsaver( - (ui_automator._COMMISSION, ['m5stack', '34970112332', 'Office']) - ) @mock.patch.object(time, 'sleep', autospec=True) @mock.patch('builtins.open', autospec=True) @mock.patch.object(android_device, 'get_all_instances', autospec=True) @@ -689,9 +686,6 @@ ) self.assertEqual(mock_open.call_count, 2) - @flagsaver.flagsaver( - (ui_automator._COMMISSION, ['m5stack', '34970112332', 'Office']) - ) @mock.patch.object(time, 'sleep', autospec=True) @mock.patch('builtins.open', autospec=True) @mock.patch.object(android_device, 'get_all_instances', autospec=True) @@ -747,9 +741,6 @@ gha_room='Office', ) - @flagsaver.flagsaver( - (ui_automator._COMMISSION, ['m5stack', '34970112332', 'Office']) - ) @mock.patch.object(time, 'sleep', autospec=True) @mock.patch('builtins.open', autospec=True) @mock.patch.object(android_device, 'get_all_instances', autospec=True) @@ -1008,6 +999,67 @@ self.assertRegex(testcase6, expected_testcase6_re) mock_exit.assert_called_once() + @mock.patch.object(android_device, 'get_all_instances', autospec=True) + def test_get_report_info_includes_apk_versions_with_device_connected( + self, + mock_get_all_instances, + ): + self.mock_android_device.adb.shell.side_effect = [ + b'versionName=0.0.0\n', + b'versionName=0.0.1\n', + ] + mock_get_all_instances.return_value = [self.mock_android_device] + self.ui_automator.load_device() + + report_info = self.ui_automator.get_report_info() + + self.assertIsNotNone(report_info) + self.assertEqual(report_info.get('gha_version'), '0.0.0') + self.assertEqual(report_info.get('gms_core_version'), '0.0.1') + + def test_get_report_info_returns_empty_dict_when_no_device_connected( + self, + ): + report_info = self.ui_automator.get_report_info() + + self.assertDictEqual(report_info, {}) + + @mock.patch.object(time, 'sleep', autospec=True) + @mock.patch('builtins.open', autospec=True) + @mock.patch.object(android_device, 'get_all_instances', autospec=True) + @mock.patch.object(ui_automator.UIAutomator, 'get_report_info', autospec=True) + def test_run_regression_tests_should_insert_report_info_into_summary( + self, + mock_get_report_info, + mock_get_all_instances, + mock_open, + mock_sleep, + ): + mock_sleep.return_value = None + fake_report_info: test_reporter.ReportInfo = { + 'gha_version': '0.0.0', + 'gms_core_version': '0.0.1', + } + mock_get_report_info.return_value = fake_report_info + mock_get_all_instances.return_value = [self.mock_android_device] + + with mock.patch.object( + test_reporter.TestResult, 'write_summary_in_txt', autospec=True + ) as mock_write_summary_in_txt: + self.ui_automator.run_regression_tests( + 3, + ui_automator.RegTestSuiteType.COMMISSION, + device_name='m5stack', + pairing_code='34970112332', + gha_room='Office', + ) + + self.assertEqual( + mock_write_summary_in_txt.call_args.kwargs.get('report_info'), + fake_report_info, + ) + mock_open.assert_called_once() + if __name__ == '__main__': unittest.main()
diff --git a/ui_automator/unit_test_utils.py b/ui_automator/unit_test_utils.py index d383152..7bde017 100644 --- a/ui_automator/unit_test_utils.py +++ b/ui_automator/unit_test_utils.py
@@ -66,14 +66,14 @@ Returns: Test summary. """ - dut = kwargs.get('dut', 'placeholder') - gha = kwargs.get('gha', 'placeholder') - test_date = kwargs.get('test_date', 'placeholder') - gms_core = kwargs.get('gms_core', 'placeholder') + dut = kwargs.get('dut', 'n/a') + gha = kwargs.get('gha_version', 'n/a') + test_date = kwargs.get('test_date', 'n/a') + gms_core = kwargs.get('gms_core_version', 'n/a') duration = kwargs.get('duration', 0) - hub = kwargs.get('hub', 'placeholder') + hub = kwargs.get('hub_version', 'n/a') total_runs = kwargs.get('total_runs', 0) - device = kwargs.get('device', 'placeholder') + device = kwargs.get('device_firmware', 'n/a') total_successful_runs = kwargs.get('total_successful_runs', 0) success_rate = round(100.0 * float(total_successful_runs) / float(total_runs)) rows: list[list[str]] = []