We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Wrong msg in snippet client's exception
Expected device id, but got snippet client `repr` in an error msg.

```
  File "mobly/controllers/android_device_lib/callback_handler.py", line 94, in waitAndGet
    self._id, event_name, timeout_ms)
  File "mobly/controllers/android_device_lib/jsonrpc_client_base.py", line 295, in rpc_call
    return self._rpc(name, *args)
  File "mobly/controllers/android_device_lib/jsonrpc_client_base.py", line 274, in _rpc
    ProtocolError.NO_RESPONSE_FROM_SERVER)
ProtocolError: <mobly.controllers.android_device_lib.snippet_client.SnippetClient object at 0x20c4490> No response from server.
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mobly/controllers/android_device_lib/snippet_client.py`
Content:
```
1 # Copyright 2016 Google Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 """JSON RPC interface to Mobly Snippet Lib."""
15 import logging
16 import re
17 import time
18 
19 from mobly import utils
20 from mobly.controllers.android_device_lib import adb
21 from mobly.controllers.android_device_lib import errors
22 from mobly.controllers.android_device_lib import jsonrpc_client_base
23 
24 _INSTRUMENTATION_RUNNER_PACKAGE = (
25     'com.google.android.mobly.snippet.SnippetRunner')
26 
27 # Major version of the launch and communication protocol being used by this
28 # client.
29 # Incrementing this means that compatibility with clients using the older
30 # version is broken. Avoid breaking compatibility unless there is no other
31 # choice.
32 _PROTOCOL_MAJOR_VERSION = 1
33 
34 # Minor version of the launch and communication protocol.
35 # Increment this when new features are added to the launch and communication
36 # protocol that are backwards compatible with the old protocol and don't break
37 # existing clients.
38 _PROTOCOL_MINOR_VERSION = 0
39 
40 _LAUNCH_CMD = ('%s am instrument -w -e action start %s/' +
41                _INSTRUMENTATION_RUNNER_PACKAGE)
42 
43 _STOP_CMD = (
44     'am instrument -w -e action stop %s/' + _INSTRUMENTATION_RUNNER_PACKAGE)
45 
46 # Test that uses UiAutomation requires the shell session to be maintained while
47 # test is in progress. However, this requirement does not hold for the test that
48 # deals with device USB disconnection (Once device disconnects, the shell
49 # session that started the instrument ends, and UiAutomation fails with error:
50 # "UiAutomation not connected"). To keep the shell session and redirect
51 # stdin/stdout/stderr, use "setsid" or "nohup" while launching the
52 # instrumentation test. Because these commands may not be available in every
53 # android system, try to use them only if exists.
54 _SETSID_COMMAND = 'setsid'
55 
56 _NOHUP_COMMAND = 'nohup'
57 
58 
59 class ProtocolVersionError(jsonrpc_client_base.AppStartError):
60     """Raised when the protocol reported by the snippet is unknown."""
61 
62 
63 class SnippetClient(jsonrpc_client_base.JsonRpcClientBase):
64     """A client for interacting with snippet APKs using Mobly Snippet Lib.
65 
66     See superclass documentation for a list of public attributes.
67 
68     For a description of the launch protocols, see the documentation in
69     mobly-snippet-lib, SnippetRunner.java.
70     """
71 
72     def __init__(self, package, ad):
73         """Initializes a SnippetClient.
74 
75         Args:
76             package: (str) The package name of the apk where the snippets are
77                 defined.
78             ad: (AndroidDevice) the device object associated with this client.
79         """
80         super(SnippetClient, self).__init__(app_name=package, ad=ad)
81         self.package = package
82         self._ad = ad
83         self._adb = ad.adb
84         self._proc = None
85 
86     def start_app_and_connect(self):
87         """Overrides superclass. Launches a snippet app and connects to it."""
88         self._check_app_installed()
89 
90         persists_shell_cmd = self._get_persist_command()
91         # Use info here so people can follow along with the snippet startup
92         # process. Starting snippets can be slow, especially if there are
93         # multiple, and this avoids the perception that the framework is hanging
94         # for a long time doing nothing.
95         self.log.info('Launching snippet apk %s with protocol %d.%d',
96                       self.package, _PROTOCOL_MAJOR_VERSION,
97                       _PROTOCOL_MINOR_VERSION)
98         cmd = _LAUNCH_CMD % (persists_shell_cmd, self.package)
99         start_time = time.time()
100         self._proc = self._do_start_app(cmd)
101 
102         # Check protocol version and get the device port
103         line = self._read_protocol_line()
104         match = re.match('^SNIPPET START, PROTOCOL ([0-9]+) ([0-9]+)$', line)
105         if not match or match.group(1) != '1':
106             raise ProtocolVersionError(self._ad, line)
107 
108         line = self._read_protocol_line()
109         match = re.match('^SNIPPET SERVING, PORT ([0-9]+)$', line)
110         if not match:
111             raise ProtocolVersionError(self._ad, line)
112         self.device_port = int(match.group(1))
113 
114         # Forward the device port to a new host port, and connect to that port
115         self.host_port = utils.get_available_host_port()
116         self._adb.forward(
117             ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])
118         self.connect()
119 
120         # Yaaay! We're done!
121         self.log.debug('Snippet %s started after %.1fs on host port %s',
122                        self.package, time.time() - start_time, self.host_port)
123 
124     def restore_app_connection(self, port=None):
125         """Restores the app after device got reconnected.
126 
127         Instead of creating new instance of the client:
128           - Uses the given port (or find a new available host_port if none is
129             given).
130           - Tries to connect to remote server with selected port.
131 
132         Args:
133           port: If given, this is the host port from which to connect to remote
134               device port. If not provided, find a new available port as host
135               port.
136 
137         Raises:
138             AppRestoreConnectionError: When the app was not able to be started.
139         """
140         self.host_port = port or utils.get_available_host_port()
141         self._adb.forward(
142             ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])
143         try:
144             self.connect()
145         except:
146             # Failed to connect to app, something went wrong.
147             raise jsonrpc_client_base.AppRestoreConnectionError(self._ad
148                 ('Failed to restore app connection for %s at host port %s, '
149                  'device port %s'), self.package, self.host_port,
150                 self.device_port)
151 
152         # Because the previous connection was lost, update self._proc
153         self._proc = None
154         self._restore_event_client()
155 
156     def stop_app(self):
157         # Kill the pending 'adb shell am instrument -w' process if there is one.
158         # Although killing the snippet apk would abort this process anyway, we
159         # want to call stop_standing_subprocess() to perform a health check,
160         # print the failure stack trace if there was any, and reap it from the
161         # process table.
162         self.log.debug('Stopping snippet apk %s', self.package)
163         try:
164             # Close the socket connection.
165             self.disconnect()
166             if self._proc:
167                 utils.stop_standing_subprocess(self._proc)
168             out = self._adb.shell(_STOP_CMD % self.package).decode('utf-8')
169             if 'OK (0 tests)' not in out:
170                 raise errors.DeviceError(self._ad,
171                     'Failed to stop existing apk. Unexpected output: %s' % out)
172         finally:
173             # Always clean up the adb port
174             if self.host_port:
175                 self._adb.forward(['--remove', 'tcp:%d' % self.host_port])
176 
177     def _start_event_client(self):
178         """Overrides superclass."""
179         event_client = SnippetClient(package=self.package, ad=self)
180         event_client.host_port = self.host_port
181         event_client.device_port = self.device_port
182         event_client.connect(self.uid,
183                              jsonrpc_client_base.JsonRpcCommand.CONTINUE)
184         return event_client
185 
186     def _restore_event_client(self):
187         """Restores previously created event client."""
188         if not self._event_client:
189             self._event_client = self._start_event_client()
190             return
191         self._event_client.host_port = self.host_port
192         self._event_client.device_port = self.device_port
193         self._event_client.connect()
194 
195     def _check_app_installed(self):
196         # Check that the Mobly Snippet app is installed.
197         out = self._adb.shell('pm list package')
198         if not utils.grep('^package:%s$' % self.package, out):
199             raise jsonrpc_client_base.AppStartError(
200                 self._ad, '%s is not installed.' % self.package)
201         # Check that the app is instrumented.
202         out = self._adb.shell('pm list instrumentation')
203         matched_out = utils.grep('^instrumentation:%s/%s' %
204                                  (self.package,
205                                   _INSTRUMENTATION_RUNNER_PACKAGE), out)
206         if not matched_out:
207             raise jsonrpc_client_base.AppStartError(self._ad,
208                 '%s is installed, but it is not instrumented.' % self.package)
209         match = re.search('^instrumentation:(.*)\/(.*) \(target=(.*)\)$',
210                           matched_out[0])
211         target_name = match.group(3)
212         # Check that the instrumentation target is installed if it's not the
213         # same as the snippet package.
214         if target_name != self.package:
215             out = self._adb.shell('pm list package')
216             if not utils.grep('^package:%s$' % target_name, out):
217                 raise jsonrpc_client_base.AppStartError(self._ad,
218                     'Instrumentation target %s is not installed.' %
219                     target_name)
220 
221     def _do_start_app(self, launch_cmd):
222         adb_cmd = [adb.ADB]
223         if self._adb.serial:
224             adb_cmd += ['-s', self._adb.serial]
225         adb_cmd += ['shell', launch_cmd]
226         return utils.start_standing_subprocess(adb_cmd, shell=False)
227 
228     def _read_protocol_line(self):
229         """Reads the next line of instrumentation output relevant to snippets.
230 
231         This method will skip over lines that don't start with 'SNIPPET' or
232         'INSTRUMENTATION_RESULT'.
233 
234         Returns:
235             (str) Next line of snippet-related instrumentation output, stripped.
236 
237         Raises:
238             jsonrpc_client_base.AppStartError: If EOF is reached without any
239                 protocol lines being read.
240         """
241         while True:
242             line = self._proc.stdout.readline().decode('utf-8')
243             if not line:
244                 raise jsonrpc_client_base.AppStartError(self._ad,
245                     'Unexpected EOF waiting for app to start')
246             # readline() uses an empty string to mark EOF, and a single newline
247             # to mark regular empty lines in the output. Don't move the strip()
248             # call above the truthiness check, or this method will start
249             # considering any blank output line to be EOF.
250             line = line.strip()
251             if (line.startswith('INSTRUMENTATION_RESULT:') or
252                     line.startswith('SNIPPET ')):
253                 self.log.debug(
254                     'Accepted line from instrumentation output: "%s"', line)
255                 return line
256             self.log.debug('Discarded line from instrumentation output: "%s"',
257                            line)
258 
259     def _get_persist_command(self):
260         """Check availability and return path of command if available."""
261         for command in [_SETSID_COMMAND, _NOHUP_COMMAND]:
262             try:
263                 if command in self._adb.shell(['which',
264                                                command]).decode('utf-8'):
265                     return command
266             except adb.AdbError:
267                 continue
268         self.log.warning(
269             'No %s and %s commands available to launch instrument '
270             'persistently, tests that depend on UiAutomator and '
271             'at the same time performs USB disconnection may fail',
272             _SETSID_COMMAND, _NOHUP_COMMAND)
273         return ''
274
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mobly/controllers/android_device_lib/snippet_client.py b/mobly/controllers/android_device_lib/snippet_client.py
--- a/mobly/controllers/android_device_lib/snippet_client.py
+++ b/mobly/controllers/android_device_lib/snippet_client.py
@@ -144,9 +144,10 @@
             self.connect()
         except:
             # Failed to connect to app, something went wrong.
-            raise jsonrpc_client_base.AppRestoreConnectionError(self._ad
-                ('Failed to restore app connection for %s at host port %s, '
-                 'device port %s'), self.package, self.host_port,
+            raise jsonrpc_client_base.AppRestoreConnectionError(
+                self._ad(
+                    'Failed to restore app connection for %s at host port %s, '
+                    'device port %s'), self.package, self.host_port,
                 self.device_port)
 
         # Because the previous connection was lost, update self._proc
@@ -167,7 +168,8 @@
                 utils.stop_standing_subprocess(self._proc)
             out = self._adb.shell(_STOP_CMD % self.package).decode('utf-8')
             if 'OK (0 tests)' not in out:
-                raise errors.DeviceError(self._ad,
+                raise errors.DeviceError(
+                    self._ad,
                     'Failed to stop existing apk. Unexpected output: %s' % out)
         finally:
             # Always clean up the adb port
@@ -176,7 +178,7 @@
 
     def _start_event_client(self):
         """Overrides superclass."""
-        event_client = SnippetClient(package=self.package, ad=self)
+        event_client = SnippetClient(package=self.package, ad=self._ad)
         event_client.host_port = self.host_port
         event_client.device_port = self.device_port
         event_client.connect(self.uid,
@@ -204,7 +206,8 @@
                                  (self.package,
                                   _INSTRUMENTATION_RUNNER_PACKAGE), out)
         if not matched_out:
-            raise jsonrpc_client_base.AppStartError(self._ad,
+            raise jsonrpc_client_base.AppStartError(
+                self._ad,
                 '%s is installed, but it is not instrumented.' % self.package)
         match = re.search('^instrumentation:(.*)\/(.*) \(target=(.*)\)$',
                           matched_out[0])
@@ -214,8 +217,8 @@
         if target_name != self.package:
             out = self._adb.shell('pm list package')
             if not utils.grep('^package:%s$' % target_name, out):
-                raise jsonrpc_client_base.AppStartError(self._ad,
-                    'Instrumentation target %s is not installed.' %
+                raise jsonrpc_client_base.AppStartError(
+                    self._ad, 'Instrumentation target %s is not installed.' %
                     target_name)
 
     def _do_start_app(self, launch_cmd):
@@ -241,8 +244,8 @@
         while True:
             line = self._proc.stdout.readline().decode('utf-8')
             if not line:
-                raise jsonrpc_client_base.AppStartError(self._ad,
-                    'Unexpected EOF waiting for app to start')
+                raise jsonrpc_client_base.AppStartError(
+                    self._ad, 'Unexpected EOF waiting for app to start')
             # readline() uses an empty string to mark EOF, and a single newline
             # to mark regular empty lines in the output. Don't move the strip()
             # call above the truthiness check, or this method will start

{"golden_diff": "diff --git a/mobly/controllers/android_device_lib/snippet_client.py b/mobly/controllers/android_device_lib/snippet_client.py\n--- a/mobly/controllers/android_device_lib/snippet_client.py\n+++ b/mobly/controllers/android_device_lib/snippet_client.py\n@@ -144,9 +144,10 @@\n             self.connect()\n         except:\n             # Failed to connect to app, something went wrong.\n-            raise jsonrpc_client_base.AppRestoreConnectionError(self._ad\n-                ('Failed to restore app connection for %s at host port %s, '\n-                 'device port %s'), self.package, self.host_port,\n+            raise jsonrpc_client_base.AppRestoreConnectionError(\n+                self._ad(\n+                    'Failed to restore app connection for %s at host port %s, '\n+                    'device port %s'), self.package, self.host_port,\n                 self.device_port)\n \n         # Because the previous connection was lost, update self._proc\n@@ -167,7 +168,8 @@\n                 utils.stop_standing_subprocess(self._proc)\n             out = self._adb.shell(_STOP_CMD % self.package).decode('utf-8')\n             if 'OK (0 tests)' not in out:\n-                raise errors.DeviceError(self._ad,\n+                raise errors.DeviceError(\n+                    self._ad,\n                     'Failed to stop existing apk. Unexpected output: %s' % out)\n         finally:\n             # Always clean up the adb port\n@@ -176,7 +178,7 @@\n \n     def _start_event_client(self):\n         \"\"\"Overrides superclass.\"\"\"\n-        event_client = SnippetClient(package=self.package, ad=self)\n+        event_client = SnippetClient(package=self.package, ad=self._ad)\n         event_client.host_port = self.host_port\n         event_client.device_port = self.device_port\n         event_client.connect(self.uid,\n@@ -204,7 +206,8 @@\n                                  (self.package,\n                                   _INSTRUMENTATION_RUNNER_PACKAGE), out)\n         if not matched_out:\n-            raise jsonrpc_client_base.AppStartError(self._ad,\n+            raise jsonrpc_client_base.AppStartError(\n+                self._ad,\n                 '%s is installed, but it is not instrumented.' % self.package)\n         match = re.search('^instrumentation:(.*)\\/(.*) \\(target=(.*)\\)$',\n                           matched_out[0])\n@@ -214,8 +217,8 @@\n         if target_name != self.package:\n             out = self._adb.shell('pm list package')\n             if not utils.grep('^package:%s$' % target_name, out):\n-                raise jsonrpc_client_base.AppStartError(self._ad,\n-                    'Instrumentation target %s is not installed.' %\n+                raise jsonrpc_client_base.AppStartError(\n+                    self._ad, 'Instrumentation target %s is not installed.' %\n                     target_name)\n \n     def _do_start_app(self, launch_cmd):\n@@ -241,8 +244,8 @@\n         while True:\n             line = self._proc.stdout.readline().decode('utf-8')\n             if not line:\n-                raise jsonrpc_client_base.AppStartError(self._ad,\n-                    'Unexpected EOF waiting for app to start')\n+                raise jsonrpc_client_base.AppStartError(\n+                    self._ad, 'Unexpected EOF waiting for app to start')\n             # readline() uses an empty string to mark EOF, and a single newline\n             # to mark regular empty lines in the output. Don't move the strip()\n             # call above the truthiness check, or this method will start\n", "issue": "Wrong msg in snippet client's exception\nExpected device id, but got snippet client `repr` in an error msg.\r\n\r\n```\r\n  File \"mobly/controllers/android_device_lib/callback_handler.py\", line 94, in waitAndGet\r\n    self._id, event_name, timeout_ms)\r\n  File \"mobly/controllers/android_device_lib/jsonrpc_client_base.py\", line 295, in rpc_call\r\n    return self._rpc(name, *args)\r\n  File \"mobly/controllers/android_device_lib/jsonrpc_client_base.py\", line 274, in _rpc\r\n    ProtocolError.NO_RESPONSE_FROM_SERVER)\r\nProtocolError: <mobly.controllers.android_device_lib.snippet_client.SnippetClient object at 0x20c4490> No response from server.\r\n```\n", "before_files": [{"content": "# Copyright 2016 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"JSON RPC interface to Mobly Snippet Lib.\"\"\"\nimport logging\nimport re\nimport time\n\nfrom mobly import utils\nfrom mobly.controllers.android_device_lib import adb\nfrom mobly.controllers.android_device_lib import errors\nfrom mobly.controllers.android_device_lib import jsonrpc_client_base\n\n_INSTRUMENTATION_RUNNER_PACKAGE = (\n    'com.google.android.mobly.snippet.SnippetRunner')\n\n# Major version of the launch and communication protocol being used by this\n# client.\n# Incrementing this means that compatibility with clients using the older\n# version is broken. Avoid breaking compatibility unless there is no other\n# choice.\n_PROTOCOL_MAJOR_VERSION = 1\n\n# Minor version of the launch and communication protocol.\n# Increment this when new features are added to the launch and communication\n# protocol that are backwards compatible with the old protocol and don't break\n# existing clients.\n_PROTOCOL_MINOR_VERSION = 0\n\n_LAUNCH_CMD = ('%s am instrument -w -e action start %s/' +\n               _INSTRUMENTATION_RUNNER_PACKAGE)\n\n_STOP_CMD = (\n    'am instrument -w -e action stop %s/' + _INSTRUMENTATION_RUNNER_PACKAGE)\n\n# Test that uses UiAutomation requires the shell session to be maintained while\n# test is in progress. However, this requirement does not hold for the test that\n# deals with device USB disconnection (Once device disconnects, the shell\n# session that started the instrument ends, and UiAutomation fails with error:\n# \"UiAutomation not connected\"). To keep the shell session and redirect\n# stdin/stdout/stderr, use \"setsid\" or \"nohup\" while launching the\n# instrumentation test. Because these commands may not be available in every\n# android system, try to use them only if exists.\n_SETSID_COMMAND = 'setsid'\n\n_NOHUP_COMMAND = 'nohup'\n\n\nclass ProtocolVersionError(jsonrpc_client_base.AppStartError):\n    \"\"\"Raised when the protocol reported by the snippet is unknown.\"\"\"\n\n\nclass SnippetClient(jsonrpc_client_base.JsonRpcClientBase):\n    \"\"\"A client for interacting with snippet APKs using Mobly Snippet Lib.\n\n    See superclass documentation for a list of public attributes.\n\n    For a description of the launch protocols, see the documentation in\n    mobly-snippet-lib, SnippetRunner.java.\n    \"\"\"\n\n    def __init__(self, package, ad):\n        \"\"\"Initializes a SnippetClient.\n\n        Args:\n            package: (str) The package name of the apk where the snippets are\n                defined.\n            ad: (AndroidDevice) the device object associated with this client.\n        \"\"\"\n        super(SnippetClient, self).__init__(app_name=package, ad=ad)\n        self.package = package\n        self._ad = ad\n        self._adb = ad.adb\n        self._proc = None\n\n    def start_app_and_connect(self):\n        \"\"\"Overrides superclass. Launches a snippet app and connects to it.\"\"\"\n        self._check_app_installed()\n\n        persists_shell_cmd = self._get_persist_command()\n        # Use info here so people can follow along with the snippet startup\n        # process. Starting snippets can be slow, especially if there are\n        # multiple, and this avoids the perception that the framework is hanging\n        # for a long time doing nothing.\n        self.log.info('Launching snippet apk %s with protocol %d.%d',\n                      self.package, _PROTOCOL_MAJOR_VERSION,\n                      _PROTOCOL_MINOR_VERSION)\n        cmd = _LAUNCH_CMD % (persists_shell_cmd, self.package)\n        start_time = time.time()\n        self._proc = self._do_start_app(cmd)\n\n        # Check protocol version and get the device port\n        line = self._read_protocol_line()\n        match = re.match('^SNIPPET START, PROTOCOL ([0-9]+) ([0-9]+)$', line)\n        if not match or match.group(1) != '1':\n            raise ProtocolVersionError(self._ad, line)\n\n        line = self._read_protocol_line()\n        match = re.match('^SNIPPET SERVING, PORT ([0-9]+)$', line)\n        if not match:\n            raise ProtocolVersionError(self._ad, line)\n        self.device_port = int(match.group(1))\n\n        # Forward the device port to a new host port, and connect to that port\n        self.host_port = utils.get_available_host_port()\n        self._adb.forward(\n            ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])\n        self.connect()\n\n        # Yaaay! We're done!\n        self.log.debug('Snippet %s started after %.1fs on host port %s',\n                       self.package, time.time() - start_time, self.host_port)\n\n    def restore_app_connection(self, port=None):\n        \"\"\"Restores the app after device got reconnected.\n\n        Instead of creating new instance of the client:\n          - Uses the given port (or find a new available host_port if none is\n            given).\n          - Tries to connect to remote server with selected port.\n\n        Args:\n          port: If given, this is the host port from which to connect to remote\n              device port. If not provided, find a new available port as host\n              port.\n\n        Raises:\n            AppRestoreConnectionError: When the app was not able to be started.\n        \"\"\"\n        self.host_port = port or utils.get_available_host_port()\n        self._adb.forward(\n            ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])\n        try:\n            self.connect()\n        except:\n            # Failed to connect to app, something went wrong.\n            raise jsonrpc_client_base.AppRestoreConnectionError(self._ad\n                ('Failed to restore app connection for %s at host port %s, '\n                 'device port %s'), self.package, self.host_port,\n                self.device_port)\n\n        # Because the previous connection was lost, update self._proc\n        self._proc = None\n        self._restore_event_client()\n\n    def stop_app(self):\n        # Kill the pending 'adb shell am instrument -w' process if there is one.\n        # Although killing the snippet apk would abort this process anyway, we\n        # want to call stop_standing_subprocess() to perform a health check,\n        # print the failure stack trace if there was any, and reap it from the\n        # process table.\n        self.log.debug('Stopping snippet apk %s', self.package)\n        try:\n            # Close the socket connection.\n            self.disconnect()\n            if self._proc:\n                utils.stop_standing_subprocess(self._proc)\n            out = self._adb.shell(_STOP_CMD % self.package).decode('utf-8')\n            if 'OK (0 tests)' not in out:\n                raise errors.DeviceError(self._ad,\n                    'Failed to stop existing apk. Unexpected output: %s' % out)\n        finally:\n            # Always clean up the adb port\n            if self.host_port:\n                self._adb.forward(['--remove', 'tcp:%d' % self.host_port])\n\n    def _start_event_client(self):\n        \"\"\"Overrides superclass.\"\"\"\n        event_client = SnippetClient(package=self.package, ad=self)\n        event_client.host_port = self.host_port\n        event_client.device_port = self.device_port\n        event_client.connect(self.uid,\n                             jsonrpc_client_base.JsonRpcCommand.CONTINUE)\n        return event_client\n\n    def _restore_event_client(self):\n        \"\"\"Restores previously created event client.\"\"\"\n        if not self._event_client:\n            self._event_client = self._start_event_client()\n            return\n        self._event_client.host_port = self.host_port\n        self._event_client.device_port = self.device_port\n        self._event_client.connect()\n\n    def _check_app_installed(self):\n        # Check that the Mobly Snippet app is installed.\n        out = self._adb.shell('pm list package')\n        if not utils.grep('^package:%s$' % self.package, out):\n            raise jsonrpc_client_base.AppStartError(\n                self._ad, '%s is not installed.' % self.package)\n        # Check that the app is instrumented.\n        out = self._adb.shell('pm list instrumentation')\n        matched_out = utils.grep('^instrumentation:%s/%s' %\n                                 (self.package,\n                                  _INSTRUMENTATION_RUNNER_PACKAGE), out)\n        if not matched_out:\n            raise jsonrpc_client_base.AppStartError(self._ad,\n                '%s is installed, but it is not instrumented.' % self.package)\n        match = re.search('^instrumentation:(.*)\\/(.*) \\(target=(.*)\\)$',\n                          matched_out[0])\n        target_name = match.group(3)\n        # Check that the instrumentation target is installed if it's not the\n        # same as the snippet package.\n        if target_name != self.package:\n            out = self._adb.shell('pm list package')\n            if not utils.grep('^package:%s$' % target_name, out):\n                raise jsonrpc_client_base.AppStartError(self._ad,\n                    'Instrumentation target %s is not installed.' %\n                    target_name)\n\n    def _do_start_app(self, launch_cmd):\n        adb_cmd = [adb.ADB]\n        if self._adb.serial:\n            adb_cmd += ['-s', self._adb.serial]\n        adb_cmd += ['shell', launch_cmd]\n        return utils.start_standing_subprocess(adb_cmd, shell=False)\n\n    def _read_protocol_line(self):\n        \"\"\"Reads the next line of instrumentation output relevant to snippets.\n\n        This method will skip over lines that don't start with 'SNIPPET' or\n        'INSTRUMENTATION_RESULT'.\n\n        Returns:\n            (str) Next line of snippet-related instrumentation output, stripped.\n\n        Raises:\n            jsonrpc_client_base.AppStartError: If EOF is reached without any\n                protocol lines being read.\n        \"\"\"\n        while True:\n            line = self._proc.stdout.readline().decode('utf-8')\n            if not line:\n                raise jsonrpc_client_base.AppStartError(self._ad,\n                    'Unexpected EOF waiting for app to start')\n            # readline() uses an empty string to mark EOF, and a single newline\n            # to mark regular empty lines in the output. Don't move the strip()\n            # call above the truthiness check, or this method will start\n            # considering any blank output line to be EOF.\n            line = line.strip()\n            if (line.startswith('INSTRUMENTATION_RESULT:') or\n                    line.startswith('SNIPPET ')):\n                self.log.debug(\n                    'Accepted line from instrumentation output: \"%s\"', line)\n                return line\n            self.log.debug('Discarded line from instrumentation output: \"%s\"',\n                           line)\n\n    def _get_persist_command(self):\n        \"\"\"Check availability and return path of command if available.\"\"\"\n        for command in [_SETSID_COMMAND, _NOHUP_COMMAND]:\n            try:\n                if command in self._adb.shell(['which',\n                                               command]).decode('utf-8'):\n                    return command\n            except adb.AdbError:\n                continue\n        self.log.warning(\n            'No %s and %s commands available to launch instrument '\n            'persistently, tests that depend on UiAutomator and '\n            'at the same time performs USB disconnection may fail',\n            _SETSID_COMMAND, _NOHUP_COMMAND)\n        return ''\n", "path": "mobly/controllers/android_device_lib/snippet_client.py"}], "after_files": [{"content": "# Copyright 2016 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"JSON RPC interface to Mobly Snippet Lib.\"\"\"\nimport logging\nimport re\nimport time\n\nfrom mobly import utils\nfrom mobly.controllers.android_device_lib import adb\nfrom mobly.controllers.android_device_lib import errors\nfrom mobly.controllers.android_device_lib import jsonrpc_client_base\n\n_INSTRUMENTATION_RUNNER_PACKAGE = (\n    'com.google.android.mobly.snippet.SnippetRunner')\n\n# Major version of the launch and communication protocol being used by this\n# client.\n# Incrementing this means that compatibility with clients using the older\n# version is broken. Avoid breaking compatibility unless there is no other\n# choice.\n_PROTOCOL_MAJOR_VERSION = 1\n\n# Minor version of the launch and communication protocol.\n# Increment this when new features are added to the launch and communication\n# protocol that are backwards compatible with the old protocol and don't break\n# existing clients.\n_PROTOCOL_MINOR_VERSION = 0\n\n_LAUNCH_CMD = ('%s am instrument -w -e action start %s/' +\n               _INSTRUMENTATION_RUNNER_PACKAGE)\n\n_STOP_CMD = (\n    'am instrument -w -e action stop %s/' + _INSTRUMENTATION_RUNNER_PACKAGE)\n\n# Test that uses UiAutomation requires the shell session to be maintained while\n# test is in progress. However, this requirement does not hold for the test that\n# deals with device USB disconnection (Once device disconnects, the shell\n# session that started the instrument ends, and UiAutomation fails with error:\n# \"UiAutomation not connected\"). To keep the shell session and redirect\n# stdin/stdout/stderr, use \"setsid\" or \"nohup\" while launching the\n# instrumentation test. Because these commands may not be available in every\n# android system, try to use them only if exists.\n_SETSID_COMMAND = 'setsid'\n\n_NOHUP_COMMAND = 'nohup'\n\n\nclass ProtocolVersionError(jsonrpc_client_base.AppStartError):\n    \"\"\"Raised when the protocol reported by the snippet is unknown.\"\"\"\n\n\nclass SnippetClient(jsonrpc_client_base.JsonRpcClientBase):\n    \"\"\"A client for interacting with snippet APKs using Mobly Snippet Lib.\n\n    See superclass documentation for a list of public attributes.\n\n    For a description of the launch protocols, see the documentation in\n    mobly-snippet-lib, SnippetRunner.java.\n    \"\"\"\n\n    def __init__(self, package, ad):\n        \"\"\"Initializes a SnippetClient.\n\n        Args:\n            package: (str) The package name of the apk where the snippets are\n                defined.\n            ad: (AndroidDevice) the device object associated with this client.\n        \"\"\"\n        super(SnippetClient, self).__init__(app_name=package, ad=ad)\n        self.package = package\n        self._ad = ad\n        self._adb = ad.adb\n        self._proc = None\n\n    def start_app_and_connect(self):\n        \"\"\"Overrides superclass. Launches a snippet app and connects to it.\"\"\"\n        self._check_app_installed()\n\n        persists_shell_cmd = self._get_persist_command()\n        # Use info here so people can follow along with the snippet startup\n        # process. Starting snippets can be slow, especially if there are\n        # multiple, and this avoids the perception that the framework is hanging\n        # for a long time doing nothing.\n        self.log.info('Launching snippet apk %s with protocol %d.%d',\n                      self.package, _PROTOCOL_MAJOR_VERSION,\n                      _PROTOCOL_MINOR_VERSION)\n        cmd = _LAUNCH_CMD % (persists_shell_cmd, self.package)\n        start_time = time.time()\n        self._proc = self._do_start_app(cmd)\n\n        # Check protocol version and get the device port\n        line = self._read_protocol_line()\n        match = re.match('^SNIPPET START, PROTOCOL ([0-9]+) ([0-9]+)$', line)\n        if not match or match.group(1) != '1':\n            raise ProtocolVersionError(self._ad, line)\n\n        line = self._read_protocol_line()\n        match = re.match('^SNIPPET SERVING, PORT ([0-9]+)$', line)\n        if not match:\n            raise ProtocolVersionError(self._ad, line)\n        self.device_port = int(match.group(1))\n\n        # Forward the device port to a new host port, and connect to that port\n        self.host_port = utils.get_available_host_port()\n        self._adb.forward(\n            ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])\n        self.connect()\n\n        # Yaaay! We're done!\n        self.log.debug('Snippet %s started after %.1fs on host port %s',\n                       self.package, time.time() - start_time, self.host_port)\n\n    def restore_app_connection(self, port=None):\n        \"\"\"Restores the app after device got reconnected.\n\n        Instead of creating new instance of the client:\n          - Uses the given port (or find a new available host_port if none is\n            given).\n          - Tries to connect to remote server with selected port.\n\n        Args:\n          port: If given, this is the host port from which to connect to remote\n              device port. If not provided, find a new available port as host\n              port.\n\n        Raises:\n            AppRestoreConnectionError: When the app was not able to be started.\n        \"\"\"\n        self.host_port = port or utils.get_available_host_port()\n        self._adb.forward(\n            ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])\n        try:\n            self.connect()\n        except:\n            # Failed to connect to app, something went wrong.\n            raise jsonrpc_client_base.AppRestoreConnectionError(\n                self._ad(\n                    'Failed to restore app connection for %s at host port %s, '\n                    'device port %s'), self.package, self.host_port,\n                self.device_port)\n\n        # Because the previous connection was lost, update self._proc\n        self._proc = None\n        self._restore_event_client()\n\n    def stop_app(self):\n        # Kill the pending 'adb shell am instrument -w' process if there is one.\n        # Although killing the snippet apk would abort this process anyway, we\n        # want to call stop_standing_subprocess() to perform a health check,\n        # print the failure stack trace if there was any, and reap it from the\n        # process table.\n        self.log.debug('Stopping snippet apk %s', self.package)\n        try:\n            # Close the socket connection.\n            self.disconnect()\n            if self._proc:\n                utils.stop_standing_subprocess(self._proc)\n            out = self._adb.shell(_STOP_CMD % self.package).decode('utf-8')\n            if 'OK (0 tests)' not in out:\n                raise errors.DeviceError(\n                    self._ad,\n                    'Failed to stop existing apk. Unexpected output: %s' % out)\n        finally:\n            # Always clean up the adb port\n            if self.host_port:\n                self._adb.forward(['--remove', 'tcp:%d' % self.host_port])\n\n    def _start_event_client(self):\n        \"\"\"Overrides superclass.\"\"\"\n        event_client = SnippetClient(package=self.package, ad=self._ad)\n        event_client.host_port = self.host_port\n        event_client.device_port = self.device_port\n        event_client.connect(self.uid,\n                             jsonrpc_client_base.JsonRpcCommand.CONTINUE)\n        return event_client\n\n    def _restore_event_client(self):\n        \"\"\"Restores previously created event client.\"\"\"\n        if not self._event_client:\n            self._event_client = self._start_event_client()\n            return\n        self._event_client.host_port = self.host_port\n        self._event_client.device_port = self.device_port\n        self._event_client.connect()\n\n    def _check_app_installed(self):\n        # Check that the Mobly Snippet app is installed.\n        out = self._adb.shell('pm list package')\n        if not utils.grep('^package:%s$' % self.package, out):\n            raise jsonrpc_client_base.AppStartError(\n                self._ad, '%s is not installed.' % self.package)\n        # Check that the app is instrumented.\n        out = self._adb.shell('pm list instrumentation')\n        matched_out = utils.grep('^instrumentation:%s/%s' %\n                                 (self.package,\n                                  _INSTRUMENTATION_RUNNER_PACKAGE), out)\n        if not matched_out:\n            raise jsonrpc_client_base.AppStartError(\n                self._ad,\n                '%s is installed, but it is not instrumented.' % self.package)\n        match = re.search('^instrumentation:(.*)\\/(.*) \\(target=(.*)\\)$',\n                          matched_out[0])\n        target_name = match.group(3)\n        # Check that the instrumentation target is installed if it's not the\n        # same as the snippet package.\n        if target_name != self.package:\n            out = self._adb.shell('pm list package')\n            if not utils.grep('^package:%s$' % target_name, out):\n                raise jsonrpc_client_base.AppStartError(\n                    self._ad, 'Instrumentation target %s is not installed.' %\n                    target_name)\n\n    def _do_start_app(self, launch_cmd):\n        adb_cmd = [adb.ADB]\n        if self._adb.serial:\n            adb_cmd += ['-s', self._adb.serial]\n        adb_cmd += ['shell', launch_cmd]\n        return utils.start_standing_subprocess(adb_cmd, shell=False)\n\n    def _read_protocol_line(self):\n        \"\"\"Reads the next line of instrumentation output relevant to snippets.\n\n        This method will skip over lines that don't start with 'SNIPPET' or\n        'INSTRUMENTATION_RESULT'.\n\n        Returns:\n            (str) Next line of snippet-related instrumentation output, stripped.\n\n        Raises:\n            jsonrpc_client_base.AppStartError: If EOF is reached without any\n                protocol lines being read.\n        \"\"\"\n        while True:\n            line = self._proc.stdout.readline().decode('utf-8')\n            if not line:\n                raise jsonrpc_client_base.AppStartError(\n                    self._ad, 'Unexpected EOF waiting for app to start')\n            # readline() uses an empty string to mark EOF, and a single newline\n            # to mark regular empty lines in the output. Don't move the strip()\n            # call above the truthiness check, or this method will start\n            # considering any blank output line to be EOF.\n            line = line.strip()\n            if (line.startswith('INSTRUMENTATION_RESULT:') or\n                    line.startswith('SNIPPET ')):\n                self.log.debug(\n                    'Accepted line from instrumentation output: \"%s\"', line)\n                return line\n            self.log.debug('Discarded line from instrumentation output: \"%s\"',\n                           line)\n\n    def _get_persist_command(self):\n        \"\"\"Check availability and return path of command if available.\"\"\"\n        for command in [_SETSID_COMMAND, _NOHUP_COMMAND]:\n            try:\n                if command in self._adb.shell(['which',\n                                               command]).decode('utf-8'):\n                    return command\n            except adb.AdbError:\n                continue\n        self.log.warning(\n            'No %s and %s commands available to launch instrument '\n            'persistently, tests that depend on UiAutomator and '\n            'at the same time performs USB disconnection may fail',\n            _SETSID_COMMAND, _NOHUP_COMMAND)\n        return ''\n", "path": "mobly/controllers/android_device_lib/snippet_client.py"}]}