xpconanfan: 



Review status: 0 of 5 files reviewed at latest revision, 1 unresolved discussion.

---

*[mobly/controllers/android_device_lib/snippet_client.py, line 142 at r1](https://reviewable.io:443/reviews/google/mobly/222#-KlQEEpQJ3ucg8uTkN-j:-KlQEEpQJ3ucg8uTkN-k:b-3m5zkq) ([raw file](https://github.com/google/mobly/blob/c20b6f1ba95f9939858dffa423fdf1bf7fef3d44/mobly/controllers/android_device_lib/snippet_client.py#L142)):*
> ```Python
>         event_client = SnippetClient(
>             package=self.package, adb_proxy=self._adb, log=self.log)
>         event_client.host_port = self.host_port
> ```

(Not sure if this is within the scope of this PR.)
Should we add a unit test that makes sure the `event_client` uses the same `host_port` as the parent client that created the event client?

---


*Comments from [Reviewable](https://reviewable.io:443/reviews/google/mobly/222)*
<!-- Sent from Reviewable.io -->

adorokhine: 



Review status: 0 of 5 files reviewed at latest revision, 1 unresolved discussion.

---

*[mobly/controllers/android_device_lib/snippet_client.py, line 142 at r1](https://reviewable.io:443/reviews/google/mobly/222#-KlQEEpQJ3ucg8uTkN-j:-KlQKWYfOqcjaBOZtZA1:b-896fix) ([raw file](https://github.com/google/mobly/blob/c20b6f1ba95f9939858dffa423fdf1bf7fef3d44/mobly/controllers/android_device_lib/snippet_client.py#L142)):*
<details><summary><i>Previously, xpconanfan (Ang Li) wrote…</i></summary><blockquote>

(Not sure if this is within the scope of this PR.)
Should we add a unit test that makes sure the `event_client` uses the same `host_port` as the parent client that created the event client?
</blockquote></details>

Done.

---


*Comments from [Reviewable](https://reviewable.io:443/reviews/google/mobly/222)*
<!-- Sent from Reviewable.io -->

xpconanfan: 



Review status: 0 of 5 files reviewed at latest revision, 1 unresolved discussion.

---

*[tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py, line 231 at r2](https://reviewable.io:443/reviews/google/mobly/222#-KlR02OhSVsXA6sC48Qq:-KlR02OhSVsXA6sC48Qr:b-y4rdz8) ([raw file](https://github.com/google/mobly/blob/4de916df19009682c2e51d5f4a5bc8e9bb5279e0/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py#L231)):*
> ```Python
> 
>     @mock.patch('socket.create_connection')
>     def test_rpc_call_async(self, mock_create_connection):
> ```

Hmm, this seems identical to `test_rpc_call_increment_counter`. Am I missing something?...

---


*Comments from [Reviewable](https://reviewable.io:443/reviews/google/mobly/222)*
<!-- Sent from Reviewable.io -->

adorokhine: 



Review status: 0 of 5 files reviewed at latest revision, 1 unresolved discussion.

---

*[tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py, line 231 at r2](https://reviewable.io:443/reviews/google/mobly/222#-KlR02OhSVsXA6sC48Qq:-KlR17AzZtpMMrBdbnHW:b5qk4v) ([raw file](https://github.com/google/mobly/blob/4de916df19009682c2e51d5f4a5bc8e9bb5279e0/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py#L231)):*
<details><summary><i>Previously, xpconanfan (Ang Li) wrote…</i></summary><blockquote>

Hmm, this seems identical to `test_rpc_call_increment_counter`. Am I missing something?...
</blockquote></details>

Whoops, good catch! I started creating a new test here but decided to put it in snippet_client instead. This code shouldn't have been committed. Done.

---


*Comments from [Reviewable](https://reviewable.io:443/reviews/google/mobly/222)*
<!-- Sent from Reviewable.io -->

xpconanfan: 



Review status: 0 of 5 files reviewed at latest revision, 1 unresolved discussion.

---

*[tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py, line 148 at r3](https://reviewable.io:443/reviews/google/mobly/222#-KlR208gYCLqbHRd_csT:-KlR208gYCLqbHRd_csU:b-creb0e) ([raw file](https://github.com/google/mobly/blob/bb85ca2966791baecca17e2cb251c09ded139d07/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py#L148)):*
> ```Python
> 
>     @mock.patch('socket.create_connection')
>     def test_connect_no_response(self, mock_create_connection):
> ```

Hmm, why is this test being removed in this PR?

---


*Comments from [Reviewable](https://reviewable.io:443/reviews/google/mobly/222)*
<!-- Sent from Reviewable.io -->

adorokhine: 



Review status: 0 of 5 files reviewed at latest revision, 1 unresolved discussion.

---

*[tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py, line 148 at r3](https://reviewable.io:443/reviews/google/mobly/222#-KlR208gYCLqbHRd_csT:-KlR2xtrn1hhmrQ9gju9:bmpx7qh) ([raw file](https://github.com/google/mobly/blob/bb85ca2966791baecca17e2cb251c09ded139d07/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py#L148)):*
<details><summary><i>Previously, xpconanfan (Ang Li) wrote…</i></summary><blockquote>

Hmm, why is this test being removed in this PR?
</blockquote></details>

`assertRaises` was being used incorrectly here (msg is what it prints if it triggers, not what it checks against). Upon fixing the bug it turned out that it's actually returning NO_RESPONSE_FROM_SERVER instead of NO_RESPONSE_FROM_HANDSHAKE, which makes sense because by the time you've finished connect, handshake has already completed.

NO_RESPONSE_FROM_SERVER is already being tested by `test_rpc_no_response`, and in fact the code seems to be the same.

Handshake errors from connect are already being tested by `test_connect_handshake`.

---


*Comments from [Reviewable](https://reviewable.io:443/reviews/google/mobly/222)*
<!-- Sent from Reviewable.io -->

xpconanfan: <img class="emoji" title=":lgtm:" alt=":lgtm:" align="absmiddle" src="https://reviewable.io/lgtm.png" height="20" width="61"/>

Thanks for fixing this!

---

Review status: 0 of 5 files reviewed at latest revision, all discussions resolved.

---



*Comments from [Reviewable](https://reviewable.io:443/reviews/google/mobly/222#-:-KlR48Z-pox3K3INMlFn:b-kym177)*
<!-- Sent from Reviewable.io -->

diff --git a/mobly/controllers/android_device_lib/snippet_client.py b/mobly/controllers/android_device_lib/snippet_client.py
index 1470927..f47308a 100644
--- a/mobly/controllers/android_device_lib/snippet_client.py
+++ b/mobly/controllers/android_device_lib/snippet_client.py
@@ -138,10 +138,8 @@ class SnippetClient(jsonrpc_client_base.JsonRpcClientBase):
     def _start_event_client(self):
         """Overrides superclass."""
         event_client = SnippetClient(
-            package=self.package,
-            host_port=self.host_port,
-            adb_proxy=self._adb,
-            log=self.log)
+            package=self.package, adb_proxy=self._adb, log=self.log)
+        event_client.host_port = self.host_port
         event_client.connect(self.uid,
                              jsonrpc_client_base.JsonRpcCommand.CONTINUE)
         return event_client

`snippet_client._start_event_client` crashes
because of the extra `host_port` arg.

This crashes all tests that use `@AsyncRpc`.

We should add some unit tests for this...
diff --git a/tests/lib/jsonrpc_client_test_base.py b/tests/lib/jsonrpc_client_test_base.py
new file mode 100755
index 0000000..c4ca7a8
--- /dev/null
+++ b/tests/lib/jsonrpc_client_test_base.py
@@ -0,0 +1,71 @@
+# 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.
+
+from builtins import str
+
+import mock
+import unittest
+
+
+class JsonRpcClientTestBase(unittest.TestCase):
+    """Base class for tests of JSONRPC clients.
+
+    Contains infrastructure for mocking responses.
+    """
+
+    MOCK_RESP = (
+        b'{"id": 0, "result": 123, "error": null, "status": 1, "uid": 1, '
+        b'"callback": null}')
+    MOCK_RESP_WITHOUT_CALLBACK = (
+        b'{"id": 0, "result": 123, "error": null, "status": 1, "uid": 1}')
+    MOCK_RESP_TEMPLATE = (
+        '{"id": %d, "result": 123, "error": null, "status": 1, "uid": 1, '
+        '"callback": null}')
+    MOCK_RESP_UNKNOWN_STATUS = (
+        b'{"id": 0, "result": 123, "error": null, "status": 0, '
+        b'"callback": null}')
+    MOCK_RESP_WITH_CALLBACK = (
+        b'{"id": 0, "result": 123, "error": null, "status": 1, "uid": 1, '
+        b'"callback": "1-0"}')
+    MOCK_RESP_WITH_ERROR = b'{"id": 0, "error": 1, "status": 1, "uid": 1}'
+
+    class MockSocketFile(object):
+        def __init__(self, resp):
+            self.resp = resp
+            self.last_write = None
+
+        def write(self, msg):
+            self.last_write = msg
+
+        def readline(self):
+            return self.resp
+
+        def flush(self):
+            pass
+
+    def setup_mock_socket_file(self, mock_create_connection, resp=MOCK_RESP):
+        """Sets up a fake socket file from the mock connection.
+
+        Args:
+            mock_create_connection: The mock method for creating a method.
+            resp: (str) response to give. MOCK_RESP by default.
+
+        Returns:
+            The mock file that will be injected into the code.
+        """
+        fake_file = self.MockSocketFile(resp)
+        fake_conn = mock.MagicMock()
+        fake_conn.makefile.return_value = fake_file
+        mock_create_connection.return_value = fake_conn
+        return fake_file
diff --git a/tests/mobly/base_test_test.py b/tests/mobly/base_test_test.py
index 58ceba1..1a2a759 100755
--- a/tests/mobly/base_test_test.py
+++ b/tests/mobly/base_test_test.py
@@ -127,8 +127,8 @@ class BaseTestTest(unittest.TestCase):
 
         bt_cls = MockBaseTest(self.mock_test_cls_configs)
         expected_msg = ('Test method name not_a_test_something does not follow '
-                        'naming convention test_*, abort.')
-        with self.assertRaises(base_test.Error, msg=expected_msg):
+                        'naming convention test_\*, abort.')
+        with self.assertRaisesRegexp(base_test.Error, expected_msg):
             bt_cls.run(test_names=["not_a_test_something"])
 
     def test_default_execution_of_all_tests(self):
@@ -790,9 +790,9 @@ class BaseTestTest(unittest.TestCase):
         """Missing a required param should raise an error."""
         required = ["something"]
         bc = base_test.BaseTestClass(self.mock_test_cls_configs)
-        expected_msg = ("Missing required user param '%s' in test "
-                        "configuration.") % required[0]
-        with self.assertRaises(base_test.Error, msg=expected_msg):
+        expected_msg = ('Missing required user param "%s" in test '
+                        'configuration.') % required[0]
+        with self.assertRaisesRegexp(base_test.Error, expected_msg):
             bc.unpack_userparams(required)
 
     def test_unpack_userparams_optional(self):
diff --git a/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py b/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py
index 3fbe8cf..7263bdc 100755
--- a/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py
+++ b/tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py
@@ -20,33 +20,7 @@ import socket
 import unittest
 
 from mobly.controllers.android_device_lib import jsonrpc_client_base
-
-MOCK_RESP = (b'{"id": 0, "result": 123, "error": null, "status": 1, "uid": 1,'
-             b' "callback": null}')
-MOCK_RESP_WITHOUT_CALLBACK = (b'{"id": 0, "result": 123, "error": null, '
-                              b'"status": 1, "uid": 1}')
-MOCK_RESP_TEMPLATE = ('{"id": %d, "result": 123, "error": null, "status": 1, '
-                      '"uid": 1, "callback": null}')
-MOCK_RESP_UNKNOWN_STATUS = (b'{"id": 0, "result": 123, "error": null, '
-                            b'"status": 0, "callback": null}')
-MOCK_RESP_WITH_CALLBACK = (b'{"id": 0, "result": 123, "error": null, '
-                           b'"status": 1, "uid": 1, "callback": "1-0"}')
-MOCK_RESP_WITH_ERROR = b'{"id": 0, "error": 1, "status": 1, "uid": 1}'
-
-
-class MockSocketFile(object):
-    def __init__(self, resp):
-        self.resp = resp
-        self.last_write = None
-
-    def write(self, msg):
-        self.last_write = msg
-
-    def readline(self):
-        return self.resp
-
-    def flush(self):
-        pass
+from tests.lib import jsonrpc_client_test_base
 
 
 class FakeRpcClient(jsonrpc_client_base.JsonRpcClientBase):
@@ -54,25 +28,10 @@ class FakeRpcClient(jsonrpc_client_base.JsonRpcClientBase):
         super(FakeRpcClient, self).__init__(app_name='FakeRpcClient')
 
 
-class JsonRpcClientBaseTest(unittest.TestCase):
+class JsonRpcClientBaseTest(jsonrpc_client_test_base.JsonRpcClientTestBase):
     """Unit tests for mobly.controllers.android_device_lib.jsonrpc_client_base.
     """
 
-    def setup_mock_socket_file(self, mock_create_connection):
-        """Sets up a fake socket file from the mock connection.
-
-        Args:
-            mock_create_connection: The mock method for creating a method.
-
-        Returns:
-            The mock file that will be injected into the code.
-        """
-        fake_file = MockSocketFile(MOCK_RESP)
-        fake_conn = mock.MagicMock()
-        fake_conn.makefile.return_value = fake_file
-        mock_create_connection.return_value = fake_conn
-        return fake_file
-
     @mock.patch('socket.create_connection')
     def test_open_timeout_io_error(self, mock_create_connection):
         """Test socket timeout with io error
@@ -104,11 +63,11 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         Test that if there is an error in the jsonrpc handshake then a protocol
         error will be raised.
         """
-        fake_conn = mock.MagicMock()
-        fake_conn.makefile.return_value = MockSocketFile(None)
-        mock_create_connection.return_value = fake_conn
-        with self.assertRaises(jsonrpc_client_base.ProtocolError):
-            client = FakeRpcClient()
+        self.setup_mock_socket_file(mock_create_connection, resp=None)
+        client = FakeRpcClient()
+        with self.assertRaisesRegexp(
+                jsonrpc_client_base.ProtocolError,
+                jsonrpc_client_base.ProtocolError.NO_RESPONSE_FROM_HANDSHAKE):
             client.connect()
 
     @mock.patch('socket.create_connection')
@@ -118,13 +77,9 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         Test that at the end of a handshake with no errors the client object
         has the correct parameters.
         """
-        fake_conn = mock.MagicMock()
-        fake_conn.makefile.return_value = MockSocketFile(MOCK_RESP)
-        mock_create_connection.return_value = fake_conn
-
+        self.setup_mock_socket_file(mock_create_connection)
         client = FakeRpcClient()
         client.connect()
-
         self.assertEqual(client.uid, 1)
 
     @mock.patch('socket.create_connection')
@@ -134,36 +89,12 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         Test that when the handshake is given an unknown status then the client
         will not be given a uid.
         """
-        fake_conn = mock.MagicMock()
-        fake_conn.makefile.return_value = MockSocketFile(
-            MOCK_RESP_UNKNOWN_STATUS)
-        mock_create_connection.return_value = fake_conn
-
+        self.setup_mock_socket_file(
+            mock_create_connection, resp=self.MOCK_RESP_UNKNOWN_STATUS)
         client = FakeRpcClient()
         client.connect()
-
         self.assertEqual(client.uid, jsonrpc_client_base.UNKNOWN_UID)
 
-    @mock.patch('socket.create_connection')
-    def test_connect_no_response(self, mock_create_connection):
-        """Test handshake no response
-
-        Test that if a handshake recieves no response then it will give a
-        protocol error.
-        """
-        fake_file = self.setup_mock_socket_file(mock_create_connection)
-
-        client = FakeRpcClient()
-        client.connect()
-
-        fake_file.resp = None
-
-        with self.assertRaises(
-                jsonrpc_client_base.ProtocolError,
-                msg=
-                jsonrpc_client_base.ProtocolError.NO_RESPONSE_FROM_HANDSHAKE):
-            client.some_rpc(1, 2, 3)
-
     @mock.patch('socket.create_connection')
     def test_rpc_error_response(self, mock_create_connection):
         """Test rpc that is given an error response
@@ -176,9 +107,9 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         client = FakeRpcClient()
         client.connect()
 
-        fake_file.resp = MOCK_RESP_WITH_ERROR
+        fake_file.resp = self.MOCK_RESP_WITH_ERROR
 
-        with self.assertRaises(jsonrpc_client_base.ApiError, msg=1):
+        with self.assertRaisesRegexp(jsonrpc_client_base.ApiError, '1'):
             client.some_rpc(1, 2, 3)
 
     @mock.patch('socket.create_connection')
@@ -193,7 +124,7 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         client = FakeRpcClient()
         client.connect()
 
-        fake_file.resp = MOCK_RESP_WITH_CALLBACK
+        fake_file.resp = self.MOCK_RESP_WITH_CALLBACK
         client._event_client = mock.Mock()
 
         callback = client.some_rpc(1, 2, 3)
@@ -212,11 +143,11 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         client = FakeRpcClient()
         client.connect()
 
-        fake_file.resp = (MOCK_RESP_TEMPLATE % 52).encode('utf8')
+        fake_file.resp = (self.MOCK_RESP_TEMPLATE % 52).encode('utf8')
 
-        with self.assertRaises(
+        with self.assertRaisesRegexp(
                 jsonrpc_client_base.ProtocolError,
-                msg=jsonrpc_client_base.ProtocolError.MISMATCHED_API_ID):
+                jsonrpc_client_base.ProtocolError.MISMATCHED_API_ID):
             client.some_rpc(1, 2, 3)
 
     @mock.patch('socket.create_connection')
@@ -233,9 +164,9 @@ class JsonRpcClientBaseTest(unittest.TestCase):
 
         fake_file.resp = None
 
-        with self.assertRaises(
+        with self.assertRaisesRegexp(
                 jsonrpc_client_base.ProtocolError,
-                msg=jsonrpc_client_base.ProtocolError.NO_RESPONSE_FROM_SERVER):
+                jsonrpc_client_base.ProtocolError.NO_RESPONSE_FROM_SERVER):
             client.some_rpc(1, 2, 3)
 
     @mock.patch('socket.create_connection')
@@ -265,10 +196,8 @@ class JsonRpcClientBaseTest(unittest.TestCase):
 
         Logic is the same as test_rpc_send_to_socket.
         """
-        fake_file = MockSocketFile(MOCK_RESP_WITHOUT_CALLBACK)
-        fake_conn = mock.MagicMock()
-        fake_conn.makefile.return_value = fake_file
-        mock_create_connection.return_value = fake_conn
+        fake_file = self.setup_mock_socket_file(
+            mock_create_connection, resp=self.MOCK_RESP_WITHOUT_CALLBACK)
 
         client = FakeRpcClient()
         client.connect()
@@ -293,11 +222,11 @@ class JsonRpcClientBaseTest(unittest.TestCase):
         client.connect()
 
         for i in range(0, 10):
-            fake_file.resp = (MOCK_RESP_TEMPLATE % i).encode('utf-8')
+            fake_file.resp = (self.MOCK_RESP_TEMPLATE % i).encode('utf-8')
             client.some_rpc()
 
         self.assertEquals(next(client._counter), 10)
 
 
-if __name__ == "__main__":
+if __name__ == '__main__':
     unittest.main()
diff --git a/tests/mobly/controllers/android_device_lib/snippet_client_test.py b/tests/mobly/controllers/android_device_lib/snippet_client_test.py
index c5b7df4..911f53e 100755
--- a/tests/mobly/controllers/android_device_lib/snippet_client_test.py
+++ b/tests/mobly/controllers/android_device_lib/snippet_client_test.py
@@ -20,10 +20,11 @@ import unittest
 
 from mobly.controllers.android_device_lib import jsonrpc_client_base
 from mobly.controllers.android_device_lib import snippet_client
+from tests.lib import jsonrpc_client_test_base
 
 MOCK_PACKAGE_NAME = 'some.package.name'
 MOCK_MISSING_PACKAGE_NAME = 'not.installed'
-JSONRPC_BASE_PACKAGE = 'mobly.controllers.android_device_lib.jsonrpc_client_base.JsonRpcClientBase'
+JSONRPC_BASE_CLASS = 'mobly.controllers.android_device_lib.jsonrpc_client_base.JsonRpcClientBase'
 
 
 class MockAdbProxy(object):
@@ -63,47 +64,62 @@ class MockAdbProxy(object):
         return adb_call
 
 
-class JsonRpcClientBaseTest(unittest.TestCase):
+class SnippetClientTest(jsonrpc_client_test_base.JsonRpcClientTestBase):
     """Unit tests for mobly.controllers.android_device_lib.snippet_client.
     """
 
-    @mock.patch('socket.create_connection')
-    @mock.patch(JSONRPC_BASE_PACKAGE)
-    def test_check_app_installed_normal(self, mock_create_connection,
-                                        mock_client_base):
+    def test_check_app_installed_normal(self):
         sc = self._make_client()
         sc._check_app_installed()
 
-    @mock.patch('socket.create_connection')
-    @mock.patch(JSONRPC_BASE_PACKAGE)
-    def test_check_app_installed_fail_app_not_installed(
-            self, mock_create_connection, mock_client_base):
+    def test_check_app_installed_fail_app_not_installed(self):
         sc = self._make_client(MockAdbProxy(apk_not_installed=True))
         expected_msg = '%s is not installed on .*' % MOCK_PACKAGE_NAME
         with self.assertRaisesRegexp(jsonrpc_client_base.AppStartError,
                                      expected_msg):
             sc._check_app_installed()
 
-    @mock.patch('socket.create_connection')
-    @mock.patch(JSONRPC_BASE_PACKAGE)
-    def test_check_app_installed_fail_not_instrumented(
-            self, mock_create_connection, mock_client_base):
+    def test_check_app_installed_fail_not_instrumented(self):
         sc = self._make_client(MockAdbProxy(apk_not_instrumented=True))
         expected_msg = '%s is installed on .*, but it is not instrumented.' % MOCK_PACKAGE_NAME
         with self.assertRaisesRegexp(jsonrpc_client_base.AppStartError,
                                      expected_msg):
             sc._check_app_installed()
 
-    @mock.patch('socket.create_connection')
-    @mock.patch(JSONRPC_BASE_PACKAGE)
-    def test_check_app_installed_fail_target_not_installed(
-            self, mock_create_connection, mock_client_base):
+    def test_check_app_installed_fail_target_not_installed(self):
         sc = self._make_client(MockAdbProxy(target_not_installed=True))
         expected_msg = 'Instrumentation target %s is not installed on .*' % MOCK_MISSING_PACKAGE_NAME
         with self.assertRaisesRegexp(jsonrpc_client_base.AppStartError,
                                      expected_msg):
             sc._check_app_installed()
 
+    @mock.patch('socket.create_connection')
+    def test_snippet_start(self, mock_create_connection):
+        self.setup_mock_socket_file(mock_create_connection)
+        client = self._make_client()
+        client.connect()
+        result = client.testSnippetCall()
+        self.assertEqual(123, result)
+
+    @mock.patch('socket.create_connection')
+    def test_snippet_start_event_client(self, mock_create_connection):
+        fake_file = self.setup_mock_socket_file(mock_create_connection)
+        client = self._make_client()
+        client.host_port = 123  # normally picked by start_app_and_connect
+        client.connect()
+        fake_file.resp = self.MOCK_RESP_WITH_CALLBACK
+        callback = client.testSnippetCall()
+        self.assertEqual(123, callback.ret_value)
+        self.assertEqual('1-0', callback._id)
+
+        # Check to make sure the event client is using the same port as the
+        # main client.
+        self.assertEqual(123, callback._event_client.host_port)
+
+        fake_file.resp = self.MOCK_RESP_WITH_ERROR
+        with self.assertRaisesRegexp(jsonrpc_client_base.ApiError, '1'):
+            callback.getAll('eventName')
+
     def _make_client(self, adb_proxy=MockAdbProxy()):
         return snippet_client.SnippetClient(
             package=MOCK_PACKAGE_NAME, adb_proxy=adb_proxy)

pip install -e .
parse_log_pytest
pytest
apt-get update apt-get install -y adb
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup==1.2.2
future==1.0.0
iniconfig==2.1.0
-e git+https://github.com/google/mobly.git@bb85ca2966791baecca17e2cb251c09ded139d07#egg=mobly
mock==1.0.1
packaging==24.2
pluggy==1.5.0
portpicker==1.6.0
psutil==7.0.0
pytest==8.3.5
pytz==2025.2
PyYAML==6.0.2
timeout-decorator==0.5.0
tomli==2.2.1

name: mobly
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - exceptiongroup==1.2.2
      - future==1.0.0
      - iniconfig==2.1.0
      - mock==1.0.1
      - packaging==24.2
      - pluggy==1.5.0
      - portpicker==1.6.0
      - psutil==7.0.0
      - pytest==8.3.5
      - pytz==2025.2
      - pyyaml==6.0.2
      - timeout-decorator==0.5.0
      - tomli==2.2.1
prefix: /opt/conda/envs/mobly

tests/mobly/controllers/android_device_lib/snippet_client_test.py::SnippetClientTest::test_snippet_start_event_client
tests/mobly/base_test_test.py::BaseTestTest::test_abort_class tests/mobly/base_test_test.py::BaseTestTest::test_assert_equal_fail tests/mobly/base_test_test.py::BaseTestTest::test_assert_equal_fail_with_msg tests/mobly/base_test_test.py::BaseTestTest::test_assert_equal_pass tests/mobly/base_test_test.py::BaseTestTest::test_assert_raises_fail_with_noop tests/mobly/base_test_test.py::BaseTestTest::test_assert_raises_fail_with_wrong_error tests/mobly/base_test_test.py::BaseTestTest::test_assert_raises_fail_with_wrong_regex tests/mobly/base_test_test.py::BaseTestTest::test_assert_raises_pass tests/mobly/base_test_test.py::BaseTestTest::test_assert_raises_regex_fail_with_noop tests/mobly/base_test_test.py::BaseTestTest::test_assert_raises_regex_fail_with_wrong_error tests/mobly/base_test_test.py::BaseTestTest::test_assert_raises_regex_pass tests/mobly/base_test_test.py::BaseTestTest::test_assert_true tests/mobly/base_test_test.py::BaseTestTest::test_both_teardown_and_test_body_raise_exceptions tests/mobly/base_test_test.py::BaseTestTest::test_cli_test_selection_fail_by_convention tests/mobly/base_test_test.py::BaseTestTest::test_cli_test_selection_override_self_tests_list tests/mobly/base_test_test.py::BaseTestTest::test_current_test_name tests/mobly/base_test_test.py::BaseTestTest::test_default_execution_of_all_tests tests/mobly/base_test_test.py::BaseTestTest::test_explicit_pass tests/mobly/base_test_test.py::BaseTestTest::test_explicit_pass_but_teardown_test_raises_an_exception tests/mobly/base_test_test.py::BaseTestTest::test_fail tests/mobly/base_test_test.py::BaseTestTest::test_failure_in_procedure_functions_is_recorded tests/mobly/base_test_test.py::BaseTestTest::test_failure_to_call_procedure_function_is_recorded tests/mobly/base_test_test.py::BaseTestTest::test_generate_tests_call_outside_of_setup_generated_tests tests/mobly/base_test_test.py::BaseTestTest::test_generate_tests_dup_test_name tests/mobly/base_test_test.py::BaseTestTest::test_generate_tests_run tests/mobly/base_test_test.py::BaseTestTest::test_generate_tests_selected_run tests/mobly/base_test_test.py::BaseTestTest::test_generated_tests tests/mobly/base_test_test.py::BaseTestTest::test_implicit_pass tests/mobly/base_test_test.py::BaseTestTest::test_missing_requested_test_func tests/mobly/base_test_test.py::BaseTestTest::test_on_fail_executed_if_teardown_test_fails tests/mobly/base_test_test.py::BaseTestTest::test_on_fail_executed_if_test_fails tests/mobly/base_test_test.py::BaseTestTest::test_on_fail_executed_if_test_setup_fails_by_exception tests/mobly/base_test_test.py::BaseTestTest::test_on_fail_raise_exception tests/mobly/base_test_test.py::BaseTestTest::test_on_pass_raise_exception tests/mobly/base_test_test.py::BaseTestTest::test_self_tests_list tests/mobly/base_test_test.py::BaseTestTest::test_self_tests_list_fail_by_convention tests/mobly/base_test_test.py::BaseTestTest::test_setup_class_fail_by_exception tests/mobly/base_test_test.py::BaseTestTest::test_setup_test_fail_by_exception tests/mobly/base_test_test.py::BaseTestTest::test_setup_test_fail_by_test_signal tests/mobly/base_test_test.py::BaseTestTest::test_skip tests/mobly/base_test_test.py::BaseTestTest::test_skip_if tests/mobly/base_test_test.py::BaseTestTest::test_teardown_test_assert_fail tests/mobly/base_test_test.py::BaseTestTest::test_teardown_test_executed_if_setup_test_fails tests/mobly/base_test_test.py::BaseTestTest::test_teardown_test_executed_if_test_fails tests/mobly/base_test_test.py::BaseTestTest::test_teardown_test_executed_if_test_pass tests/mobly/base_test_test.py::BaseTestTest::test_teardown_test_raise_exception tests/mobly/base_test_test.py::BaseTestTest::test_uncaught_exception tests/mobly/base_test_test.py::BaseTestTest::test_unpack_userparams_basic tests/mobly/base_test_test.py::BaseTestTest::test_unpack_userparams_default_None tests/mobly/base_test_test.py::BaseTestTest::test_unpack_userparams_default_overwrite tests/mobly/base_test_test.py::BaseTestTest::test_unpack_userparams_default_overwrite_by_optional_param_list tests/mobly/base_test_test.py::BaseTestTest::test_unpack_userparams_default_overwrite_by_required_param_list tests/mobly/base_test_test.py::BaseTestTest::test_unpack_userparams_optional tests/mobly/base_test_test.py::BaseTestTest::test_unpack_userparams_optional_missing tests/mobly/base_test_test.py::BaseTestTest::test_unpack_userparams_optional_with_default tests/mobly/base_test_test.py::BaseTestTest::test_unpack_userparams_required tests/mobly/base_test_test.py::BaseTestTest::test_unpack_userparams_required_missing tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_connect_handshake tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_connect_handshake_unknown_status tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_connect_timeout tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_handshake_error tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_open_timeout_io_error tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_call_increment_counter tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_callback_response tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_error_response tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_id_mismatch tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_no_response tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_send_to_socket tests/mobly/controllers/android_device_lib/jsonrpc_client_base_test.py::JsonRpcClientBaseTest::test_rpc_send_to_socket_without_callback tests/mobly/controllers/android_device_lib/snippet_client_test.py::SnippetClientTest::test_check_app_installed_fail_app_not_installed tests/mobly/controllers/android_device_lib/snippet_client_test.py::SnippetClientTest::test_check_app_installed_fail_not_instrumented tests/mobly/controllers/android_device_lib/snippet_client_test.py::SnippetClientTest::test_check_app_installed_fail_target_not_installed tests/mobly/controllers/android_device_lib/snippet_client_test.py::SnippetClientTest::test_check_app_installed_normal tests/mobly/controllers/android_device_lib/snippet_client_test.py::SnippetClientTest::test_snippet_start
mobly/controllers/android_device_lib/snippet_client.py
mobly/controllers/android_device_lib/snippet_client.py