testing/marionette/transport/marionette_transport/transport.py

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:89ed6d83f870
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5 import errno
6 import json
7 import socket
8
9
10 class MarionetteTransport(object):
11 """ The Marionette socket client. This speaks the same protocol
12 as the remote debugger inside Gecko, in which messages are
13 always preceded by the message length and a colon, e.g.,
14
15 20:{'command': 'test'}
16 """
17
18 max_packet_length = 4096
19 connection_lost_msg = "Connection to Marionette server is lost. Check gecko.log (desktop firefox) or logcat (b2g) for errors."
20
21 def __init__(self, addr, port):
22 self.addr = addr
23 self.port = port
24 self.sock = None
25 self.traits = None
26 self.applicationType = None
27 self.actor = 'root'
28
29 def _recv_n_bytes(self, n):
30 """ Convenience method for receiving exactly n bytes from
31 self.sock (assuming it's open and connected).
32 """
33 data = ''
34 while len(data) < n:
35 chunk = self.sock.recv(n - len(data))
36 if chunk == '':
37 break
38 data += chunk
39 return data
40
41 def receive(self):
42 """ Receive the next complete response from the server, and return
43 it as a dict. Each response from the server is prepended by
44 len(message) + ':'.
45 """
46 assert(self.sock)
47 response = self.sock.recv(10)
48 sep = response.find(':')
49 length = response[0:sep]
50 if length != '':
51 response = response[sep + 1:]
52 response += self._recv_n_bytes(int(length) + 1 + len(length) - 10)
53 return json.loads(response)
54 else:
55 raise IOError(self.connection_lost_msg)
56
57 def connect(self, timeout=360.0):
58 """ Connect to the server and process the hello message we expect
59 to receive in response.
60 """
61 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
62 self.sock.settimeout(timeout)
63 try:
64 self.sock.connect((self.addr, self.port))
65 except:
66 # Unset self.sock so that the next attempt to send will cause
67 # another connection attempt.
68 self.sock = None
69 raise
70 hello = self.receive()
71 self.traits = hello.get('traits')
72 self.applicationType = hello.get('applicationType')
73
74 # get the marionette actor id
75 response = self.send({'to': 'root', 'name': 'getMarionetteID'})
76 self.actor = response['id']
77
78 def send(self, msg):
79 """ Send a message on the socket, prepending it with len(msg) + ':'.
80 """
81 if not self.sock:
82 self.connect()
83 if 'to' not in msg:
84 msg['to'] = self.actor
85 data = json.dumps(msg)
86 data = '%s:%s' % (len(data), data)
87
88 for packet in [data[i:i + self.max_packet_length] for i in
89 range(0, len(data), self.max_packet_length)]:
90 try:
91 self.sock.send(packet)
92 except IOError as e:
93 if e.errno == errno.EPIPE:
94 raise IOError("%s: %s" % (str(e)), self.connection_lost_msg)
95 else:
96 raise e
97
98 response = self.receive()
99 return response
100
101 def close(self):
102 """ Close the socket.
103 """
104 if self.sock:
105 self.sock.close()
106 self.sock = None

mercurial