blob: b8224e584df1bfa7afc93ad4a71b38f373e8b1d3 [file] [log] [blame]
"""Unittest Lab exercise to test implementation of "Synonym Dictionary"."""
import os
import subprocess
import sys
import unittest
from unittest import mock
from mobly.controllers import android_device
from mobly.controllers.android_device_lib import adb
from mobly.snippet import errors as snippet_errors
from ui_automator import errors
from ui_automator import ui_automator
_FAKE_MATTER_DEVICE_NAME = 'fake-matter-device-name'
_FAKE_GHA_ROOM = 'Office'
_FAKE_PAIRING_CODE = '34970112332'
_GOOGLE_HOME_APP = {
'id': 'gha',
'name': 'Google Home Application (GHA)',
'minVersion': '3.1.18.1',
'minMatter': '231456000',
'minThread': '231456000',
}
_PYTHON_PATH = subprocess.check_output(['which', 'python']).decode('utf-8')
_PYTHON_BIN_PATH = _PYTHON_PATH[:-7]
_FAKE_VALID_SYS_ARGV = [
_PYTHON_BIN_PATH + 'ui-automator',
'--commission',
'm5stack,34970112332,Office',
]
_FAKE_SYS_ARGV_WITH_INVALID_PAIRING_CODE = [
_PYTHON_BIN_PATH + 'ui-automator',
'--commission',
'm5stack,3497,Office',
]
_FAKE_SYS_ARGV_WITH_INVALID_LENGTH = [
_PYTHON_BIN_PATH + 'ui-automator',
'--commission',
'',
]
class UIAutomatorTest(unittest.TestCase):
def setUp(self):
"""This method will be run before each of the test methods in the class."""
super().setUp()
self.ui_automator = ui_automator.UIAutomator()
self.mock_android_device = mock.patch.object(
android_device, 'AndroidDevice'
).start()
@mock.patch.object(android_device, 'get_all_instances', autospec=True)
def test_load_device_raises_an_error_when_adb_not_installed(
self, mock_get_all_instances
):
mock_get_all_instances.side_effect = adb.AdbError(
cmd='adb devices',
stdout='fake_msg',
stderr='adb command not found',
ret_code=1,
)
with self.assertRaisesRegex(
errors.AdbError,
r'Please install adb and add it to your PATH environment variable\.',
):
self.ui_automator.load_device()
@mock.patch.object(
android_device, 'get_all_instances', autospec=True, return_value=[]
)
def test_load_device_no_android_device_error(
self, unused_mock_get_all_instances
):
with self.assertRaises(errors.NoAndroidDeviceError):
self.ui_automator.load_device()
@mock.patch.object(
android_device, 'get_all_instances', autospec=True, return_value=[1, 2]
)
def test_load_device_success(self, unused_mock_get_all_instances):
with self.assertLogs() as cm:
self.ui_automator.load_device()
self.assertEqual(cm.output, ['INFO:root:connected device: [1]'])
def test_load_snippet_without_load_device_raises_error(self):
with self.assertRaises(errors.NoAndroidDeviceError):
self.ui_automator.load_snippet()
@mock.patch.object(android_device, 'get_all_instances', autospec=True)
def test_load_snippet_success(self, mock_get_all_instances):
mock_get_all_instances.return_value = [self.mock_android_device]
self.ui_automator.load_device()
self.ui_automator.load_snippet()
@mock.patch.object(android_device, 'get_all_instances', autospec=True)
def test_load_snippet_fails_with_server_start_pre_check_error(
self, mock_get_all_instances
):
self.mock_android_device.load_snippet.side_effect = (
snippet_errors.ServerStartPreCheckError('fake_ad', 'fake_msg')
)
mock_get_all_instances.return_value = [self.mock_android_device]
self.ui_automator.load_device()
with self.assertRaises(errors.AndroidDeviceNotReadyError):
self.ui_automator.load_snippet()
@mock.patch.object(android_device, 'get_all_instances', autospec=True)
def test_load_snippet_fails_with_server_start_error(
self, mock_get_all_instances
):
self.mock_android_device.load_snippet.side_effect = (
snippet_errors.ServerStartError('fake_ad', 'fake_msg')
)
mock_get_all_instances.return_value = [self.mock_android_device]
self.ui_automator.load_device()
with self.assertRaises(errors.AndroidDeviceNotReadyError):
self.ui_automator.load_snippet()
@mock.patch.object(android_device, 'get_all_instances', autospec=True)
def test_load_snippet_fails_with_protocol_error(self, mock_get_all_instances):
self.mock_android_device.load_snippet.side_effect = (
snippet_errors.ProtocolError('fake_ad', 'fake_msg')
)
mock_get_all_instances.return_value = [self.mock_android_device]
self.ui_automator.load_device()
with self.assertRaises(errors.AndroidDeviceNotReadyError):
self.ui_automator.load_snippet()
@mock.patch.object(android_device, 'get_all_instances', autospec=True)
def test_load_snippet_fails_with_snippet_error(self, mock_get_all_instances):
self.mock_android_device.load_snippet.side_effect = (
android_device.SnippetError('fake_device', 'fake_msg')
)
mock_get_all_instances.return_value = [self.mock_android_device]
self.ui_automator.load_device()
with self.assertLogs(level='DEBUG') as cm:
self.ui_automator.load_snippet()
self.assertEqual(
cm.output,
[
"DEBUG:root:'fake_device'::Service<SnippetManagementService>"
' fake_msg'
],
)
@mock.patch.object(android_device, 'get_all_instances', autospec=True)
@mock.patch.object(
os.path, 'dirname', autospec=True, return_value='/path/to/'
)
def test_load_snippet_installs_apk_when_apk_is_not_installed(
self, mock_dirname, mock_get_all_instances
):
self.mock_android_device.adb.shell.return_value = b'package:installed.apk\n'
mock_get_all_instances.return_value = [self.mock_android_device]
self.ui_automator.load_device()
self.ui_automator.load_snippet()
self.mock_android_device.adb.install.assert_called_once_with(
['-r', '-g', '/path/to/android/app/snippet-0.0.0.apk']
)
mock_dirname.assert_called_once()
@mock.patch.object(android_device, 'get_all_instances', autospec=True)
@mock.patch.object(os.path, 'dirname', autospec=True)
def test_load_snippet_should_not_install_apk_when_apk_is_installed(
self, mock_dirname, mock_get_all_instances
):
self.mock_android_device.adb.shell.return_value = (
b'package:com.chip.interop.moblysnippet'
)
mock_get_all_instances.return_value = [self.mock_android_device]
self.ui_automator.load_device()
self.ui_automator.load_snippet()
self.mock_android_device.adb.install.assert_not_called()
mock_dirname.assert_not_called()
@mock.patch.object(ui_automator.UIAutomator, 'load_device')
@mock.patch.object(ui_automator.UIAutomator, 'load_snippet')
def test_get_android_device_ready_raises_an_error_when_load_device_throws_an_error(
self, mock_load_snippet, mock_load_device
):
mock_load_device.side_effect = errors.NoAndroidDeviceError(
'No Android device connected to the host computer.'
)
@ui_automator.get_android_device_ready
def decorated_function(self):
del self
pass
with self.assertRaisesRegex(
errors.AndroidDeviceNotReadyError, r'decorated_function failed\.'
):
decorated_function(self.ui_automator)
mock_load_device.assert_called_once()
mock_load_snippet.assert_not_called()
@mock.patch.object(ui_automator.UIAutomator, 'load_device')
@mock.patch.object(ui_automator.UIAutomator, 'load_snippet')
def test_get_android_device_ready_raises_an_error_when_load_snippet_throws_an_error(
self, mock_load_snippet, mock_load_device
):
mock_load_snippet.side_effect = errors.AndroidDeviceNotReadyError(
'Check device(fake-serial) has installed required apk.'
)
@ui_automator.get_android_device_ready
def decorated_function(self):
del self
pass
with self.assertRaisesRegex(
errors.AndroidDeviceNotReadyError, r'decorated_function failed\.'
):
decorated_function(self.ui_automator)
mock_load_snippet.assert_called_once()
mock_load_device.assert_called_once()
@mock.patch.object(ui_automator.UIAutomator, 'load_device')
@mock.patch.object(ui_automator.UIAutomator, 'load_snippet')
def test_get_android_device_ready_raises_no_error_on_success(
self, mock_load_snippet, mock_load_device
):
@ui_automator.get_android_device_ready
def decorated_function(self):
del self
pass
decorated_function(self.ui_automator)
mock_load_snippet.assert_called_once()
mock_load_device.assert_called_once()
@mock.patch.object(android_device, 'get_all_instances')
@mock.patch.object(ui_automator.UIAutomator, 'load_snippet')
def test_commission_device_raises_an_error_when_device_is_not_ready(
self, mock_load_snippet, mock_get_all_instances
):
mock_get_all_instances.return_value = [self.mock_android_device]
error_msg = 'Check device(fake-serial) is ready.'
mock_load_snippet.side_effect = errors.AndroidDeviceNotReadyError(error_msg)
with self.assertRaisesRegex(
errors.AndroidDeviceNotReadyError, 'commission_device failed.'
):
self.ui_automator.commission_device(
_FAKE_MATTER_DEVICE_NAME, _FAKE_PAIRING_CODE, _FAKE_GHA_ROOM
)
@mock.patch.object(android_device, 'get_all_instances')
def test_commission_device_calls_a_method_in_snippet_with_desired_args(
self, mock_get_all_instances
):
mock_get_all_instances.return_value = [self.mock_android_device]
expected_matter_device = {
'id': '_fake_matter_device_name',
'name': _FAKE_MATTER_DEVICE_NAME,
'pairingCode': _FAKE_PAIRING_CODE,
'roomName': _FAKE_GHA_ROOM,
}
self.ui_automator.commission_device(
_FAKE_MATTER_DEVICE_NAME,
_FAKE_PAIRING_CODE,
_FAKE_GHA_ROOM,
)
self.mock_android_device.mbs.commissionDevice.assert_called_once_with(
_GOOGLE_HOME_APP, expected_matter_device
)
@mock.patch.object(android_device, 'get_all_instances')
def test_commission_device_fails_when_snippet_method_throws_an_error(
self, mock_get_all_instances
):
mock_get_all_instances.return_value = [self.mock_android_device]
expected_error_message = (
'Unable to continue automated commissioning process on'
f' device({self.mock_android_device.device_info["serial"]}).'
)
with mock.patch.object(self.mock_android_device, 'mbs') as snippet:
snippet.commissionDevice.side_effect = Exception(
'Can not find next button in the page.'
)
with self.assertRaises(errors.MoblySnippetError) as exc:
self.ui_automator.commission_device(
_FAKE_MATTER_DEVICE_NAME,
_FAKE_PAIRING_CODE,
_FAKE_GHA_ROOM,
)
self.assertIn(expected_error_message, str(exc.exception))
@mock.patch.object(android_device, 'get_all_instances')
def test_commission_device_raises_an_error_when_device_name_exceeds_limit(
self, mock_get_all_instances
):
invalid_device_name = 'fakeDeviceNameWith25Chars'
mock_get_all_instances.return_value = [self.mock_android_device]
with self.assertRaisesRegex(
ValueError,
'Value of DeviceName is invalid. Device name should be no more than 24'
' characters.',
):
self.ui_automator.commission_device(
invalid_device_name,
_FAKE_PAIRING_CODE,
_FAKE_GHA_ROOM,
)
@mock.patch.object(android_device, 'get_all_instances')
def test_commission_device_raises_an_error_when_pairing_code_is_invalid(
self, mock_get_all_instances
):
invalid_pairing_code = '123456789'
mock_get_all_instances.return_value = [self.mock_android_device]
with self.assertRaisesRegex(
ValueError,
'Value of PairingCode is invalid. Paring code should be 11-digit or'
' 21-digit numeric code.',
):
self.ui_automator.commission_device(
device_name=_FAKE_MATTER_DEVICE_NAME,
pairing_code=invalid_pairing_code,
gha_room=_FAKE_GHA_ROOM,
)
@mock.patch.object(android_device, 'get_all_instances')
def test_commission_device_raises_an_error_when_gha_room_is_invalid(
self, mock_get_all_instances
):
invalid_gha_room = 'Attic 2'
mock_get_all_instances.return_value = [self.mock_android_device]
with self.assertRaisesRegex(
ValueError,
(
'Value of GHARoom is invalid. Valid values for GHARoom: Attic|Back'
' door|Backyard|Basement|Bathroom|Bedroom|Den|Dining'
' Room|Entryway|Family Room|Front door|Front'
' Yard|Garage|Hallway|Kitchen|Living Room|Master'
' Bedroom|Office|Shed|Side door'
),
):
self.ui_automator.commission_device(
device_name=_FAKE_MATTER_DEVICE_NAME,
pairing_code=_FAKE_PAIRING_CODE,
gha_room=invalid_gha_room,
)
@mock.patch.object(sys, 'exit')
@mock.patch.object(ui_automator.UIAutomator, 'commission_device')
def test_run_calls_commission_device_with_valid_arguments(
self, mock_commission_device, mock_exit
):
with mock.patch.object(sys, 'argv', _FAKE_VALID_SYS_ARGV):
ui_automator.run()
mock_commission_device.assert_called_once_with(
'm5stack', '34970112332', 'Office'
)
mock_exit.assert_called()
@mock.patch.object(sys.stderr, 'write')
@mock.patch.object(sys, 'exit')
def test_commission_with_cmd_invalid_arg_should_stderr(
self, mock_exit, mock_stderr_write
):
with mock.patch.object(sys, 'argv', _FAKE_SYS_ARGV_WITH_INVALID_LENGTH):
ui_automator.run()
self.assertEqual(mock_stderr_write.call_count, 2)
first_call_args = ''.join(mock_stderr_write.call_args_list[0][0])
self.assertEqual(
first_call_args,
'FATAL Flags parsing error: flag --commission=[]: Use'
' --commission {DeviceName},{PairingCode},{GHARoom} to commission a'
' device to google fabric on GHA.\n',
)
second_call_args = ''.join(mock_stderr_write.call_args_list[1][0])
self.assertEqual(
second_call_args,
'Pass --helpshort or --helpfull to see help on flags.\n',
)
self.mock_android_device.mbs.commissionDevice.assert_not_called()
mock_exit.assert_called()
if __name__ == '__main__':
unittest.main()