blob: b674ce0ed54294d40542000f18c15c2973aacd95 [file] [log] [blame]
# 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 socket
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
MOCK_DEVICE_PORT = 1234
class _MockAdbProxy(mock_android_device.MockAdbProxy):
"""Mock class of adb proxy which covers all the calls used by snippet clients.
To enable testing snippet clients, this class extends the functionality of
base class from the following aspects:
* Records the arguments of all the calls to the shell method and forward
method.
* Handles the adb calls to stop the snippet server in the shell function
properly.
Attributes:
mock_shell_func: mock.Mock, used for recording the calls to the shell
method.
mock_forward_func: mock.Mock, used for recording the calls to the forward
method.
"""
def __init__(self, *args, **kwargs):
"""Initializes the instance of _MockAdbProxy."""
super().__init__(*args, **kwargs)
self.mock_shell_func = mock.Mock()
self.mock_forward_func = mock.Mock()
def shell(self, *args, **kwargs):
"""Mock `shell` of mobly.controllers.android_device_lib.adb.AdbProxy."""
# Record all the call args
self.mock_shell_func(*args, **kwargs)
# Handle the server stop command properly
if f'am instrument --user 0 -w -e action stop {MOCK_SERVER_PATH}' in args:
return b'OK (0 tests)'
# For other commands, hand it over to the base class.
return super().shell(*args, **kwargs)
def forward(self, *args, **kwargs):
"""Mock `forward` of mobly.controllers.android_device_lib.adb.AdbProxy."""
self.mock_forward_func(*args, **kwargs)
def _setup_mock_socket_file(mock_socket_create_conn, resp):
"""Sets up a mock socket file from the mock connection.
Args:
mock_socket_create_conn: The mock method for creating a socket connection.
resp: iterable, the side effect of the `readline` function of the mock
socket file.
Returns:
The mock socket file that will be injected into the code.
"""
fake_file = mock.Mock()
fake_file.readline.side_effect = resp
fake_conn = mock.Mock()
fake_conn.makefile.return_value = fake_file
mock_socket_create_conn.return_value = fake_conn
return fake_file
class SnippetClientV2Test(unittest.TestCase):
"""Unit tests for SnippetClientV2."""
def _make_client(self, adb_proxy=None, mock_properties=None):
adb_proxy = adb_proxy or _MockAdbProxy(instrumented_packages=[
(MOCK_PACKAGE_NAME, snippet_client_v2._INSTRUMENTATION_RUNNER_PACKAGE,
MOCK_PACKAGE_NAME)
],
mock_properties=mock_properties)
self.adb = adb_proxy
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.device = device
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=None):
resp_lines = resp_lines or [
b'SNIPPET START, PROTOCOL 1 0', b'SNIPPET SERVING, PORT 1234'
]
mock_proc = mock_start_subprocess.return_value
mock_proc.stdout.readline.side_effect = resp_lines
def _make_client_and_mock_socket_conn(self,
mock_socket_create_conn,
socket_resp=None,
device_port=MOCK_DEVICE_PORT,
adb_proxy=None,
mock_properties=None,
set_counter=True):
"""Makes the snippet client and mocks the socket connection."""
self._make_client(adb_proxy, mock_properties)
if socket_resp is None:
socket_resp = [b'{"status": true, "uid": 1}']
self.mock_socket_file = _setup_mock_socket_file(mock_socket_create_conn,
socket_resp)
self.client.device_port = device_port
self.socket_conn = mock_socket_create_conn.return_value
if set_counter:
self.client._counter = self.client._id_counter()
def _assert_client_resources_released(self, mock_start_subprocess,
mock_stop_standing_subprocess,
mock_get_port):
"""Asserts the resources had been released before the client stopped."""
self.assertIs(self.client._proc, None)
self.adb.mock_shell_func.assert_any_call(
f'am instrument --user {MOCK_USER_ID} -w -e action stop '
f'{MOCK_SERVER_PATH}')
mock_stop_standing_subprocess.assert_called_once_with(
mock_start_subprocess.return_value)
self.assertFalse(self.client.is_alive)
self.assertIs(self.client._conn, None)
self.socket_conn.close.assert_called_once_with()
self.assertIs(self.client.host_port, None)
self.adb.mock_forward_func.assert_any_call(
['--remove', f'tcp:{mock_get_port.return_value}'])
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.get_available_host_port',
return_value=12345)
@mock.patch('socket.create_connection')
@mock.patch('mobly.utils.stop_standing_subprocess')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
def test_the_whole_lifecycle_with_a_sync_rpc(self, mock_start_subprocess,
mock_stop_standing_subprocess,
mock_socket_create_conn,
mock_get_port):
"""Tests the whole lifecycle of the client with sending a sync RPC."""
socket_resp = [
b'{"status": true, "uid": 1}',
b'{"id": 0, "result": 123, "error": null, "callback": null}',
]
expected_socket_writes = [
mock.call(b'{"cmd": "initiate", "uid": -1}\n'),
mock.call(b'{"id": 0, "method": "some_sync_rpc", '
b'"params": [1, 2, "hello"]}\n'),
]
self._make_client_and_mock_socket_conn(mock_socket_create_conn,
socket_resp,
set_counter=False)
self._mock_server_process_starting_response(mock_start_subprocess)
self.client.initialize()
rpc_result = self.client.some_sync_rpc(1, 2, 'hello')
self.client.stop()
self._assert_client_resources_released(mock_start_subprocess,
mock_stop_standing_subprocess,
mock_get_port)
self.assertListEqual(self.mock_socket_file.write.call_args_list,
expected_socket_writes)
self.assertEqual(rpc_result, 123)
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.get_available_host_port',
return_value=12345)
@mock.patch('socket.create_connection')
@mock.patch('mobly.utils.stop_standing_subprocess')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
@mock.patch('mobly.controllers.android_device_lib.callback_handler.'
'CallbackHandler')
def test_the_whole_lifecycle_with_an_async_rpc(self, mock_callback_class,
mock_start_subprocess,
mock_stop_standing_subprocess,
mock_socket_create_conn,
mock_get_port):
"""Tests the whole lifecycle of the client with sending an async RPC."""
mock_socket_resp = [
b'{"status": true, "uid": 1}',
b'{"id": 0, "result": 123, "error": null, "callback": "1-0"}',
b'{"status": true, "uid": 1}',
]
expected_socket_writes = [
mock.call(b'{"cmd": "initiate", "uid": -1}\n'),
mock.call(b'{"id": 0, "method": "some_async_rpc", '
b'"params": [1, 2, "async"]}\n'),
mock.call(b'{"cmd": "continue", "uid": 1}\n'),
]
self._make_client_and_mock_socket_conn(mock_socket_create_conn,
mock_socket_resp,
set_counter=False)
self._mock_server_process_starting_response(mock_start_subprocess)
self.client.initialize()
rpc_result = self.client.some_async_rpc(1, 2, 'async')
self.client.stop()
self._assert_client_resources_released(mock_start_subprocess,
mock_stop_standing_subprocess,
mock_get_port)
self.assertListEqual(self.mock_socket_file.write.call_args_list,
expected_socket_writes)
mock_callback_class.assert_called_with(
callback_id='1-0',
event_client=self.client._event_client,
ret_value=123,
method_name='some_async_rpc',
ad=self.device)
self.assertIs(rpc_result, mock_callback_class.return_value)
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.get_available_host_port',
return_value=12345)
@mock.patch('socket.create_connection')
@mock.patch('mobly.utils.stop_standing_subprocess')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
@mock.patch('mobly.controllers.android_device_lib.callback_handler.'
'CallbackHandler')
def test_the_whole_lifecycle_with_multiple_rpcs(self, mock_callback_class,
mock_start_subprocess,
mock_stop_standing_subprocess,
mock_socket_create_conn,
mock_get_port):
"""Tests the whole lifecycle of the client with sending multiple RPCs."""
# Prepare the test
mock_socket_resp = [
b'{"status": true, "uid": 1}',
b'{"id": 0, "result": 123, "error": null, "callback": null}',
b'{"id": 1, "result": 456, "error": null, "callback": "1-0"}',
# Response for starting the event client
b'{"status": true, "uid": 1}',
b'{"id": 2, "result": 789, "error": null, "callback": null}',
b'{"id": 3, "result": 321, "error": null, "callback": "2-0"}',
]
self._make_client_and_mock_socket_conn(mock_socket_create_conn,
mock_socket_resp,
set_counter=False)
self._mock_server_process_starting_response(mock_start_subprocess)
rpc_results_expected = [
123,
mock.Mock(),
789,
mock.Mock(),
]
# Extract the two mock objects to use as return values of callback handler
# class
mock_callback_class.side_effect = [
rpc_results_expected[1], rpc_results_expected[3]
]
# Run tests
rpc_results = []
self.client.initialize()
rpc_results.append(self.client.some_sync_rpc(1, 2, 'hello'))
rpc_results.append(self.client.some_async_rpc(3, 4, 'async'))
rpc_results.append(self.client.some_sync_rpc(5, 'hello'))
rpc_results.append(self.client.some_async_rpc(6, 'async'))
self.client.stop()
# Assertions
mock_callback_class_calls_expected = [
mock.call(callback_id='1-0',
event_client=self.client._event_client,
ret_value=456,
method_name='some_async_rpc',
ad=self.device),
mock.call(callback_id='2-0',
event_client=self.client._event_client,
ret_value=321,
method_name='some_async_rpc',
ad=self.device),
]
self.assertListEqual(rpc_results, rpc_results_expected)
mock_callback_class.assert_has_calls(mock_callback_class_calls_expected)
self._assert_client_resources_released(mock_start_subprocess,
mock_stop_standing_subprocess,
mock_get_port)
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(_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(_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(
_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()
def test_disable_hidden_api_normally(self):
"""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.device.is_rootable = True
self.client._disable_hidden_api_blocklist()
self.adb.mock_shell_func.assert_called_with(
'settings put global hidden_api_blacklist_exemptions "*"')
def test_disable_hidden_api_low_sdk(self):
"""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.device.is_rootable = True
self.client._disable_hidden_api_blocklist()
self.adb.mock_shell_func.assert_not_called()
def test_disable_hidden_api_non_rootable(self):
"""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.device.is_rootable = False
self.client._disable_hidden_api_blocklist()
self.adb.mock_shell_func.assert_not_called()
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
@mock.patch.object(_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)
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(_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)
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(_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)
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)
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)
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')
def test_stop_normally(self, mock_stop_standing_subprocess):
"""Tests that stopping server process works normally."""
self._make_client()
mock_proc = mock.Mock()
self.client._proc = mock_proc
mock_conn = mock.Mock()
self.client._conn = mock_conn
self.client.host_port = 12345
self.client.stop()
self.assertIs(self.client._proc, None)
self.adb.mock_shell_func.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)
self.assertFalse(self.client.is_alive)
self.assertIs(self.client._conn, None)
mock_conn.close.assert_called_once_with()
self.assertIs(self.client.host_port, None)
self.device.adb.mock_forward_func.assert_called_once_with(
['--remove', 'tcp:12345'])
@mock.patch('mobly.utils.stop_standing_subprocess')
def test_stop_when_server_is_already_cleaned(self,
mock_stop_standing_subprocess):
"""Tests that stop server process when subprocess is already cleaned."""
self._make_client()
self.client._proc = None
mock_conn = mock.Mock()
self.client._conn = mock_conn
self.client.host_port = 12345
self.client.stop()
self.assertIs(self.client._proc, None)
mock_stop_standing_subprocess.assert_not_called()
self.adb.assert_called_once_with(
f'am instrument --user {MOCK_USER_ID} -w -e action stop '
f'{MOCK_SERVER_PATH}')
self.assertFalse(self.client.is_alive)
self.assertIs(self.client._conn, None)
mock_conn.close.assert_called_once_with()
self.assertIs(self.client.host_port, None)
self.device.adb.mock_forward_func.assert_called_once_with(
['--remove', 'tcp:12345'])
@mock.patch('mobly.utils.stop_standing_subprocess')
def test_stop_when_conn_is_already_cleaned(self,
mock_stop_standing_subprocess):
"""Tests that stop server process when the connection is already closed."""
self._make_client()
mock_proc = mock.Mock()
self.client._proc = mock_proc
self.client._conn = None
self.client.host_port = 12345
self.client.stop()
self.assertIs(self.client._proc, None)
mock_stop_standing_subprocess.assert_called_once_with(mock_proc)
self.adb.assert_called_once_with(
f'am instrument --user {MOCK_USER_ID} -w -e action stop '
f'{MOCK_SERVER_PATH}')
self.assertFalse(self.client.is_alive)
self.assertIs(self.client._conn, None)
self.assertIs(self.client.host_port, None)
self.device.adb.mock_forward_func.assert_called_once_with(
['--remove', 'tcp:12345'])
@mock.patch('mobly.utils.stop_standing_subprocess')
@mock.patch.object(_MockAdbProxy, 'shell', return_value=b'Closed with error.')
def test_stop_with_device_side_error(self, mock_adb_shell,
mock_stop_standing_subprocess):
"""Tests all resources will be cleaned when server stop throws an error."""
self._make_client()
mock_proc = mock.Mock()
self.client._proc = mock_proc
mock_conn = mock.Mock()
self.client._conn = mock_conn
self.client.host_port = 12345
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_adb_shell.assert_called_once_with(
f'am instrument --user {MOCK_USER_ID} -w -e action stop '
f'{MOCK_SERVER_PATH}')
self.assertFalse(self.client.is_alive)
self.assertIs(self.client._conn, None)
mock_conn.close.assert_called_once_with()
self.assertIs(self.client.host_port, None)
self.device.adb.mock_forward_func.assert_called_once_with(
['--remove', 'tcp:12345'])
@mock.patch('mobly.utils.stop_standing_subprocess')
def test_stop_with_conn_close_error(self, mock_stop_standing_subprocess):
"""Tests port resource will be cleaned when socket close throws an error."""
del mock_stop_standing_subprocess
self._make_client()
mock_proc = mock.Mock()
self.client._proc = mock_proc
mock_conn = mock.Mock()
# The deconstructor will call this mock function again after tests, so
# only throw this error when it is called the first time.
mock_conn.close.side_effect = (OSError('Closed with error'), None)
self.client._conn = mock_conn
self.client.host_port = 12345
with self.assertRaisesRegex(OSError, 'Closed with error'):
self.client.stop()
self.device.adb.mock_forward_func.assert_called_once_with(
['--remove', 'tcp:12345'])
def test_close_connection_normally(self):
"""Tests that closing connection works normally."""
self._make_client()
mock_conn = mock.Mock()
self.client._conn = mock_conn
self.client.host_port = 123
self.client.close_connection()
self.assertIs(self.client._conn, None)
self.assertIs(self.client.host_port, None)
mock_conn.close.assert_called_once_with()
self.device.adb.mock_forward_func.assert_called_once_with(
['--remove', 'tcp:123'])
def test_close_connection_when_host_port_has_been_released(self):
"""Tests that close connection when the host port has been released."""
self._make_client()
mock_conn = mock.Mock()
self.client._conn = mock_conn
self.client.host_port = None
self.client.close_connection()
self.assertIs(self.client._conn, None)
self.assertIs(self.client.host_port, None)
mock_conn.close.assert_called_once_with()
self.device.adb.mock_forward_func.assert_not_called()
def test_close_connection_when_conn_have_been_closed(self):
"""Tests that close connection when the connection has been closed."""
self._make_client()
self.client._conn = None
self.client.host_port = 123
self.client.close_connection()
self.assertIs(self.client._conn, None)
self.assertIs(self.client.host_port, None)
self.device.adb.mock_forward_func.assert_called_once_with(
['--remove', 'tcp:123'])
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
def test_send_sync_rpc_normally(self, mock_start_subprocess,
mock_socket_create_conn):
"""Tests that sending a sync RPC works normally."""
socket_resp = [
b'{"status": true, "uid": 1}',
b'{"id": 0, "result": 123, "error": null, "callback": null}',
]
self._make_client_and_mock_socket_conn(mock_socket_create_conn, socket_resp)
self._mock_server_process_starting_response(mock_start_subprocess)
self.client.make_connection()
rpc_result = self.client.some_rpc(1, 2, 'hello')
self.assertEqual(rpc_result, 123)
self.mock_socket_file.write.assert_called_with(
b'{"id": 0, "method": "some_rpc", "params": [1, 2, "hello"]}\n')
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
@mock.patch('mobly.controllers.android_device_lib.callback_handler.'
'CallbackHandler')
def test_async_rpc_start_event_client(self, mock_callback_class,
mock_start_subprocess,
mock_socket_create_conn):
"""Tests that sending an async RPC starts the event client."""
socket_resp = [
b'{"status": true, "uid": 1}',
b'{"id": 0, "result": 123, "error": null, "callback": "1-0"}',
b'{"status": true, "uid": 1}',
b'{"id":1,"result":"async-rpc-event","callback":null,"error":null}',
]
socket_write_expected = [
mock.call(b'{"cmd": "initiate", "uid": -1}\n'),
mock.call(b'{"id": 0, "method": "some_async_rpc", '
b'"params": [1, 2, "hello"]}\n'),
mock.call(b'{"cmd": "continue", "uid": 1}\n'),
mock.call(b'{"id": 1, "method": "eventGetAll", '
b'"params": ["1-0", "eventName"]}\n'),
]
self._make_client_and_mock_socket_conn(mock_socket_create_conn,
socket_resp,
set_counter=True)
self._mock_server_process_starting_response(mock_start_subprocess)
self.client.host_port = 12345
self.client.make_connection()
rpc_result = self.client.some_async_rpc(1, 2, 'hello')
mock_callback_class.assert_called_with(
callback_id='1-0',
event_client=self.client._event_client,
ret_value=123,
method_name='some_async_rpc',
ad=self.device)
self.assertIs(rpc_result, mock_callback_class.return_value)
# Ensure the event client is alive
self.assertTrue(self.client._event_client.is_alive)
# Ensure the event client shared the same ports and uid with main client
self.assertEqual(self.client._event_client.host_port, 12345)
self.assertEqual(self.client._event_client.device_port, MOCK_DEVICE_PORT)
self.assertEqual(self.client._event_client.uid, self.client.uid)
# Ensure the event client has reset its own RPC id counter
self.assertEqual(next(self.client._counter), 1)
self.assertEqual(next(self.client._event_client._counter), 0)
# Ensure that event client can send RPCs
event_string = self.client._event_client.eventGetAll('1-0', 'eventName')
self.assertEqual(event_string, 'async-rpc-event')
self.assertListEqual(
self.mock_socket_file.write.call_args_list,
socket_write_expected,
)
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.get_available_host_port')
def test_initialize_client_normally(self, mock_get_port,
mock_start_subprocess,
mock_socket_create_conn):
"""Tests that initializing the client works normally."""
mock_get_port.return_value = 12345
socket_resp = [b'{"status": true, "uid": 1}']
self._make_client_and_mock_socket_conn(mock_socket_create_conn,
socket_resp,
set_counter=True)
self._mock_server_process_starting_response(mock_start_subprocess)
self.client.initialize()
self.assertTrue(self.client.is_alive)
self.assertEqual(self.client.uid, 1)
self.assertEqual(self.client.host_port, 12345)
self.assertEqual(self.client.device_port, MOCK_DEVICE_PORT)
self.assertEqual(next(self.client._counter), 0)
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.get_available_host_port')
def test_restore_event_client(self, mock_get_port, mock_start_subprocess,
mock_socket_create_conn):
"""Tests restoring the event client."""
mock_get_port.return_value = 12345
socket_resp = [
# response of handshake when initializing the client
b'{"status": true, "uid": 1}',
# response of an async RPC
b'{"id": 0, "result": 123, "error": null, "callback": "1-0"}',
# response of starting event client
b'{"status": true, "uid": 1}',
# response of restoring server connection
b'{"status": true, "uid": 2}',
# response of restoring event client
b'{"status": true, "uid": 3}',
# response of restoring server connection
b'{"status": true, "uid": 4}',
# response of restoring event client
b'{"status": true, "uid": 5}',
]
socket_write_expected = [
# request of handshake when initializing the client
mock.call(b'{"cmd": "initiate", "uid": -1}\n'),
# request of an async RPC
mock.call(b'{"id": 0, "method": "some_async_rpc", "params": []}\n'),
# request of starting event client
mock.call(b'{"cmd": "continue", "uid": 1}\n'),
# request of restoring server connection
mock.call(b'{"cmd": "initiate", "uid": -1}\n'),
# request of restoring event client
mock.call(b'{"cmd": "initiate", "uid": -1}\n'),
# request of restoring server connection
mock.call(b'{"cmd": "initiate", "uid": -1}\n'),
# request of restoring event client
mock.call(b'{"cmd": "initiate", "uid": -1}\n'),
]
self._make_client_and_mock_socket_conn(mock_socket_create_conn, socket_resp)
self._mock_server_process_starting_response(mock_start_subprocess)
self.client.make_connection()
callback = self.client.some_async_rpc()
# before reconnect, clients use previously selected ports
self.assertEqual(self.client.host_port, 12345)
self.assertEqual(self.client.device_port, MOCK_DEVICE_PORT)
self.assertEqual(callback._event_client.host_port, 12345)
self.assertEqual(callback._event_client.device_port, MOCK_DEVICE_PORT)
self.assertEqual(next(self.client._event_client._counter), 0)
# after reconnect, if host port specified, clients use specified port
self.client.restore_server_connection(port=54321)
self.assertEqual(self.client.host_port, 54321)
self.assertEqual(self.client.device_port, MOCK_DEVICE_PORT)
self.assertEqual(callback._event_client.host_port, 54321)
self.assertEqual(callback._event_client.device_port, MOCK_DEVICE_PORT)
self.assertEqual(next(self.client._event_client._counter), 0)
# after reconnect, if host port not specified, clients use selected
# available port
mock_get_port.return_value = 56789
self.client.restore_server_connection()
self.assertEqual(self.client.host_port, 56789)
self.assertEqual(self.client.device_port, MOCK_DEVICE_PORT)
self.assertEqual(callback._event_client.host_port, 56789)
self.assertEqual(callback._event_client.device_port, MOCK_DEVICE_PORT)
self.assertEqual(next(self.client._event_client._counter), 0)
# if unable to reconnect for any reason, a
# errors.ServerRestoreConnectionError is raised.
mock_socket_create_conn.side_effect = IOError('socket timed out')
with self.assertRaisesRegex(
errors.ServerRestoreConnectionError,
(f'Failed to restore server connection for {MOCK_PACKAGE_NAME} at '
f'host port 56789, device port {MOCK_DEVICE_PORT}')):
self.client.restore_server_connection()
self.assertListEqual(self.mock_socket_file.write.call_args_list,
socket_write_expected)
@mock.patch.object(snippet_client_v2.SnippetClientV2, '_make_connection')
@mock.patch.object(snippet_client_v2.SnippetClientV2,
'send_handshake_request')
@mock.patch.object(snippet_client_v2.SnippetClientV2,
'create_socket_connection')
def test_restore_server_connection_with_event_client(
self, mock_create_socket_conn_func, mock_send_handshake_func,
mock_make_connection):
"""Tests restoring server connection when the event client is not None."""
self._make_client()
event_client = snippet_client_v2.SnippetClientV2('mock-package',
mock.Mock())
self.client._event_client = event_client
self.client.device_port = 54321
self.client.uid = 5
self.client.restore_server_connection(port=12345)
mock_make_connection.assert_called_once_with()
self.assertEqual(event_client.host_port, 12345)
self.assertEqual(event_client.device_port, 54321)
self.assertEqual(next(event_client._counter), 0)
mock_create_socket_conn_func.assert_called_once_with()
mock_send_handshake_func.assert_called_once_with(
-1, snippet_client_v2.ConnectionHandshakeCommand.INIT)
@mock.patch('builtins.print')
def test_help_rpc_when_printing_by_default(self, mock_print):
"""Tests the `help` method when it prints the output by default."""
self._make_client()
mock_rpc = mock.MagicMock()
self.client._rpc = mock_rpc
result = self.client.help()
mock_rpc.assert_called_once_with('help')
self.assertIsNone(result)
mock_print.assert_called_once_with(mock_rpc.return_value)
@mock.patch('builtins.print')
def test_help_rpc_when_not_printing(self, mock_print):
"""Tests the `help` method when it was set not to print the output."""
self._make_client()
mock_rpc = mock.MagicMock()
self.client._rpc = mock_rpc
result = self.client.help(print_output=False)
mock_rpc.assert_called_once_with('help')
self.assertEqual(mock_rpc.return_value, result)
mock_print.assert_not_called()
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.get_available_host_port',
return_value=12345)
def test_make_connection_normally(self, mock_get_port, mock_start_subprocess,
mock_socket_create_conn):
"""Tests that making a connection works normally."""
del mock_get_port
socket_resp = [b'{"status": true, "uid": 1}']
self._make_client_and_mock_socket_conn(mock_socket_create_conn, socket_resp)
self._mock_server_process_starting_response(mock_start_subprocess)
self.client.make_connection()
self.assertEqual(self.client.uid, 1)
self.assertEqual(self.client.device_port, MOCK_DEVICE_PORT)
self.adb.mock_forward_func.assert_called_once_with(
['tcp:12345', f'tcp:{MOCK_DEVICE_PORT}'])
mock_socket_create_conn.assert_called_once_with(
('localhost', 12345), snippet_client_v2._SOCKET_CONNECTION_TIMEOUT)
self.socket_conn.settimeout.assert_called_once_with(
snippet_client_v2._SOCKET_READ_TIMEOUT)
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.get_available_host_port',
return_value=12345)
def test_make_connection_with_preset_host_port(self, mock_get_port,
mock_start_subprocess,
mock_socket_create_conn):
"""Tests that make a connection with the preset host port."""
del mock_get_port
socket_resp = [b'{"status": true, "uid": 1}']
self._make_client_and_mock_socket_conn(mock_socket_create_conn, socket_resp)
self._mock_server_process_starting_response(mock_start_subprocess)
self.client.host_port = 23456
self.client.make_connection()
self.assertEqual(self.client.uid, 1)
self.assertEqual(self.client.device_port, MOCK_DEVICE_PORT)
# Test that the host port for forwarding is 23456 instead of 12345
self.adb.mock_forward_func.assert_called_once_with(
['tcp:23456', f'tcp:{MOCK_DEVICE_PORT}'])
mock_socket_create_conn.assert_called_once_with(
('localhost', 23456), snippet_client_v2._SOCKET_CONNECTION_TIMEOUT)
self.socket_conn.settimeout.assert_called_once_with(
snippet_client_v2._SOCKET_READ_TIMEOUT)
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.get_available_host_port',
return_value=12345)
def test_make_connection_with_ip(self, mock_get_port, mock_start_subprocess,
mock_socket_create_conn):
"""Tests that make a connection with 127.0.0.1 instead of localhost."""
del mock_get_port
socket_resp = [b'{"status": true, "uid": 1}']
self._make_client_and_mock_socket_conn(mock_socket_create_conn, socket_resp)
self._mock_server_process_starting_response(mock_start_subprocess)
mock_conn = mock_socket_create_conn.return_value
# Refuse creating socket connection with 'localhost', only accept
# '127.0.0.1' as address
def _mock_create_conn_side_effect(address, *args, **kwargs):
del args, kwargs
if address[0] == '127.0.0.1':
return mock_conn
raise ConnectionRefusedError(f'Refusing connection to {address[0]}.')
mock_socket_create_conn.side_effect = _mock_create_conn_side_effect
self.client.make_connection()
self.assertEqual(self.client.uid, 1)
self.assertEqual(self.client.device_port, MOCK_DEVICE_PORT)
self.adb.mock_forward_func.assert_called_once_with(
['tcp:12345', f'tcp:{MOCK_DEVICE_PORT}'])
mock_socket_create_conn.assert_any_call(
('127.0.0.1', 12345), snippet_client_v2._SOCKET_CONNECTION_TIMEOUT)
self.socket_conn.settimeout.assert_called_once_with(
snippet_client_v2._SOCKET_READ_TIMEOUT)
@mock.patch('socket.create_connection')
def test_make_connection_io_error(self, mock_socket_create_conn):
"""Tests IOError occurred trying to create a socket connection."""
mock_socket_create_conn.side_effect = IOError()
with self.assertRaises(IOError):
self._make_client()
self.client.device_port = 123
self.client.make_connection()
@mock.patch('socket.create_connection')
def test_make_connection_timeout(self, mock_socket_create_conn):
"""Tests timeout occurred trying to create a socket connection."""
mock_socket_create_conn.side_effect = socket.timeout
with self.assertRaises(socket.timeout):
self._make_client()
self.client.device_port = 123
self.client.make_connection()
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
def test_make_connection_receives_none_handshake_response(
self, mock_start_subprocess, mock_socket_create_conn):
"""Tests make_connection receives None as the handshake response."""
socket_resp = [None]
self._make_client_and_mock_socket_conn(mock_socket_create_conn, socket_resp)
self._mock_server_process_starting_response(mock_start_subprocess)
with self.assertRaisesRegex(
errors.ProtocolError, errors.ProtocolError.NO_RESPONSE_FROM_HANDSHAKE):
self.client.make_connection()
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
def test_make_connection_receives_empty_handshake_response(
self, mock_start_subprocess, mock_socket_create_conn):
"""Tests make_connection receives an empty handshake response."""
socket_resp = [b'']
self._make_client_and_mock_socket_conn(mock_socket_create_conn, socket_resp)
self._mock_server_process_starting_response(mock_start_subprocess)
with self.assertRaisesRegex(
errors.ProtocolError, errors.ProtocolError.NO_RESPONSE_FROM_HANDSHAKE):
self.client.make_connection()
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
def test_make_connection_receives_invalid_handshake_response(
self, mock_start_subprocess, mock_socket_create_conn):
"""Tests make_connection receives an invalid handshake response."""
socket_resp = [b'{"status": false, "uid": 1}']
self._make_client_and_mock_socket_conn(mock_socket_create_conn, socket_resp)
self._mock_server_process_starting_response(mock_start_subprocess)
self.client.make_connection()
self.assertEqual(self.client.uid, -1)
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
def test_make_connection_send_handshake_request_error(
self, mock_start_subprocess, mock_socket_create_conn):
"""Tests that an error occurred trying to send a handshake request."""
self._make_client_and_mock_socket_conn(mock_socket_create_conn)
self._mock_server_process_starting_response(mock_start_subprocess)
self.mock_socket_file.write.side_effect = socket.error('Socket write error')
with self.assertRaisesRegex(errors.Error, 'Socket write error'):
self.client.make_connection()
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
def test_make_connection_receive_handshake_response_error(
self, mock_start_subprocess, mock_socket_create_conn):
"""Tests that an error occurred trying to receive a handshake response."""
self._make_client_and_mock_socket_conn(mock_socket_create_conn)
self._mock_server_process_starting_response(mock_start_subprocess)
self.mock_socket_file.readline.side_effect = socket.error(
'Socket read error')
with self.assertRaisesRegex(errors.Error, 'Socket read error'):
self.client.make_connection()
@mock.patch('socket.create_connection')
@mock.patch('mobly.controllers.android_device_lib.snippet_client_v2.'
'utils.start_standing_subprocess')
def test_make_connection_decode_handshake_response_bytes_error(
self, mock_start_subprocess, mock_socket_create_conn):
"""Tests that an error occurred trying to decode a handshake response."""
self._make_client_and_mock_socket_conn(mock_socket_create_conn)
self._mock_server_process_starting_response(mock_start_subprocess)
self.client.log = mock.Mock()
socket_response = bytes('{"status": false, "uid": 1}', encoding='cp037')
self.mock_socket_file.readline.side_effect = [socket_response]
with self.assertRaises(UnicodeError):
self.client.make_connection()
self.client.log.error.assert_has_calls([
mock.call(
'Failed to decode socket response bytes using encoding utf8: %s',
socket_response)
])
def test_rpc_sending_and_receiving(self):
"""Test RPC sending and receiving.
Tests that when sending and receiving an RPC the correct data is used.
"""
self._make_client()
rpc_request = '{"id": 0, "method": "some_rpc", "params": []}'
rpc_response_expected = ('{"id": 0, "result": 123, "error": null, '
'"callback": null}')
socket_write_expected = [
mock.call(b'{"id": 0, "method": "some_rpc", "params": []}\n')
]
socket_response = (b'{"id": 0, "result": 123, "error": null, '
b'"callback": null}')
mock_socket_file = mock.Mock()
mock_socket_file.readline.return_value = socket_response
self.client._client = mock_socket_file
rpc_response = self.client.send_rpc_request(rpc_request)
self.assertEqual(rpc_response, rpc_response_expected)
self.assertEqual(mock_socket_file.write.call_args_list,
socket_write_expected)
def test_rpc_send_socket_write_error(self):
"""Tests that an error occurred trying to write the socket file."""
self._make_client()
self.client._client = mock.Mock()
self.client._client.write.side_effect = socket.error('Socket write error')
rpc_request = '{"id": 0, "method": "some_rpc", "params": []}'
with self.assertRaisesRegex(errors.Error, 'Socket write error'):
self.client.send_rpc_request(rpc_request)
def test_rpc_send_socket_read_error(self):
"""Tests that an error occurred trying to read the socket file."""
self._make_client()
self.client._client = mock.Mock()
self.client._client.readline.side_effect = socket.error('Socket read error')
rpc_request = '{"id": 0, "method": "some_rpc", "params": []}'
with self.assertRaisesRegex(errors.Error, 'Socket read error'):
self.client.send_rpc_request(rpc_request)
def test_rpc_send_decode_socket_response_bytes_error(self):
"""Tests that an error occurred trying to decode the socket response."""
self._make_client()
self.client.log = mock.Mock()
self.client._client = mock.Mock()
socket_response = bytes(
'{"id": 0, "result": 123, "error": null, "callback": null}',
encoding='cp037')
self.client._client.readline.return_value = socket_response
rpc_request = '{"id": 0, "method": "some_rpc", "params": []}'
with self.assertRaises(UnicodeError):
self.client.send_rpc_request(rpc_request)
self.client.log.error.assert_has_calls([
mock.call(
'Failed to decode socket response bytes using encoding utf8: %s',
socket_response)
])
@mock.patch.object(snippet_client_v2.SnippetClientV2,
'send_handshake_request')
@mock.patch.object(snippet_client_v2.SnippetClientV2,
'create_socket_connection')
def test_make_conn_with_forwarded_port_init(self,
mock_create_socket_conn_func,
mock_send_handshake_func):
"""Tests make_connection_with_forwarded_port initiates a new session."""
self._make_client()
self.client._counter = None
self.client.make_connection_with_forwarded_port(12345, 54321)
self.assertEqual(self.client.host_port, 12345)
self.assertEqual(self.client.device_port, 54321)
self.assertEqual(next(self.client._counter), 0)
mock_create_socket_conn_func.assert_called_once_with()
mock_send_handshake_func.assert_called_once_with(
-1, snippet_client_v2.ConnectionHandshakeCommand.INIT)
@mock.patch.object(snippet_client_v2.SnippetClientV2,
'send_handshake_request')
@mock.patch.object(snippet_client_v2.SnippetClientV2,
'create_socket_connection')
def test_make_conn_with_forwarded_port_continue(self,
mock_create_socket_conn_func,
mock_send_handshake_func):
"""Tests make_connection_with_forwarded_port continues current session."""
self._make_client()
self.client._counter = None
self.client.make_connection_with_forwarded_port(
12345, 54321, 3, snippet_client_v2.ConnectionHandshakeCommand.CONTINUE)
self.assertEqual(self.client.host_port, 12345)
self.assertEqual(self.client.device_port, 54321)
self.assertEqual(next(self.client._counter), 0)
mock_create_socket_conn_func.assert_called_once_with()
mock_send_handshake_func.assert_called_once_with(
3, snippet_client_v2.ConnectionHandshakeCommand.CONTINUE)
if __name__ == '__main__':
unittest.main()