testing/marionette/transport/marionette_transport/transport.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/marionette/transport/marionette_transport/transport.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,106 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +import errno
     1.9 +import json
    1.10 +import socket
    1.11 +
    1.12 +
    1.13 +class MarionetteTransport(object):
    1.14 +    """ The Marionette socket client.  This speaks the same protocol
    1.15 +        as the remote debugger inside Gecko, in which messages are
    1.16 +        always preceded by the message length and a colon, e.g.,
    1.17 +
    1.18 +        20:{'command': 'test'}
    1.19 +    """
    1.20 +
    1.21 +    max_packet_length = 4096
    1.22 +    connection_lost_msg = "Connection to Marionette server is lost. Check gecko.log (desktop firefox) or logcat (b2g) for errors."
    1.23 +
    1.24 +    def __init__(self, addr, port):
    1.25 +        self.addr = addr
    1.26 +        self.port = port
    1.27 +        self.sock = None
    1.28 +        self.traits = None
    1.29 +        self.applicationType = None
    1.30 +        self.actor = 'root'
    1.31 +
    1.32 +    def _recv_n_bytes(self, n):
    1.33 +        """ Convenience method for receiving exactly n bytes from
    1.34 +            self.sock (assuming it's open and connected).
    1.35 +        """
    1.36 +        data = ''
    1.37 +        while len(data) < n:
    1.38 +            chunk = self.sock.recv(n - len(data))
    1.39 +            if chunk == '':
    1.40 +                break
    1.41 +            data += chunk
    1.42 +        return data
    1.43 +
    1.44 +    def receive(self):
    1.45 +        """ Receive the next complete response from the server, and return
    1.46 +            it as a dict.  Each response from the server is prepended by
    1.47 +            len(message) + ':'.
    1.48 +        """
    1.49 +        assert(self.sock)
    1.50 +        response = self.sock.recv(10)
    1.51 +        sep = response.find(':')
    1.52 +        length = response[0:sep]
    1.53 +        if length != '':
    1.54 +            response = response[sep + 1:]
    1.55 +            response += self._recv_n_bytes(int(length) + 1 + len(length) - 10)
    1.56 +            return json.loads(response)
    1.57 +        else:
    1.58 +            raise IOError(self.connection_lost_msg)
    1.59 +
    1.60 +    def connect(self, timeout=360.0):
    1.61 +        """ Connect to the server and process the hello message we expect
    1.62 +            to receive in response.
    1.63 +        """
    1.64 +        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    1.65 +        self.sock.settimeout(timeout)
    1.66 +        try:
    1.67 +            self.sock.connect((self.addr, self.port))
    1.68 +        except:
    1.69 +            # Unset self.sock so that the next attempt to send will cause
    1.70 +            # another connection attempt.
    1.71 +            self.sock = None
    1.72 +            raise
    1.73 +        hello = self.receive()
    1.74 +        self.traits = hello.get('traits')
    1.75 +        self.applicationType = hello.get('applicationType')
    1.76 +
    1.77 +        # get the marionette actor id
    1.78 +        response = self.send({'to': 'root', 'name': 'getMarionetteID'})
    1.79 +        self.actor = response['id']
    1.80 +
    1.81 +    def send(self, msg):
    1.82 +        """ Send a message on the socket, prepending it with len(msg) + ':'.
    1.83 +        """
    1.84 +        if not self.sock:
    1.85 +            self.connect()
    1.86 +        if 'to' not in msg:
    1.87 +            msg['to'] = self.actor
    1.88 +        data = json.dumps(msg)
    1.89 +        data = '%s:%s' % (len(data), data)
    1.90 +
    1.91 +        for packet in [data[i:i + self.max_packet_length] for i in
    1.92 +                       range(0, len(data), self.max_packet_length)]:
    1.93 +            try: 
    1.94 +                self.sock.send(packet)
    1.95 +            except IOError as e:
    1.96 +                if e.errno == errno.EPIPE:
    1.97 +                    raise IOError("%s: %s" % (str(e)), self.connection_lost_msg)
    1.98 +                else:
    1.99 +                    raise e
   1.100 +
   1.101 +        response = self.receive()
   1.102 +        return response
   1.103 +
   1.104 +    def close(self):
   1.105 +        """ Close the socket.
   1.106 +        """
   1.107 +        if self.sock:
   1.108 +            self.sock.close()
   1.109 +        self.sock = None

mercurial