We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
`snippet_shell` is broken for python3
```
$ snippet_shell.py --mbs
s.logINFO:root:[AndroidDevice|HT67K0300103] Launching snippet apk com.google.android.mobly.snippet.bundled with protocol v1
VTraceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/bin/snippet_shell.py", line 6, in <module>
    exec(compile(open(__file__).read(), __file__, 'exec'))
  File "/Users/angli/Developer/mobly/tools/snippet_shell.py", line 84, in <module>
    SnippetShell(package).main(args.serial)
  File "/Users/angli/Developer/mobly/mobly/controllers/android_device_lib/jsonrpc_shell_base.py", line 88, in main
    self.start_console()
  File "/Users/angli/Developer/mobly/mobly/controllers/android_device_lib/jsonrpc_shell_base.py", line 73, in start_console
    self._start_services(console_env)
  File "/Users/angli/Developer/mobly/tools/snippet_shell.py", line 43, in _start_services
    self._ad.load_snippet(name='snippet', package=self._package)
  File "/Users/angli/Developer/mobly/mobly/controllers/android_device.py", line 646, in load_snippet
    client.start_app_and_connect()
  File "/Users/angli/Developer/mobly/mobly/controllers/android_device_lib/snippet_client.py", line 112, in start_app_and_connect
    line)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/re.py", line 172, in match
    return _compile(pattern, flags).match(string)
TypeError: cannot use a string pattern on a bytes-like object
```

--- 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 jsonrpc_client_base
22 
23 _INSTRUMENTATION_RUNNER_PACKAGE = (
24     'com.google.android.mobly.snippet.SnippetRunner')
25 
26 # TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is removed.
27 _LAUNCH_CMD_V0 = ('am instrument -w -e action start -e port %s %s/' +
28                   _INSTRUMENTATION_RUNNER_PACKAGE)
29 
30 _LAUNCH_CMD_V1 = (
31     'am instrument -w -e action start %s/' + _INSTRUMENTATION_RUNNER_PACKAGE)
32 
33 _STOP_CMD = (
34     'am instrument -w -e action stop %s/' + _INSTRUMENTATION_RUNNER_PACKAGE)
35 
36 # Maximum time to wait for a v0 snippet to start on the device (10 minutes).
37 # TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is removed.
38 _APP_START_WAIT_TIME_V0 = 10 * 60
39 
40 
41 class Error(Exception):
42     pass
43 
44 
45 class ProtocolVersionError(Error):
46     """Raised when the protocol reported by the snippet is unknown."""
47 
48 
49 class SnippetClient(jsonrpc_client_base.JsonRpcClientBase):
50     """A client for interacting with snippet APKs using Mobly Snippet Lib.
51 
52     See superclass documentation for a list of public attributes.
53 
54     It currently supports both v0 and v1 snippet launch protocols, although
55     support for v0 will be removed in a future version.
56 
57     For a description of the launch protocols, see the documentation in
58     mobly-snippet-lib, SnippetRunner.java.
59     """
60 
61     def __init__(self, package, adb_proxy, log=logging.getLogger()):
62         """Initializes a SnippetClient.
63   
64         Args:
65             package: (str) The package name of the apk where the snippets are
66                      defined.
67             adb_proxy: (adb.AdbProxy) Adb proxy for running adb commands.
68             log: (logging.Logger) logger to which to send log messages.
69         """
70         super(SnippetClient, self).__init__(app_name=package, log=log)
71         self.package = package
72         self._adb = adb_proxy
73         self._proc = None
74 
75     def start_app_and_connect(self):
76         """Overrides superclass. Launches a snippet app and connects to it."""
77         self._check_app_installed()
78 
79         # Try launching the app with the v1 protocol. If that fails, fall back
80         # to v0 for compatibility. Use info here so people know exactly what's
81         # happening here, which is helpful since they need to create their own
82         # instrumentations and manifest.
83         self.log.info('Launching snippet apk %s with protocol v1',
84                       self.package)
85         cmd = _LAUNCH_CMD_V1 % self.package
86         start_time = time.time()
87         self._proc = self._do_start_app(cmd)
88 
89         # "Instrumentation crashed" could be due to several reasons, eg
90         # exception thrown during startup or just a launch protocol 0 snippet
91         # dying because it needs the port flag. Sadly we have no way to tell so
92         # just warn and retry as v0.
93         # TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is
94         # removed.
95         line = self._read_line()
96         if line in ('INSTRUMENTATION_RESULT: shortMsg=Process crashed.',
97                     'INSTRUMENTATION_RESULT: shortMsg='
98                     'java.lang.IllegalArgumentException'):
99             self.log.warning('Snippet %s crashed on startup. This might be an '
100                              'actual error or a snippet using deprecated v0 '
101                              'start protocol. Retrying as a v0 snippet.',
102                              self.package)
103             self.host_port = utils.get_available_host_port()
104             # Reuse the host port as the device port in v0 snippet. This isn't
105             # safe in general, but the protocol is deprecated.
106             cmd = _LAUNCH_CMD_V0 % (self.host_port, self.package)
107             self._proc = self._do_start_app(cmd)
108             self._connect_to_v0()
109         else:
110             # Check protocol version and get the device port
111             match = re.match('^SNIPPET START, PROTOCOL ([0-9]+) ([0-9]+)$',
112                              line)
113             if not match or match.group(1) != '1':
114                 raise ProtocolVersionError(line)
115             self._connect_to_v1()
116         self.log.debug('Snippet %s started after %.1fs on host port %s',
117                        self.package, time.time() - start_time, self.host_port)
118 
119     def stop_app(self):
120         # Kill the pending 'adb shell am instrument -w' process if there is one.
121         # Although killing the snippet apk would abort this process anyway, we
122         # want to call stop_standing_subprocess() to perform a health check,
123         # print the failure stack trace if there was any, and reap it from the
124         # process table.
125         self.log.debug('Stopping snippet apk %s', self.package)
126         try:
127             # Close the socket connection.
128             self.disconnect()
129             if self._proc:
130                 utils.stop_standing_subprocess(self._proc)
131             out = self._adb.shell(_STOP_CMD % self.package).decode('utf-8')
132             if 'OK (0 tests)' not in out:
133                 raise Error('Failed to stop existing apk. Unexpected '
134                             'output: %s' % out)
135         finally:
136             # Always clean up the adb port
137             if self.host_port:
138                 self._adb.forward(['--remove', 'tcp:%d' % self.host_port])
139 
140     def _start_event_client(self):
141         """Overrides superclass."""
142         event_client = SnippetClient(
143             package=self.package, adb_proxy=self._adb, log=self.log)
144         event_client.host_port = self.host_port
145         event_client.connect(self.uid,
146                              jsonrpc_client_base.JsonRpcCommand.CONTINUE)
147         return event_client
148 
149     def _check_app_installed(self):
150         # Check that the Mobly Snippet app is installed.
151         out = self._adb.shell('pm list package')
152         if not utils.grep('^package:%s$' % self.package, out):
153             raise jsonrpc_client_base.AppStartError(
154                 '%s is not installed on %s' % (self.package, self._adb.serial))
155         # Check that the app is instrumented.
156         out = self._adb.shell('pm list instrumentation')
157         matched_out = utils.grep('^instrumentation:%s/%s' %
158                                  (self.package,
159                                   _INSTRUMENTATION_RUNNER_PACKAGE), out)
160         if not matched_out:
161             raise jsonrpc_client_base.AppStartError(
162                 '%s is installed on %s, but it is not instrumented.' %
163                 (self.package, self._adb.serial))
164         match = re.search('^instrumentation:(.*)\/(.*) \(target=(.*)\)$',
165                           matched_out[0])
166         target_name = match.group(3)
167         # Check that the instrumentation target is installed if it's not the
168         # same as the snippet package.
169         if target_name != self.package:
170             out = self._adb.shell('pm list package')
171             if not utils.grep('^package:%s$' % target_name, out):
172                 raise jsonrpc_client_base.AppStartError(
173                     'Instrumentation target %s is not installed on %s' %
174                     (target_name, self._adb.serial))
175 
176     def _do_start_app(self, launch_cmd):
177         adb_cmd = [adb.ADB]
178         if self._adb.serial:
179             adb_cmd += ['-s', self._adb.serial]
180         adb_cmd += ['shell', launch_cmd]
181         return utils.start_standing_subprocess(adb_cmd, shell=False)
182 
183     # TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is
184     # removed.
185     def _connect_to_v0(self):
186         self.device_port = self.host_port
187         self._adb.forward(
188             ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])
189         start_time = time.time()
190         expiration_time = start_time + _APP_START_WAIT_TIME_V0
191         while time.time() < expiration_time:
192             self.log.debug('Attempting to start %s.', self.package)
193             try:
194                 self.connect()
195                 return
196             except:
197                 self.log.debug(
198                     'v0 snippet %s is not yet running, retrying',
199                     self.package,
200                     exc_info=True)
201             time.sleep(1)
202         raise jsonrpc_client_base.AppStartError(
203             '%s failed to start on %s.' % (self.package, self._adb.serial))
204 
205     def _connect_to_v1(self):
206         line = self._read_line()
207         match = re.match('^SNIPPET SERVING, PORT ([0-9]+)$', line)
208         if not match:
209             raise ProtocolVersionError(line)
210         self.device_port = int(match.group(1))
211 
212         # Forward the device port to a new host port, and connect to that port
213         self.host_port = utils.get_available_host_port()
214         self._adb.forward(
215             ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])
216         self.connect()
217 
218     def _read_line(self):
219         line = self._proc.stdout.readline().rstrip()
220         self.log.debug('Read line from instrumentation output: "%s"', line)
221         return line
222
```
--- 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
@@ -1,11 +1,11 @@
 # Copyright 2016 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.
@@ -42,7 +42,7 @@
     pass
 
 
-class ProtocolVersionError(Error):
+class ProtocolVersionError(jsonrpc_client_base.AppStartError):
     """Raised when the protocol reported by the snippet is unknown."""
 
 
@@ -92,7 +92,7 @@
         # just warn and retry as v0.
         # TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is
         # removed.
-        line = self._read_line()
+        line = self._read_protocol_line()
         if line in ('INSTRUMENTATION_RESULT: shortMsg=Process crashed.',
                     'INSTRUMENTATION_RESULT: shortMsg='
                     'java.lang.IllegalArgumentException'):
@@ -185,7 +185,8 @@
     def _connect_to_v0(self):
         self.device_port = self.host_port
         self._adb.forward(
-            ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])
+            ['tcp:%d' % self.host_port,
+             'tcp:%d' % self.device_port])
         start_time = time.time()
         expiration_time = start_time + _APP_START_WAIT_TIME_V0
         while time.time() < expiration_time:
@@ -203,19 +204,46 @@
             '%s failed to start on %s.' % (self.package, self._adb.serial))
 
     def _connect_to_v1(self):
-        line = self._read_line()
+        line = self._read_protocol_line()
         match = re.match('^SNIPPET SERVING, PORT ([0-9]+)$', line)
         if not match:
-            raise ProtocolVersionError(line)
+            raise jsonrpc_client_base.AppStartError(line)
         self.device_port = int(match.group(1))
 
         # Forward the device port to a new host port, and connect to that port
         self.host_port = utils.get_available_host_port()
         self._adb.forward(
-            ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])
+            ['tcp:%d' % self.host_port,
+             'tcp:%d' % self.device_port])
         self.connect()
 
-    def _read_line(self):
-        line = self._proc.stdout.readline().rstrip()
-        self.log.debug('Read line from instrumentation output: "%s"', line)
-        return line
+    def _read_protocol_line(self):
+        """Reads the next line of instrumentation output relevant to snippets.
+
+        This method will skip over lines that don't start with 'SNIPPET' or
+        'INSTRUMENTATION_RESULT'.
+
+        Returns:
+            (str) Next line of snippet-related instrumentation output, stripped.
+
+        Raises:
+            jsonrpc_client_base.AppStartError: If EOF is reached without any
+                                               protocol lines being read.
+        """
+        while True:
+            line = self._proc.stdout.readline().decode('utf-8')
+            if not line:
+                raise jsonrpc_client_base.AppStartError(
+                    '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
+            # considering any blank output line to be EOF.
+            line = line.strip()
+            if (line.startswith('INSTRUMENTATION_RESULT:') or
+                    line.startswith('SNIPPET ')):
+                self.log.debug(
+                    'Accepted line from instrumentation output: "%s"', line)
+                return line
+            self.log.debug('Discarded line from instrumentation output: "%s"',
+                           line)

{"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@@ -1,11 +1,11 @@\n # Copyright 2016 Google Inc.\n-# \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+#\n #     http://www.apache.org/licenses/LICENSE-2.0\n-# \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@@ -42,7 +42,7 @@\n     pass\n \n \n-class ProtocolVersionError(Error):\n+class ProtocolVersionError(jsonrpc_client_base.AppStartError):\n     \"\"\"Raised when the protocol reported by the snippet is unknown.\"\"\"\n \n \n@@ -92,7 +92,7 @@\n         # just warn and retry as v0.\n         # TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is\n         # removed.\n-        line = self._read_line()\n+        line = self._read_protocol_line()\n         if line in ('INSTRUMENTATION_RESULT: shortMsg=Process crashed.',\n                     'INSTRUMENTATION_RESULT: shortMsg='\n                     'java.lang.IllegalArgumentException'):\n@@ -185,7 +185,8 @@\n     def _connect_to_v0(self):\n         self.device_port = self.host_port\n         self._adb.forward(\n-            ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])\n+            ['tcp:%d' % self.host_port,\n+             'tcp:%d' % self.device_port])\n         start_time = time.time()\n         expiration_time = start_time + _APP_START_WAIT_TIME_V0\n         while time.time() < expiration_time:\n@@ -203,19 +204,46 @@\n             '%s failed to start on %s.' % (self.package, self._adb.serial))\n \n     def _connect_to_v1(self):\n-        line = self._read_line()\n+        line = self._read_protocol_line()\n         match = re.match('^SNIPPET SERVING, PORT ([0-9]+)$', line)\n         if not match:\n-            raise ProtocolVersionError(line)\n+            raise jsonrpc_client_base.AppStartError(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+            ['tcp:%d' % self.host_port,\n+             'tcp:%d' % self.device_port])\n         self.connect()\n \n-    def _read_line(self):\n-        line = self._proc.stdout.readline().rstrip()\n-        self.log.debug('Read line from instrumentation output: \"%s\"', line)\n-        return line\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+                    '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", "issue": "`snippet_shell` is broken for python3\n```\r\n$ snippet_shell.py --mbs\r\ns.logINFO:root:[AndroidDevice|HT67K0300103] Launching snippet apk com.google.android.mobly.snippet.bundled with protocol v1\r\nVTraceback (most recent call last):\r\n  File \"/Library/Frameworks/Python.framework/Versions/3.6/bin/snippet_shell.py\", line 6, in <module>\r\n    exec(compile(open(__file__).read(), __file__, 'exec'))\r\n  File \"/Users/angli/Developer/mobly/tools/snippet_shell.py\", line 84, in <module>\r\n    SnippetShell(package).main(args.serial)\r\n  File \"/Users/angli/Developer/mobly/mobly/controllers/android_device_lib/jsonrpc_shell_base.py\", line 88, in main\r\n    self.start_console()\r\n  File \"/Users/angli/Developer/mobly/mobly/controllers/android_device_lib/jsonrpc_shell_base.py\", line 73, in start_console\r\n    self._start_services(console_env)\r\n  File \"/Users/angli/Developer/mobly/tools/snippet_shell.py\", line 43, in _start_services\r\n    self._ad.load_snippet(name='snippet', package=self._package)\r\n  File \"/Users/angli/Developer/mobly/mobly/controllers/android_device.py\", line 646, in load_snippet\r\n    client.start_app_and_connect()\r\n  File \"/Users/angli/Developer/mobly/mobly/controllers/android_device_lib/snippet_client.py\", line 112, in start_app_and_connect\r\n    line)\r\n  File \"/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/re.py\", line 172, in match\r\n    return _compile(pattern, flags).match(string)\r\nTypeError: cannot use a string pattern on a bytes-like object\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 jsonrpc_client_base\n\n_INSTRUMENTATION_RUNNER_PACKAGE = (\n    'com.google.android.mobly.snippet.SnippetRunner')\n\n# TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is removed.\n_LAUNCH_CMD_V0 = ('am instrument -w -e action start -e port %s %s/' +\n                  _INSTRUMENTATION_RUNNER_PACKAGE)\n\n_LAUNCH_CMD_V1 = (\n    'am instrument -w -e action start %s/' + _INSTRUMENTATION_RUNNER_PACKAGE)\n\n_STOP_CMD = (\n    'am instrument -w -e action stop %s/' + _INSTRUMENTATION_RUNNER_PACKAGE)\n\n# Maximum time to wait for a v0 snippet to start on the device (10 minutes).\n# TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is removed.\n_APP_START_WAIT_TIME_V0 = 10 * 60\n\n\nclass Error(Exception):\n    pass\n\n\nclass ProtocolVersionError(Error):\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    It currently supports both v0 and v1 snippet launch protocols, although\n    support for v0 will be removed in a future version.\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, adb_proxy, log=logging.getLogger()):\n        \"\"\"Initializes a SnippetClient.\n  \n        Args:\n            package: (str) The package name of the apk where the snippets are\n                     defined.\n            adb_proxy: (adb.AdbProxy) Adb proxy for running adb commands.\n            log: (logging.Logger) logger to which to send log messages.\n        \"\"\"\n        super(SnippetClient, self).__init__(app_name=package, log=log)\n        self.package = package\n        self._adb = adb_proxy\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        # Try launching the app with the v1 protocol. If that fails, fall back\n        # to v0 for compatibility. Use info here so people know exactly what's\n        # happening here, which is helpful since they need to create their own\n        # instrumentations and manifest.\n        self.log.info('Launching snippet apk %s with protocol v1',\n                      self.package)\n        cmd = _LAUNCH_CMD_V1 % self.package\n        start_time = time.time()\n        self._proc = self._do_start_app(cmd)\n\n        # \"Instrumentation crashed\" could be due to several reasons, eg\n        # exception thrown during startup or just a launch protocol 0 snippet\n        # dying because it needs the port flag. Sadly we have no way to tell so\n        # just warn and retry as v0.\n        # TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is\n        # removed.\n        line = self._read_line()\n        if line in ('INSTRUMENTATION_RESULT: shortMsg=Process crashed.',\n                    'INSTRUMENTATION_RESULT: shortMsg='\n                    'java.lang.IllegalArgumentException'):\n            self.log.warning('Snippet %s crashed on startup. This might be an '\n                             'actual error or a snippet using deprecated v0 '\n                             'start protocol. Retrying as a v0 snippet.',\n                             self.package)\n            self.host_port = utils.get_available_host_port()\n            # Reuse the host port as the device port in v0 snippet. This isn't\n            # safe in general, but the protocol is deprecated.\n            cmd = _LAUNCH_CMD_V0 % (self.host_port, self.package)\n            self._proc = self._do_start_app(cmd)\n            self._connect_to_v0()\n        else:\n            # Check protocol version and get the device port\n            match = re.match('^SNIPPET START, PROTOCOL ([0-9]+) ([0-9]+)$',\n                             line)\n            if not match or match.group(1) != '1':\n                raise ProtocolVersionError(line)\n            self._connect_to_v1()\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 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 Error('Failed to stop existing apk. Unexpected '\n                            '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(\n            package=self.package, adb_proxy=self._adb, log=self.log)\n        event_client.host_port = self.host_port\n        event_client.connect(self.uid,\n                             jsonrpc_client_base.JsonRpcCommand.CONTINUE)\n        return event_client\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                '%s is not installed on %s' % (self.package, self._adb.serial))\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                '%s is installed on %s, but it is not instrumented.' %\n                (self.package, self._adb.serial))\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                    'Instrumentation target %s is not installed on %s' %\n                    (target_name, self._adb.serial))\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    # TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is\n    # removed.\n    def _connect_to_v0(self):\n        self.device_port = self.host_port\n        self._adb.forward(\n            ['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])\n        start_time = time.time()\n        expiration_time = start_time + _APP_START_WAIT_TIME_V0\n        while time.time() < expiration_time:\n            self.log.debug('Attempting to start %s.', self.package)\n            try:\n                self.connect()\n                return\n            except:\n                self.log.debug(\n                    'v0 snippet %s is not yet running, retrying',\n                    self.package,\n                    exc_info=True)\n            time.sleep(1)\n        raise jsonrpc_client_base.AppStartError(\n            '%s failed to start on %s.' % (self.package, self._adb.serial))\n\n    def _connect_to_v1(self):\n        line = self._read_line()\n        match = re.match('^SNIPPET SERVING, PORT ([0-9]+)$', line)\n        if not match:\n            raise ProtocolVersionError(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    def _read_line(self):\n        line = self._proc.stdout.readline().rstrip()\n        self.log.debug('Read line from instrumentation output: \"%s\"', line)\n        return line\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 jsonrpc_client_base\n\n_INSTRUMENTATION_RUNNER_PACKAGE = (\n    'com.google.android.mobly.snippet.SnippetRunner')\n\n# TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is removed.\n_LAUNCH_CMD_V0 = ('am instrument -w -e action start -e port %s %s/' +\n                  _INSTRUMENTATION_RUNNER_PACKAGE)\n\n_LAUNCH_CMD_V1 = (\n    'am instrument -w -e action start %s/' + _INSTRUMENTATION_RUNNER_PACKAGE)\n\n_STOP_CMD = (\n    'am instrument -w -e action stop %s/' + _INSTRUMENTATION_RUNNER_PACKAGE)\n\n# Maximum time to wait for a v0 snippet to start on the device (10 minutes).\n# TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is removed.\n_APP_START_WAIT_TIME_V0 = 10 * 60\n\n\nclass Error(Exception):\n    pass\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    It currently supports both v0 and v1 snippet launch protocols, although\n    support for v0 will be removed in a future version.\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, adb_proxy, log=logging.getLogger()):\n        \"\"\"Initializes a SnippetClient.\n  \n        Args:\n            package: (str) The package name of the apk where the snippets are\n                     defined.\n            adb_proxy: (adb.AdbProxy) Adb proxy for running adb commands.\n            log: (logging.Logger) logger to which to send log messages.\n        \"\"\"\n        super(SnippetClient, self).__init__(app_name=package, log=log)\n        self.package = package\n        self._adb = adb_proxy\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        # Try launching the app with the v1 protocol. If that fails, fall back\n        # to v0 for compatibility. Use info here so people know exactly what's\n        # happening here, which is helpful since they need to create their own\n        # instrumentations and manifest.\n        self.log.info('Launching snippet apk %s with protocol v1',\n                      self.package)\n        cmd = _LAUNCH_CMD_V1 % self.package\n        start_time = time.time()\n        self._proc = self._do_start_app(cmd)\n\n        # \"Instrumentation crashed\" could be due to several reasons, eg\n        # exception thrown during startup or just a launch protocol 0 snippet\n        # dying because it needs the port flag. Sadly we have no way to tell so\n        # just warn and retry as v0.\n        # TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is\n        # removed.\n        line = self._read_protocol_line()\n        if line in ('INSTRUMENTATION_RESULT: shortMsg=Process crashed.',\n                    'INSTRUMENTATION_RESULT: shortMsg='\n                    'java.lang.IllegalArgumentException'):\n            self.log.warning('Snippet %s crashed on startup. This might be an '\n                             'actual error or a snippet using deprecated v0 '\n                             'start protocol. Retrying as a v0 snippet.',\n                             self.package)\n            self.host_port = utils.get_available_host_port()\n            # Reuse the host port as the device port in v0 snippet. This isn't\n            # safe in general, but the protocol is deprecated.\n            cmd = _LAUNCH_CMD_V0 % (self.host_port, self.package)\n            self._proc = self._do_start_app(cmd)\n            self._connect_to_v0()\n        else:\n            # Check protocol version and get the device port\n            match = re.match('^SNIPPET START, PROTOCOL ([0-9]+) ([0-9]+)$',\n                             line)\n            if not match or match.group(1) != '1':\n                raise ProtocolVersionError(line)\n            self._connect_to_v1()\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 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 Error('Failed to stop existing apk. Unexpected '\n                            '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(\n            package=self.package, adb_proxy=self._adb, log=self.log)\n        event_client.host_port = self.host_port\n        event_client.connect(self.uid,\n                             jsonrpc_client_base.JsonRpcCommand.CONTINUE)\n        return event_client\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                '%s is not installed on %s' % (self.package, self._adb.serial))\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                '%s is installed on %s, but it is not instrumented.' %\n                (self.package, self._adb.serial))\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                    'Instrumentation target %s is not installed on %s' %\n                    (target_name, self._adb.serial))\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    # TODO(adorokhine): delete this in Mobly 1.6 when snippet v0 support is\n    # removed.\n    def _connect_to_v0(self):\n        self.device_port = self.host_port\n        self._adb.forward(\n            ['tcp:%d' % self.host_port,\n             'tcp:%d' % self.device_port])\n        start_time = time.time()\n        expiration_time = start_time + _APP_START_WAIT_TIME_V0\n        while time.time() < expiration_time:\n            self.log.debug('Attempting to start %s.', self.package)\n            try:\n                self.connect()\n                return\n            except:\n                self.log.debug(\n                    'v0 snippet %s is not yet running, retrying',\n                    self.package,\n                    exc_info=True)\n            time.sleep(1)\n        raise jsonrpc_client_base.AppStartError(\n            '%s failed to start on %s.' % (self.package, self._adb.serial))\n\n    def _connect_to_v1(self):\n        line = self._read_protocol_line()\n        match = re.match('^SNIPPET SERVING, PORT ([0-9]+)$', line)\n        if not match:\n            raise jsonrpc_client_base.AppStartError(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,\n             'tcp:%d' % self.device_port])\n        self.connect()\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                    '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", "path": "mobly/controllers/android_device_lib/snippet_client.py"}]}