We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Snippet client is unable to make connection on mac
Because the convoluted timeout scheme of JsonRpcClientBase.connect.


--- 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/jsonrpc_client_base.py`
Content:
```
1 #/usr/bin/env python3.4
2 #
3 # Copyright 2016 Google Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 
17 """Base class for clients that communicate with apps over a JSON RPC interface.
18 
19 The JSON protocol expected by this module is:
20 
21 Request:
22 {
23     "id": <monotonically increasing integer containing the ID of this request>
24     "method": <string containing the name of the method to execute>
25     "params": <JSON array containing the arguments to the method>
26 }
27 
28 Response:
29 {
30     "id": <int id of request that this response maps to>,
31     "result": <Arbitrary JSON object containing the result of executing the
32                method. If the method could not be executed or returned void,
33                contains 'null'.>,
34     "error": <String containing the error thrown by executing the method.
35               If no error occurred, contains 'null'.>
36 }
37 """
38 
39 from builtins import str
40 
41 import json
42 import logging
43 import socket
44 import sys
45 import threading
46 import time
47 
48 # Maximum time to wait for the app to start on the device.
49 APP_START_WAIT_TIME = 15
50 
51 # UID of the 'unknown' jsonrpc session. Will cause creation of a new session.
52 UNKNOWN_UID = -1
53 
54 # Maximum time to wait for the socket to open on the device.
55 _SOCKET_TIMEOUT = 60
56 
57 
58 class Error(Exception):
59     pass
60 
61 
62 class AppStartError(Error):
63     """Raised when the app is not able to be started."""
64 
65 
66 class ApiError(Error):
67     """Raised when remote API reports an error."""
68 
69 
70 class ProtocolError(Error):
71     """Raised when there is some error in exchanging data with server."""
72     NO_RESPONSE_FROM_HANDSHAKE = "No response from handshake."
73     NO_RESPONSE_FROM_SERVER = "No response from server."
74     MISMATCHED_API_ID = "Mismatched API id."
75 
76 
77 class JsonRpcCommand(object):
78     """Commands that can be invoked on all jsonrpc clients.
79 
80     INIT: Initializes a new session.
81     CONTINUE: Creates a connection.
82     """
83     INIT = 'initiate'
84     CONTINUE = 'continue'
85 
86 
87 class JsonRpcClientBase(object):
88     """Base class for jsonrpc clients that connect to remote servers.
89 
90     Connects to a remote device running a jsonrpc-compatible app. Before opening
91     a connection a port forward must be setup to go over usb. This be done using
92     adb.tcp_forward(). This calls the shell command adb forward <local> remote>.
93     Once the port has been forwarded it can be used in this object as the port
94     of communication.
95 
96     Attributes:
97         uid: (int) The uid of this session.
98         app_name: (str) The user-visible name of the app being communicated
99           with. Must be set by the superclass.
100     """
101     def __init__(self, adb_proxy):
102         """
103         Args:
104             adb_proxy: adb.AdbProxy, The adb proxy to use to start the app
105         """
106         self.uid = None
107         self._adb = adb_proxy
108         self._client = None  # prevent close errors on connect failure
109         self._conn = None
110         self._counter = None
111         self._lock = threading.Lock()
112 
113     def __del__(self):
114         self.close()
115 
116     # Methods to be implemented by subclasses.
117 
118     def _do_start_app(self):
119         """Starts the server app on the android device.
120 
121         Must be implemented by subclasses.
122         """
123         raise NotImplementedError()
124 
125     def stop_app(self):
126         """Kills any running instance of the app.
127 
128         Must be implemented by subclasses.
129         """
130         raise NotImplementedError()
131 
132     def _is_app_installed(self):
133         """Checks if app is installed.
134 
135         Must be implemented by subclasses.
136 
137         Returns:
138             True if installed, False otherwise.
139         """
140         raise NotImplementedError()
141 
142     def _is_app_running(self):
143         """Checks if the app is currently running on an android device.
144 
145         Must be implemented by subclasses.
146 
147         Returns:
148             True if the app is running, False otherwise.
149         """
150         raise NotImplementedError()
151 
152     # Rest of the client methods.
153 
154     def check_app_installed(self):
155       if not self._is_app_installed():
156             raise AppStartError(
157                 '%s is not installed on %s' % (self.app_name, self._adb.serial))
158 
159     def start_app(self, wait_time=APP_START_WAIT_TIME):
160         """Starts the server app on the android device.
161 
162         Args:
163             wait_time: float, The time to wait for the app to come up before
164                 raising an error.
165 
166         Raises:
167             AppStartError: When the app was not able to be started.
168         """
169         self.check_app_installed()
170         self._do_start_app()
171         for _ in range(wait_time):
172             time.sleep(1)
173             if self._is_app_running():
174                 return
175         raise AppStartError(
176             '%s failed to start on %s.' % (self.app_name, self._adb.serial))
177 
178     def connect(self, port, addr='localhost', uid=UNKNOWN_UID,
179                 connection_timeout=None, cmd=JsonRpcCommand.INIT):
180         """
181         Opens a connection to the remote client.
182 
183         Opens a connection to a remote client. The connection will error out if
184         it takes longer than the connection_timeout time. Once connected if the
185         socket takes longer than _SOCKET_TIMEOUT to respond the connection will
186         be closed.
187 
188         Args:
189             port: int, The port this client should connect to.
190             addr: str, The address this client should connect to.
191             uid: int, The uid of the session to join, or UNKNOWN_UID to start a
192                  new session.
193             connection_timeout: int, The time to wait for the connection to come
194                  up.
195             cmd: JsonRpcCommand, The command to use for creating the connection.
196 
197         Raises:
198             IOError: Raised when the socket times out from io error
199             socket.timeout: Raised when the socket waits to long for connection.
200             ProtocolError: Raised when there is an error in the protocol.
201         """
202         if connection_timeout:
203             timeout_time = time.time() + connection_timeout
204         else:
205             timeout_time = sys.maxsize
206         self._counter = self._id_counter()
207         while True:
208             try:
209                 self._conn = socket.create_connection(
210                     (addr, port), max(1, timeout_time - time.time()))
211                 self._conn.settimeout(_SOCKET_TIMEOUT)
212                 break
213             except socket.timeout:
214                 logging.exception("Failed to create socket connection!")
215                 raise
216             except (socket.error, IOError):
217                 # TODO: optimize to only forgive some errors here
218                 # error values are OS-specific so this will require
219                 # additional tuning to fail faster
220                 if time.time() + 1 >= timeout_time:
221                     logging.exception("Failed to create socket connection!")
222                     raise
223                 time.sleep(1)
224         self.port = port
225         self._client = self._conn.makefile(mode="brw")
226 
227         resp = self._cmd(cmd, uid)
228         if not resp:
229             raise ProtocolError(ProtocolError.NO_RESPONSE_FROM_HANDSHAKE)
230         result = json.loads(str(resp, encoding="utf8"))
231         if result['status']:
232             self.uid = result['uid']
233         else:
234             self.uid = UNKNOWN_UID
235 
236     def close(self):
237         """Close the connection to the remote client."""
238         if self._conn:
239             self._conn.close()
240             self._conn = None
241 
242     def _cmd(self, command, uid=None):
243         """Send a command to the server.
244 
245         Args:
246             command: str, The name of the command to execute.
247             uid: int, the uid of the session to send the command to.
248 
249         Returns:
250             The line that was written back.
251         """
252         if not uid:
253             uid = self.uid
254         self._client.write(
255             json.dumps({'cmd': command, 'uid': uid}).encode("utf8") + b'\n')
256         self._client.flush()
257         return self._client.readline()
258 
259     def _rpc(self, method, *args):
260         """Sends an rpc to the app.
261 
262         Args:
263             method: str, The name of the method to execute.
264             args: any, The args of the method.
265 
266         Returns:
267             The result of the rpc.
268 
269         Raises:
270             ProtocolError: Something went wrong with the protocol.
271             ApiError: The rpc went through, however executed with errors.
272         """
273         with self._lock:
274             apiid = next(self._counter)
275         data = {'id': apiid, 'method': method, 'params': args}
276         request = json.dumps(data)
277         self._client.write(request.encode("utf8") + b'\n')
278         self._client.flush()
279         response = self._client.readline()
280         if not response:
281             raise ProtocolError(ProtocolError.NO_RESPONSE_FROM_SERVER)
282         result = json.loads(str(response, encoding="utf8"))
283         if result['error']:
284             raise ApiError(result['error'])
285         if result['id'] != apiid:
286             raise ProtocolError(ProtocolError.MISMATCHED_API_ID)
287         return result['result']
288 
289     def __getattr__(self, name):
290         """Wrapper for python magic to turn method calls into RPC calls."""
291         def rpc_call(*args):
292             return self._rpc(name, *args)
293         return rpc_call
294 
295     def _id_counter(self):
296         i = 0
297         while True:
298             yield i
299             i += 1
300
```
--- 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/jsonrpc_client_base.py b/mobly/controllers/android_device_lib/jsonrpc_client_base.py
--- a/mobly/controllers/android_device_lib/jsonrpc_client_base.py
+++ b/mobly/controllers/android_device_lib/jsonrpc_client_base.py
@@ -176,14 +176,13 @@
             '%s failed to start on %s.' % (self.app_name, self._adb.serial))
 
     def connect(self, port, addr='localhost', uid=UNKNOWN_UID,
-                connection_timeout=None, cmd=JsonRpcCommand.INIT):
-        """
-        Opens a connection to the remote client.
+                cmd=JsonRpcCommand.INIT):
+        """Opens a connection to a JSON RPC server.
 
-        Opens a connection to a remote client. The connection will error out if
-        it takes longer than the connection_timeout time. Once connected if the
-        socket takes longer than _SOCKET_TIMEOUT to respond the connection will
-        be closed.
+        Opens a connection to a remote client. The connection attempt will time
+        out if it takes longer than _SOCKET_TIMEOUT seconds. Each subsequent
+        operation over this socket will time out after _SOCKET_TIMEOUT seconds
+        as well.
 
         Args:
             port: int, The port this client should connect to.
@@ -199,28 +198,14 @@
             socket.timeout: Raised when the socket waits to long for connection.
             ProtocolError: Raised when there is an error in the protocol.
         """
-        if connection_timeout:
-            timeout_time = time.time() + connection_timeout
-        else:
-            timeout_time = sys.maxsize
         self._counter = self._id_counter()
-        while True:
-            try:
-                self._conn = socket.create_connection(
-                    (addr, port), max(1, timeout_time - time.time()))
-                self._conn.settimeout(_SOCKET_TIMEOUT)
-                break
-            except socket.timeout:
-                logging.exception("Failed to create socket connection!")
-                raise
-            except (socket.error, IOError):
-                # TODO: optimize to only forgive some errors here
-                # error values are OS-specific so this will require
-                # additional tuning to fail faster
-                if time.time() + 1 >= timeout_time:
-                    logging.exception("Failed to create socket connection!")
-                    raise
-                time.sleep(1)
+        try:
+            self._conn = socket.create_connection((addr, port),
+                                                  _SOCKET_TIMEOUT)
+            self._conn.settimeout(_SOCKET_TIMEOUT)
+        except (socket.timeout, socket.error, IOError):
+            logging.exception("Failed to create socket connection!")
+            raise
         self.port = port
         self._client = self._conn.makefile(mode="brw")

{"golden_diff": "diff --git a/mobly/controllers/android_device_lib/jsonrpc_client_base.py b/mobly/controllers/android_device_lib/jsonrpc_client_base.py\n--- a/mobly/controllers/android_device_lib/jsonrpc_client_base.py\n+++ b/mobly/controllers/android_device_lib/jsonrpc_client_base.py\n@@ -176,14 +176,13 @@\n             '%s failed to start on %s.' % (self.app_name, self._adb.serial))\n \n     def connect(self, port, addr='localhost', uid=UNKNOWN_UID,\n-                connection_timeout=None, cmd=JsonRpcCommand.INIT):\n-        \"\"\"\n-        Opens a connection to the remote client.\n+                cmd=JsonRpcCommand.INIT):\n+        \"\"\"Opens a connection to a JSON RPC server.\n \n-        Opens a connection to a remote client. The connection will error out if\n-        it takes longer than the connection_timeout time. Once connected if the\n-        socket takes longer than _SOCKET_TIMEOUT to respond the connection will\n-        be closed.\n+        Opens a connection to a remote client. The connection attempt will time\n+        out if it takes longer than _SOCKET_TIMEOUT seconds. Each subsequent\n+        operation over this socket will time out after _SOCKET_TIMEOUT seconds\n+        as well.\n \n         Args:\n             port: int, The port this client should connect to.\n@@ -199,28 +198,14 @@\n             socket.timeout: Raised when the socket waits to long for connection.\n             ProtocolError: Raised when there is an error in the protocol.\n         \"\"\"\n-        if connection_timeout:\n-            timeout_time = time.time() + connection_timeout\n-        else:\n-            timeout_time = sys.maxsize\n         self._counter = self._id_counter()\n-        while True:\n-            try:\n-                self._conn = socket.create_connection(\n-                    (addr, port), max(1, timeout_time - time.time()))\n-                self._conn.settimeout(_SOCKET_TIMEOUT)\n-                break\n-            except socket.timeout:\n-                logging.exception(\"Failed to create socket connection!\")\n-                raise\n-            except (socket.error, IOError):\n-                # TODO: optimize to only forgive some errors here\n-                # error values are OS-specific so this will require\n-                # additional tuning to fail faster\n-                if time.time() + 1 >= timeout_time:\n-                    logging.exception(\"Failed to create socket connection!\")\n-                    raise\n-                time.sleep(1)\n+        try:\n+            self._conn = socket.create_connection((addr, port),\n+                                                  _SOCKET_TIMEOUT)\n+            self._conn.settimeout(_SOCKET_TIMEOUT)\n+        except (socket.timeout, socket.error, IOError):\n+            logging.exception(\"Failed to create socket connection!\")\n+            raise\n         self.port = port\n         self._client = self._conn.makefile(mode=\"brw\")\n", "issue": "Snippet client is unable to make connection on mac\nBecause the convoluted timeout scheme of JsonRpcClientBase.connect.\r\n\n", "before_files": [{"content": "#/usr/bin/env python3.4\n#\n# 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\n\"\"\"Base class for clients that communicate with apps over a JSON RPC interface.\n\nThe JSON protocol expected by this module is:\n\nRequest:\n{\n    \"id\": <monotonically increasing integer containing the ID of this request>\n    \"method\": <string containing the name of the method to execute>\n    \"params\": <JSON array containing the arguments to the method>\n}\n\nResponse:\n{\n    \"id\": <int id of request that this response maps to>,\n    \"result\": <Arbitrary JSON object containing the result of executing the\n               method. If the method could not be executed or returned void,\n               contains 'null'.>,\n    \"error\": <String containing the error thrown by executing the method.\n              If no error occurred, contains 'null'.>\n}\n\"\"\"\n\nfrom builtins import str\n\nimport json\nimport logging\nimport socket\nimport sys\nimport threading\nimport time\n\n# Maximum time to wait for the app to start on the device.\nAPP_START_WAIT_TIME = 15\n\n# UID of the 'unknown' jsonrpc session. Will cause creation of a new session.\nUNKNOWN_UID = -1\n\n# Maximum time to wait for the socket to open on the device.\n_SOCKET_TIMEOUT = 60\n\n\nclass Error(Exception):\n    pass\n\n\nclass AppStartError(Error):\n    \"\"\"Raised when the app is not able to be started.\"\"\"\n\n\nclass ApiError(Error):\n    \"\"\"Raised when remote API reports an error.\"\"\"\n\n\nclass ProtocolError(Error):\n    \"\"\"Raised when there is some error in exchanging data with server.\"\"\"\n    NO_RESPONSE_FROM_HANDSHAKE = \"No response from handshake.\"\n    NO_RESPONSE_FROM_SERVER = \"No response from server.\"\n    MISMATCHED_API_ID = \"Mismatched API id.\"\n\n\nclass JsonRpcCommand(object):\n    \"\"\"Commands that can be invoked on all jsonrpc clients.\n\n    INIT: Initializes a new session.\n    CONTINUE: Creates a connection.\n    \"\"\"\n    INIT = 'initiate'\n    CONTINUE = 'continue'\n\n\nclass JsonRpcClientBase(object):\n    \"\"\"Base class for jsonrpc clients that connect to remote servers.\n\n    Connects to a remote device running a jsonrpc-compatible app. Before opening\n    a connection a port forward must be setup to go over usb. This be done using\n    adb.tcp_forward(). This calls the shell command adb forward <local> remote>.\n    Once the port has been forwarded it can be used in this object as the port\n    of communication.\n\n    Attributes:\n        uid: (int) The uid of this session.\n        app_name: (str) The user-visible name of the app being communicated\n          with. Must be set by the superclass.\n    \"\"\"\n    def __init__(self, adb_proxy):\n        \"\"\"\n        Args:\n            adb_proxy: adb.AdbProxy, The adb proxy to use to start the app\n        \"\"\"\n        self.uid = None\n        self._adb = adb_proxy\n        self._client = None  # prevent close errors on connect failure\n        self._conn = None\n        self._counter = None\n        self._lock = threading.Lock()\n\n    def __del__(self):\n        self.close()\n\n    # Methods to be implemented by subclasses.\n\n    def _do_start_app(self):\n        \"\"\"Starts the server app on the android device.\n\n        Must be implemented by subclasses.\n        \"\"\"\n        raise NotImplementedError()\n\n    def stop_app(self):\n        \"\"\"Kills any running instance of the app.\n\n        Must be implemented by subclasses.\n        \"\"\"\n        raise NotImplementedError()\n\n    def _is_app_installed(self):\n        \"\"\"Checks if app is installed.\n\n        Must be implemented by subclasses.\n\n        Returns:\n            True if installed, False otherwise.\n        \"\"\"\n        raise NotImplementedError()\n\n    def _is_app_running(self):\n        \"\"\"Checks if the app is currently running on an android device.\n\n        Must be implemented by subclasses.\n\n        Returns:\n            True if the app is running, False otherwise.\n        \"\"\"\n        raise NotImplementedError()\n\n    # Rest of the client methods.\n\n    def check_app_installed(self):\n      if not self._is_app_installed():\n            raise AppStartError(\n                '%s is not installed on %s' % (self.app_name, self._adb.serial))\n\n    def start_app(self, wait_time=APP_START_WAIT_TIME):\n        \"\"\"Starts the server app on the android device.\n\n        Args:\n            wait_time: float, The time to wait for the app to come up before\n                raising an error.\n\n        Raises:\n            AppStartError: When the app was not able to be started.\n        \"\"\"\n        self.check_app_installed()\n        self._do_start_app()\n        for _ in range(wait_time):\n            time.sleep(1)\n            if self._is_app_running():\n                return\n        raise AppStartError(\n            '%s failed to start on %s.' % (self.app_name, self._adb.serial))\n\n    def connect(self, port, addr='localhost', uid=UNKNOWN_UID,\n                connection_timeout=None, cmd=JsonRpcCommand.INIT):\n        \"\"\"\n        Opens a connection to the remote client.\n\n        Opens a connection to a remote client. The connection will error out if\n        it takes longer than the connection_timeout time. Once connected if the\n        socket takes longer than _SOCKET_TIMEOUT to respond the connection will\n        be closed.\n\n        Args:\n            port: int, The port this client should connect to.\n            addr: str, The address this client should connect to.\n            uid: int, The uid of the session to join, or UNKNOWN_UID to start a\n                 new session.\n            connection_timeout: int, The time to wait for the connection to come\n                 up.\n            cmd: JsonRpcCommand, The command to use for creating the connection.\n\n        Raises:\n            IOError: Raised when the socket times out from io error\n            socket.timeout: Raised when the socket waits to long for connection.\n            ProtocolError: Raised when there is an error in the protocol.\n        \"\"\"\n        if connection_timeout:\n            timeout_time = time.time() + connection_timeout\n        else:\n            timeout_time = sys.maxsize\n        self._counter = self._id_counter()\n        while True:\n            try:\n                self._conn = socket.create_connection(\n                    (addr, port), max(1, timeout_time - time.time()))\n                self._conn.settimeout(_SOCKET_TIMEOUT)\n                break\n            except socket.timeout:\n                logging.exception(\"Failed to create socket connection!\")\n                raise\n            except (socket.error, IOError):\n                # TODO: optimize to only forgive some errors here\n                # error values are OS-specific so this will require\n                # additional tuning to fail faster\n                if time.time() + 1 >= timeout_time:\n                    logging.exception(\"Failed to create socket connection!\")\n                    raise\n                time.sleep(1)\n        self.port = port\n        self._client = self._conn.makefile(mode=\"brw\")\n\n        resp = self._cmd(cmd, uid)\n        if not resp:\n            raise ProtocolError(ProtocolError.NO_RESPONSE_FROM_HANDSHAKE)\n        result = json.loads(str(resp, encoding=\"utf8\"))\n        if result['status']:\n            self.uid = result['uid']\n        else:\n            self.uid = UNKNOWN_UID\n\n    def close(self):\n        \"\"\"Close the connection to the remote client.\"\"\"\n        if self._conn:\n            self._conn.close()\n            self._conn = None\n\n    def _cmd(self, command, uid=None):\n        \"\"\"Send a command to the server.\n\n        Args:\n            command: str, The name of the command to execute.\n            uid: int, the uid of the session to send the command to.\n\n        Returns:\n            The line that was written back.\n        \"\"\"\n        if not uid:\n            uid = self.uid\n        self._client.write(\n            json.dumps({'cmd': command, 'uid': uid}).encode(\"utf8\") + b'\\n')\n        self._client.flush()\n        return self._client.readline()\n\n    def _rpc(self, method, *args):\n        \"\"\"Sends an rpc to the app.\n\n        Args:\n            method: str, The name of the method to execute.\n            args: any, The args of the method.\n\n        Returns:\n            The result of the rpc.\n\n        Raises:\n            ProtocolError: Something went wrong with the protocol.\n            ApiError: The rpc went through, however executed with errors.\n        \"\"\"\n        with self._lock:\n            apiid = next(self._counter)\n        data = {'id': apiid, 'method': method, 'params': args}\n        request = json.dumps(data)\n        self._client.write(request.encode(\"utf8\") + b'\\n')\n        self._client.flush()\n        response = self._client.readline()\n        if not response:\n            raise ProtocolError(ProtocolError.NO_RESPONSE_FROM_SERVER)\n        result = json.loads(str(response, encoding=\"utf8\"))\n        if result['error']:\n            raise ApiError(result['error'])\n        if result['id'] != apiid:\n            raise ProtocolError(ProtocolError.MISMATCHED_API_ID)\n        return result['result']\n\n    def __getattr__(self, name):\n        \"\"\"Wrapper for python magic to turn method calls into RPC calls.\"\"\"\n        def rpc_call(*args):\n            return self._rpc(name, *args)\n        return rpc_call\n\n    def _id_counter(self):\n        i = 0\n        while True:\n            yield i\n            i += 1\n", "path": "mobly/controllers/android_device_lib/jsonrpc_client_base.py"}], "after_files": [{"content": "#/usr/bin/env python3.4\n#\n# 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\n\"\"\"Base class for clients that communicate with apps over a JSON RPC interface.\n\nThe JSON protocol expected by this module is:\n\nRequest:\n{\n    \"id\": <monotonically increasing integer containing the ID of this request>\n    \"method\": <string containing the name of the method to execute>\n    \"params\": <JSON array containing the arguments to the method>\n}\n\nResponse:\n{\n    \"id\": <int id of request that this response maps to>,\n    \"result\": <Arbitrary JSON object containing the result of executing the\n               method. If the method could not be executed or returned void,\n               contains 'null'.>,\n    \"error\": <String containing the error thrown by executing the method.\n              If no error occurred, contains 'null'.>\n}\n\"\"\"\n\nfrom builtins import str\n\nimport json\nimport logging\nimport socket\nimport sys\nimport threading\nimport time\n\n# Maximum time to wait for the app to start on the device.\nAPP_START_WAIT_TIME = 15\n\n# UID of the 'unknown' jsonrpc session. Will cause creation of a new session.\nUNKNOWN_UID = -1\n\n# Maximum time to wait for the socket to open on the device.\n_SOCKET_TIMEOUT = 60\n\n\nclass Error(Exception):\n    pass\n\n\nclass AppStartError(Error):\n    \"\"\"Raised when the app is not able to be started.\"\"\"\n\n\nclass ApiError(Error):\n    \"\"\"Raised when remote API reports an error.\"\"\"\n\n\nclass ProtocolError(Error):\n    \"\"\"Raised when there is some error in exchanging data with server.\"\"\"\n    NO_RESPONSE_FROM_HANDSHAKE = \"No response from handshake.\"\n    NO_RESPONSE_FROM_SERVER = \"No response from server.\"\n    MISMATCHED_API_ID = \"Mismatched API id.\"\n\n\nclass JsonRpcCommand(object):\n    \"\"\"Commands that can be invoked on all jsonrpc clients.\n\n    INIT: Initializes a new session.\n    CONTINUE: Creates a connection.\n    \"\"\"\n    INIT = 'initiate'\n    CONTINUE = 'continue'\n\n\nclass JsonRpcClientBase(object):\n    \"\"\"Base class for jsonrpc clients that connect to remote servers.\n\n    Connects to a remote device running a jsonrpc-compatible app. Before opening\n    a connection a port forward must be setup to go over usb. This be done using\n    adb.tcp_forward(). This calls the shell command adb forward <local> remote>.\n    Once the port has been forwarded it can be used in this object as the port\n    of communication.\n\n    Attributes:\n        uid: (int) The uid of this session.\n        app_name: (str) The user-visible name of the app being communicated\n          with. Must be set by the superclass.\n    \"\"\"\n    def __init__(self, adb_proxy):\n        \"\"\"\n        Args:\n            adb_proxy: adb.AdbProxy, The adb proxy to use to start the app\n        \"\"\"\n        self.uid = None\n        self._adb = adb_proxy\n        self._client = None  # prevent close errors on connect failure\n        self._conn = None\n        self._counter = None\n        self._lock = threading.Lock()\n\n    def __del__(self):\n        self.close()\n\n    # Methods to be implemented by subclasses.\n\n    def _do_start_app(self):\n        \"\"\"Starts the server app on the android device.\n\n        Must be implemented by subclasses.\n        \"\"\"\n        raise NotImplementedError()\n\n    def stop_app(self):\n        \"\"\"Kills any running instance of the app.\n\n        Must be implemented by subclasses.\n        \"\"\"\n        raise NotImplementedError()\n\n    def _is_app_installed(self):\n        \"\"\"Checks if app is installed.\n\n        Must be implemented by subclasses.\n\n        Returns:\n            True if installed, False otherwise.\n        \"\"\"\n        raise NotImplementedError()\n\n    def _is_app_running(self):\n        \"\"\"Checks if the app is currently running on an android device.\n\n        Must be implemented by subclasses.\n\n        Returns:\n            True if the app is running, False otherwise.\n        \"\"\"\n        raise NotImplementedError()\n\n    # Rest of the client methods.\n\n    def check_app_installed(self):\n      if not self._is_app_installed():\n            raise AppStartError(\n                '%s is not installed on %s' % (self.app_name, self._adb.serial))\n\n    def start_app(self, wait_time=APP_START_WAIT_TIME):\n        \"\"\"Starts the server app on the android device.\n\n        Args:\n            wait_time: float, The time to wait for the app to come up before\n                raising an error.\n\n        Raises:\n            AppStartError: When the app was not able to be started.\n        \"\"\"\n        self.check_app_installed()\n        self._do_start_app()\n        for _ in range(wait_time):\n            time.sleep(1)\n            if self._is_app_running():\n                return\n        raise AppStartError(\n            '%s failed to start on %s.' % (self.app_name, self._adb.serial))\n\n    def connect(self, port, addr='localhost', uid=UNKNOWN_UID,\n                cmd=JsonRpcCommand.INIT):\n        \"\"\"Opens a connection to a JSON RPC server.\n\n        Opens a connection to a remote client. The connection attempt will time\n        out if it takes longer than _SOCKET_TIMEOUT seconds. Each subsequent\n        operation over this socket will time out after _SOCKET_TIMEOUT seconds\n        as well.\n\n        Args:\n            port: int, The port this client should connect to.\n            addr: str, The address this client should connect to.\n            uid: int, The uid of the session to join, or UNKNOWN_UID to start a\n                 new session.\n            connection_timeout: int, The time to wait for the connection to come\n                 up.\n            cmd: JsonRpcCommand, The command to use for creating the connection.\n\n        Raises:\n            IOError: Raised when the socket times out from io error\n            socket.timeout: Raised when the socket waits to long for connection.\n            ProtocolError: Raised when there is an error in the protocol.\n        \"\"\"\n        self._counter = self._id_counter()\n        try:\n            self._conn = socket.create_connection((addr, port),\n                                                  _SOCKET_TIMEOUT)\n            self._conn.settimeout(_SOCKET_TIMEOUT)\n        except (socket.timeout, socket.error, IOError):\n            logging.exception(\"Failed to create socket connection!\")\n            raise\n        self.port = port\n        self._client = self._conn.makefile(mode=\"brw\")\n\n        resp = self._cmd(cmd, uid)\n        if not resp:\n            raise ProtocolError(ProtocolError.NO_RESPONSE_FROM_HANDSHAKE)\n        result = json.loads(str(resp, encoding=\"utf8\"))\n        if result['status']:\n            self.uid = result['uid']\n        else:\n            self.uid = UNKNOWN_UID\n\n    def close(self):\n        \"\"\"Close the connection to the remote client.\"\"\"\n        if self._conn:\n            self._conn.close()\n            self._conn = None\n\n    def _cmd(self, command, uid=None):\n        \"\"\"Send a command to the server.\n\n        Args:\n            command: str, The name of the command to execute.\n            uid: int, the uid of the session to send the command to.\n\n        Returns:\n            The line that was written back.\n        \"\"\"\n        if not uid:\n            uid = self.uid\n        self._client.write(\n            json.dumps({'cmd': command, 'uid': uid}).encode(\"utf8\") + b'\\n')\n        self._client.flush()\n        return self._client.readline()\n\n    def _rpc(self, method, *args):\n        \"\"\"Sends an rpc to the app.\n\n        Args:\n            method: str, The name of the method to execute.\n            args: any, The args of the method.\n\n        Returns:\n            The result of the rpc.\n\n        Raises:\n            ProtocolError: Something went wrong with the protocol.\n            ApiError: The rpc went through, however executed with errors.\n        \"\"\"\n        with self._lock:\n            apiid = next(self._counter)\n        data = {'id': apiid, 'method': method, 'params': args}\n        request = json.dumps(data)\n        self._client.write(request.encode(\"utf8\") + b'\\n')\n        self._client.flush()\n        response = self._client.readline()\n        if not response:\n            raise ProtocolError(ProtocolError.NO_RESPONSE_FROM_SERVER)\n        result = json.loads(str(response, encoding=\"utf8\"))\n        if result['error']:\n            raise ApiError(result['error'])\n        if result['id'] != apiid:\n            raise ProtocolError(ProtocolError.MISMATCHED_API_ID)\n        return result['result']\n\n    def __getattr__(self, name):\n        \"\"\"Wrapper for python magic to turn method calls into RPC calls.\"\"\"\n        def rpc_call(*args):\n            return self._rpc(name, *args)\n        return rpc_call\n\n    def _id_counter(self):\n        i = 0\n        while True:\n            yield i\n            i += 1\n", "path": "mobly/controllers/android_device_lib/jsonrpc_client_base.py"}]}