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]] = []