ac2d1175356d44c9c9d1650131416ff9f86f7c18
[WebKit-https.git] / Tools / Scripts / webkitpy / port / simulator_process.py
1 # Copyright (C) 2017 Apple Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
5 # are met:
6 # 1.  Redistributions of source code must retain the above copyright
7 #     notice, this list of conditions and the following disclaimer.
8 # 2.  Redistributions in binary form must reproduce the above copyright
9 #     notice, this list of conditions and the following disclaimer in the
10 #     documentation and/or other materials provided with the distribution.
11 #
12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
13 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
16 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
18 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
20 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
21 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23
24 import os
25 import time
26
27 from webkitpy.common.timeout_context import Timeout
28 from webkitpy.port.server_process import ServerProcess
29
30 class SimulatorProcess(ServerProcess):
31
32     class Popen(object):
33
34         def __init__(self, pid, stdin, stdout, stderr, device):
35             self.stdin = stdin
36             self.stdout = stdout
37             self.stderr = stderr
38             self.pid = pid
39             self.returncode = None
40             self._device = device
41
42         def poll(self):
43             if self.returncode:
44                 return self.returncode
45             if self._device.executive.check_running_pid(self.pid):
46                 self.returncode = None
47             else:
48                 self.returncode = 1
49             return self.returncode
50
51         def wait(self):
52             while not self.poll():
53                 time.sleep(0.01)  # In seconds
54             return self.returncode
55
56     # Python 2's implementation of makefile does not return a non-blocking file.
57     class NonBlockingFileFromSocket(object):
58
59         def __init__(self, sock, type):
60             self.socket = sock
61             self._file = os.fdopen(sock.fileno(), type, 0)
62             ServerProcess._set_file_nonblocking(self._file)
63
64         def __getattr__(self, name):
65             return getattr(self._file, name)
66
67         def close(self):
68             result = self._file.close()
69             self.socket.close()
70             return result
71
72     def __init__(self, port_obj, name, cmd, env=None, universal_newlines=False, treat_no_data_as_crash=False, target_host=None):
73         env['PORT'] = str(target_host.listening_port())  # The target_host should be a device.
74         super(SimulatorProcess, self).__init__(port_obj, name, cmd, env, universal_newlines, treat_no_data_as_crash, target_host)
75
76         self._bundle_id = port_obj.app_identifier_from_bundle(cmd[0])
77
78     def process_name(self):
79         return self._port.app_executable_from_bundle(self._cmd[0])
80
81     @staticmethod
82     def _accept_connection_create_file(server, type):
83         connection, address = server.accept()
84         assert address[0] == '127.0.0.1'
85         return SimulatorProcess.NonBlockingFileFromSocket(connection, type)
86
87     def _start(self):
88         if self._proc:
89             raise ValueError('{} already running'.format(self._name))
90         self._reset()
91
92         # Each device has a listening socket intitilaized during the port's setup_test_run.
93         # 3 client connections will be accepted for stdin, stdout and stderr in that order.
94         self._target_host.listening_socket.listen(3)
95         self._pid = self._target_host.launch_app(self._bundle_id, self._cmd[1:], env=self._env)
96
97         with Timeout(6, RuntimeError('Timed out waiting for pid {} to connect at port {}'.format(self._pid, self._target_host.listening_port()))):
98             stdin = None
99             stdout = None
100             stderr = None
101             try:
102                 # This order matches the client side connections in Tools/TestRunnerShared/IOSLayoutTestCommunication.cpp setUpIOSLayoutTestCommunication()
103                 stdin = SimulatorProcess._accept_connection_create_file(self._target_host.listening_socket, 'w')
104                 stdout = SimulatorProcess._accept_connection_create_file(self._target_host.listening_socket, 'rb')
105                 stderr = SimulatorProcess._accept_connection_create_file(self._target_host.listening_socket, 'rb')
106             except:
107                 # We set self._proc as _reset() and _kill() depend on it.
108                 self._proc = SimulatorProcess.Popen(self._pid, stdin, stdout, stderr, self._target_host)
109                 if self._proc.poll() is not None:
110                     self._reset()
111                     raise Exception('App {} with pid {} crashed before stdin could be attached'.format(os.path.basename(self._cmd[0]), self._pid))
112                 self._kill()
113                 self._reset()
114                 raise
115
116         self._proc = SimulatorProcess.Popen(self._pid, stdin, stdout, stderr, self._target_host)
117
118     def stop(self, timeout_secs=3.0):
119         # Only bother to check for leaks or stderr if the process is still running.
120         if self.poll() is None:
121             self._port.check_for_leaks(self.process_name(), self.pid())
122
123         if self._proc and self._proc.pid:
124             self._target_host.executive.kill_process(self._proc.pid)
125
126         return self._wait_for_stop(timeout_secs)