diff options
author | Minghao Li <minghaoli@google.com> | 2022-04-15 11:48:02 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-14 20:48:02 -0700 |
commit | 6d7b6210372468cc05699d53877c338029f24ea2 (patch) | |
tree | eafeca59bc672113c4ed12adffa2c1d49f125407 /tests | |
parent | 38826e31e9319a7b93ff1b5df38ce203aa443844 (diff) | |
download | mobly-6d7b6210372468cc05699d53877c338029f24ea2.tar.gz |
Adding Snippet Client V2 for Android, Step 1 (#804)
Diffstat (limited to 'tests')
-rw-r--r-- | tests/mobly/controllers/android_device_lib/snippet_client_v2_test.py | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/tests/mobly/controllers/android_device_lib/snippet_client_v2_test.py b/tests/mobly/controllers/android_device_lib/snippet_client_v2_test.py new file mode 100644 index 0000000..b97774b --- /dev/null +++ b/tests/mobly/controllers/android_device_lib/snippet_client_v2_test.py @@ -0,0 +1,394 @@ +# Copyright 2017 Google Inc. +# +# 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. +"""Unit tests for mobly.controllers.android_device_lib.snippet_client_v2.""" + +import unittest +from unittest import mock + +from mobly.controllers.android_device_lib import adb +from mobly.controllers.android_device_lib import errors as android_device_lib_errors +from mobly.controllers.android_device_lib import snippet_client_v2 +from mobly.snippet import errors +from tests.lib import mock_android_device + +MOCK_PACKAGE_NAME = 'some.package.name' +MOCK_SERVER_PATH = f'{MOCK_PACKAGE_NAME}/{snippet_client_v2._INSTRUMENTATION_RUNNER_PACKAGE}' +MOCK_USER_ID = 0 + + +class SnippetClientV2Test(unittest.TestCase): + """Unit tests for SnippetClientV2.""" + + def _make_client(self, adb_proxy=None, mock_properties=None): + adb_proxy = adb_proxy or mock_android_device.MockAdbProxy( + instrumented_packages=[ + (MOCK_PACKAGE_NAME, + snippet_client_v2._INSTRUMENTATION_RUNNER_PACKAGE, + MOCK_PACKAGE_NAME) + ], + mock_properties=mock_properties) + + device = mock.Mock() + device.adb = adb_proxy + device.adb.current_user_id = MOCK_USER_ID + device.build_info = { + 'build_version_codename': + adb_proxy.getprop('ro.build.version.codename'), + 'build_version_sdk': + adb_proxy.getprop('ro.build.version.sdk'), + } + + self.client = snippet_client_v2.SnippetClientV2(MOCK_PACKAGE_NAME, device) + + def _make_client_with_extra_adb_properties(self, extra_properties): + mock_properties = mock_android_device.DEFAULT_MOCK_PROPERTIES.copy() + mock_properties.update(extra_properties) + self._make_client(mock_properties=mock_properties) + + def _mock_server_process_starting_response(self, mock_start_subprocess, + resp_lines): + mock_proc = mock_start_subprocess.return_value + mock_proc.stdout.readline.side_effect = resp_lines + + def test_check_app_installed_normally(self): + """Tests that app checker runs normally when app installed correctly.""" + self._make_client() + self.client._validate_snippet_app_on_device() + + def test_check_app_installed_fail_app_not_installed(self): + """Tests that app checker fails without installing app.""" + self._make_client(mock_android_device.MockAdbProxy()) + expected_msg = f'.* {MOCK_PACKAGE_NAME} is not installed.' + with self.assertRaisesRegex(errors.ServerStartPreCheckError, expected_msg): + self.client._validate_snippet_app_on_device() + + def test_check_app_installed_fail_not_instrumented(self): + """Tests that app checker fails without instrumenting app.""" + self._make_client( + mock_android_device.MockAdbProxy( + installed_packages=[MOCK_PACKAGE_NAME])) + expected_msg = ( + f'.* {MOCK_PACKAGE_NAME} is installed, but it is not instrumented.') + with self.assertRaisesRegex(errors.ServerStartPreCheckError, expected_msg): + self.client._validate_snippet_app_on_device() + + def test_check_app_installed_fail_instrumentation_not_installed(self): + """Tests that app checker fails without installing instrumentation.""" + self._make_client( + mock_android_device.MockAdbProxy(instrumented_packages=[( + MOCK_PACKAGE_NAME, + snippet_client_v2._INSTRUMENTATION_RUNNER_PACKAGE, + 'not.installed')])) + expected_msg = ('.* Instrumentation target not.installed is not installed.') + with self.assertRaisesRegex(errors.ServerStartPreCheckError, expected_msg): + self.client._validate_snippet_app_on_device() + + @mock.patch.object(mock_android_device.MockAdbProxy, 'shell') + def test_disable_hidden_api_normally(self, mock_shell_func): + """Tests the disabling hidden api process works normally.""" + self._make_client_with_extra_adb_properties({ + 'ro.build.version.codename': 'S', + 'ro.build.version.sdk': '31', + }) + self.client._device.is_rootable = True + self.client._disable_hidden_api_blocklist() + mock_shell_func.assert_called_with( + 'settings put global hidden_api_blacklist_exemptions "*"') + + @mock.patch.object(mock_android_device.MockAdbProxy, 'shell') + def test_disable_hidden_api_low_sdk(self, mock_shell_func): + """Tests it doesn't disable hidden api with low SDK.""" + self._make_client_with_extra_adb_properties({ + 'ro.build.version.codename': 'O', + 'ro.build.version.sdk': '26', + }) + self.client._device.is_rootable = True + self.client._disable_hidden_api_blocklist() + mock_shell_func.assert_not_called() + + @mock.patch.object(mock_android_device.MockAdbProxy, 'shell') + def test_disable_hidden_api_non_rootable(self, mock_shell_func): + """Tests it doesn't disable hidden api with non-rootable device.""" + self._make_client_with_extra_adb_properties({ + 'ro.build.version.codename': 'S', + 'ro.build.version.sdk': '31', + }) + self.client._device.is_rootable = False + self.client._disable_hidden_api_blocklist() + mock_shell_func.assert_not_called() + + @mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.' + 'utils.start_standing_subprocess') + @mock.patch.object(mock_android_device.MockAdbProxy, + 'shell', + return_value=b'setsid') + def test_start_server_with_user_id(self, mock_adb, mock_start_subprocess): + """Tests that `--user` is added to starting command with SDK >= 24.""" + self._make_client_with_extra_adb_properties({'ro.build.version.sdk': '30'}) + self._mock_server_process_starting_response( + mock_start_subprocess, + resp_lines=[ + b'SNIPPET START, PROTOCOL 1 234', b'SNIPPET SERVING, PORT 1234' + ]) + + self.client.start_server() + start_cmd_list = [ + 'adb', 'shell', + (f'setsid am instrument --user {MOCK_USER_ID} -w -e action start ' + f'{MOCK_SERVER_PATH}') + ] + self.assertListEqual(mock_start_subprocess.call_args_list, + [mock.call(start_cmd_list, shell=False)]) + self.assertEqual(self.client.device_port, 1234) + mock_adb.assert_called_with(['which', 'setsid']) + + @mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.' + 'utils.start_standing_subprocess') + @mock.patch.object(mock_android_device.MockAdbProxy, + 'shell', + return_value=b'setsid') + def test_start_server_without_user_id(self, mock_adb, mock_start_subprocess): + """Tests that `--user` is not added to starting command on SDK < 24.""" + self._make_client_with_extra_adb_properties({'ro.build.version.sdk': '21'}) + self._mock_server_process_starting_response( + mock_start_subprocess, + resp_lines=[ + b'SNIPPET START, PROTOCOL 1 234', b'SNIPPET SERVING, PORT 1234' + ]) + + self.client.start_server() + start_cmd_list = [ + 'adb', 'shell', + f'setsid am instrument -w -e action start {MOCK_SERVER_PATH}' + ] + self.assertListEqual(mock_start_subprocess.call_args_list, + [mock.call(start_cmd_list, shell=False)]) + mock_adb.assert_called_with(['which', 'setsid']) + self.assertEqual(self.client.device_port, 1234) + + @mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.' + 'utils.start_standing_subprocess') + @mock.patch.object(mock_android_device.MockAdbProxy, + 'shell', + side_effect=adb.AdbError('cmd', 'stdout', 'stderr', + 'ret_code')) + def test_start_server_without_persisting_commands(self, mock_adb, + mock_start_subprocess): + """Checks the starting server command without persisting commands.""" + self._make_client() + self._mock_server_process_starting_response( + mock_start_subprocess, + resp_lines=[ + b'SNIPPET START, PROTOCOL 1 234', b'SNIPPET SERVING, PORT 1234' + ]) + + self.client.start_server() + start_cmd_list = [ + 'adb', 'shell', + (f' am instrument --user {MOCK_USER_ID} -w -e action start ' + f'{MOCK_SERVER_PATH}') + ] + self.assertListEqual(mock_start_subprocess.call_args_list, + [mock.call(start_cmd_list, shell=False)]) + mock_adb.assert_has_calls( + [mock.call(['which', 'setsid']), + mock.call(['which', 'nohup'])]) + self.assertEqual(self.client.device_port, 1234) + + @mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.' + 'utils.start_standing_subprocess') + def test_start_server_with_nohup(self, mock_start_subprocess): + """Checks the starting server command with nohup.""" + self._make_client() + self._mock_server_process_starting_response( + mock_start_subprocess, + resp_lines=[ + b'SNIPPET START, PROTOCOL 1 234', b'SNIPPET SERVING, PORT 1234' + ]) + + def _mocked_shell(arg): + if 'nohup' in arg: + return b'nohup' + raise adb.AdbError('cmd', 'stdout', 'stderr', 'ret_code') + + self.client._adb.shell = _mocked_shell + + self.client.start_server() + start_cmd_list = [ + 'adb', 'shell', + (f'nohup am instrument --user {MOCK_USER_ID} -w -e action start ' + f'{MOCK_SERVER_PATH}') + ] + self.assertListEqual(mock_start_subprocess.call_args_list, + [mock.call(start_cmd_list, shell=False)]) + self.assertEqual(self.client.device_port, 1234) + + @mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.' + 'utils.start_standing_subprocess') + def test_start_server_with_setsid(self, mock_start_subprocess): + """Checks the starting server command with setsid.""" + self._make_client() + self._mock_server_process_starting_response( + mock_start_subprocess, + resp_lines=[ + b'SNIPPET START, PROTOCOL 1 234', b'SNIPPET SERVING, PORT 1234' + ]) + + def _mocked_shell(arg): + if 'setsid' in arg: + return b'setsid' + raise adb.AdbError('cmd', 'stdout', 'stderr', 'ret_code') + + self.client._adb.shell = _mocked_shell + self.client.start_server() + start_cmd_list = [ + 'adb', 'shell', + (f'setsid am instrument --user {MOCK_USER_ID} -w -e action start ' + f'{MOCK_SERVER_PATH}') + ] + self.assertListEqual(mock_start_subprocess.call_args_list, + [mock.call(start_cmd_list, shell=False)]) + self.assertEqual(self.client.device_port, 1234) + + @mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.' + 'utils.start_standing_subprocess') + def test_start_server_server_crash(self, mock_start_standing_subprocess): + """Tests that starting server process crashes.""" + self._make_client() + self._mock_server_process_starting_response( + mock_start_standing_subprocess, + resp_lines=[b'INSTRUMENTATION_RESULT: shortMsg=Process crashed.\n']) + with self.assertRaisesRegex( + errors.ServerStartProtocolError, + 'INSTRUMENTATION_RESULT: shortMsg=Process crashed.'): + self.client.start_server() + + @mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.' + 'utils.start_standing_subprocess') + def test_start_server_unknown_protocol_version( + self, mock_start_standing_subprocess): + """Tests that starting server process reports unknown protocol version.""" + self._make_client() + self._mock_server_process_starting_response( + mock_start_standing_subprocess, + resp_lines=[b'SNIPPET START, PROTOCOL 99 0\n']) + with self.assertRaisesRegex(errors.ServerStartProtocolError, + 'SNIPPET START, PROTOCOL 99 0'): + self.client.start_server() + + @mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.' + 'utils.start_standing_subprocess') + def test_start_server_invalid_device_port(self, + mock_start_standing_subprocess): + """Tests that starting server process reports invalid device port.""" + self._make_client() + self._mock_server_process_starting_response( + mock_start_standing_subprocess, + resp_lines=[ + b'SNIPPET START, PROTOCOL 1 0\n', b'SNIPPET SERVING, PORT ABC\n' + ]) + with self.assertRaisesRegex(errors.ServerStartProtocolError, + 'SNIPPET SERVING, PORT ABC'): + self.client.start_server() + + @mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.' + 'utils.start_standing_subprocess') + def test_start_server_with_junk(self, mock_start_standing_subprocess): + """Tests that starting server process reports known protocol with junk.""" + self._make_client() + self._mock_server_process_starting_response( + mock_start_standing_subprocess, + resp_lines=[ + b'This is some header junk\n', + b'Some phones print arbitrary output\n', + b'SNIPPET START, PROTOCOL 1 0\n', + b'Maybe in the middle too\n', + b'SNIPPET SERVING, PORT 123\n', + ]) + self.client.start_server() + self.assertEqual(123, self.client.device_port) + + @mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.' + 'utils.start_standing_subprocess') + def test_start_server_no_valid_line(self, mock_start_standing_subprocess): + """Tests that starting server process reports unknown protocol message.""" + self._make_client() + self._mock_server_process_starting_response( + mock_start_standing_subprocess, + resp_lines=[ + b'This is some header junk\n', + b'Some phones print arbitrary output\n', + b'', # readline uses '' to mark EOF + ]) + with self.assertRaisesRegex( + errors.ServerStartError, + 'Unexpected EOF when waiting for server to start.'): + self.client.start_server() + + @mock.patch('mobly.utils.stop_standing_subprocess') + @mock.patch.object(mock_android_device.MockAdbProxy, + 'shell', + return_value=b'OK (0 tests)') + def test_stop_server_normally(self, mock_android_device_shell, + mock_stop_standing_subprocess): + """Tests that stopping server process works normally.""" + self._make_client() + mock_proc = mock.Mock() + self.client._proc = mock_proc + self.client.stop() + self.assertIs(self.client._proc, None) + mock_android_device_shell.assert_called_once_with( + f'am instrument --user {MOCK_USER_ID} -w -e action stop ' + f'{MOCK_SERVER_PATH}') + mock_stop_standing_subprocess.assert_called_once_with(mock_proc) + + @mock.patch('mobly.utils.stop_standing_subprocess') + @mock.patch.object(mock_android_device.MockAdbProxy, + 'shell', + return_value=b'OK (0 tests)') + def test_stop_server_server_already_cleaned(self, mock_android_device_shell, + mock_stop_standing_subprocess): + """Tests stopping server process when subprocess is already cleaned.""" + self._make_client() + self.client._proc = None + self.client.stop() + self.assertIs(self.client._proc, None) + mock_stop_standing_subprocess.assert_not_called() + mock_android_device_shell.assert_called_once_with( + f'am instrument --user {MOCK_USER_ID} -w -e action stop ' + f'{MOCK_SERVER_PATH}') + + @mock.patch('mobly.utils.stop_standing_subprocess') + @mock.patch.object(mock_android_device.MockAdbProxy, + 'shell', + return_value=b'Closed with error.') + def test_stop_server_stop_with_error(self, mock_android_device_shell, + mock_stop_standing_subprocess): + """Tests all resources are cleaned even if stopping server has error.""" + self._make_client() + mock_proc = mock.Mock() + self.client._proc = mock_proc + with self.assertRaisesRegex(android_device_lib_errors.DeviceError, + 'Closed with error'): + self.client.stop() + + self.assertIs(self.client._proc, None) + mock_stop_standing_subprocess.assert_called_once_with(mock_proc) + mock_android_device_shell.assert_called_once_with( + f'am instrument --user {MOCK_USER_ID} -w -e action stop ' + f'{MOCK_SERVER_PATH}') + + +if __name__ == '__main__': + unittest.main() |