Use simctl instead of LayoutTestRelay
[WebKit.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 errno
25 import os
26 import signal
27 import time
28
29 from webkitpy.port.server_process import ServerProcess
30 from webkitpy.xcode.simulator import Simulator
31
32
33 class SimulatorProcess(ServerProcess):
34
35     class Popen(object):
36
37         def __init__(self, pid, stdin, stdout, stderr):
38             self.stdin = stdin
39             self.stdout = stdout
40             self.stderr = stderr
41             self.pid = pid
42             self.returncode = None
43
44         def poll(self):
45             if self.returncode:
46                 return self.returncode
47             try:
48                 os.kill(self.pid, 0)
49             except OSError, err:
50                 assert err.errno == errno.ESRCH
51                 self.returncode = 1
52             return self.returncode
53
54         def wait(self):
55             while not self.poll():
56                 time.sleep(0.01)  # In seconds
57             return self.returncode
58
59     def __init__(self, port_obj, name, cmd, env=None, universal_newlines=False, treat_no_data_as_crash=False, worker_number=None):
60         self._bundle_id = port_obj.app_identifier_from_bundle(cmd[0])
61         self._device = Simulator(port_obj.host).find_device_by_udid(port_obj.device_id_for_worker_number(worker_number))
62         if not self._device.install_app(cmd[0]):
63             raise RuntimeError('Failed to install app {} on simulator device {}'.format(cmd[0], self._device.udid))
64         env['IPC_IDENTIFIER'] = self._bundle_id + '-' + self._device.udid
65
66         # This location matches the location used by WebKitTestRunner and DumpRenderTree
67         # for the other side of these fifos.
68         file_location = '/tmp/' + env['IPC_IDENTIFIER']
69         self._in_path = file_location + '_IN'
70         self._out_path = file_location + '_OUT'
71         self._error_path = file_location + '_ERROR'
72
73         super(SimulatorProcess, self).__init__(port_obj, name, cmd, env, universal_newlines, treat_no_data_as_crash)
74
75     def _reset(self):
76         super(SimulatorProcess, self)._reset()
77
78         # Unlinks are needed on reset in the event that the Python code unexpectedly
79         # fails between _start() and kill().  This can be caused by a SIGKILL or a crash.
80         # This ensures that os.mkfifo() will not be obstructed by previous fifos.
81         # Other files will still cause os.mkfifo() to fail.
82         try:
83             os.unlink(self._in_path)
84         except:
85             pass
86         try:
87             os.unlink(self._out_path)
88         except:
89             pass
90         try:
91             os.unlink(self._error_path)
92         except:
93             pass
94
95     def _start(self):
96         if self._proc:
97             raise ValueError('{} already running'.format(self._name))
98         self._reset()
99
100         FIFO_PERMISSION_FLAGS = 0600  # Only owner can read and write
101         os.mkfifo(self._in_path, FIFO_PERMISSION_FLAGS)
102         os.mkfifo(self._out_path, FIFO_PERMISSION_FLAGS)
103         os.mkfifo(self._error_path, FIFO_PERMISSION_FLAGS)
104
105         stdout = os.fdopen(os.open(self._out_path, os.O_RDONLY | os.O_NONBLOCK), 'rb')
106         stderr = os.fdopen(os.open(self._error_path, os.O_RDONLY | os.O_NONBLOCK), 'rb')
107
108         self._pid = self._device.launch_app(self._bundle_id, self._cmd[1:], env=self._env)
109
110         def handler(signum, frame):
111             assert signum == signal.SIGALRM
112             raise Exception('Timed out waiting for process to open {}'.format(self._in_path))
113         signal.signal(signal.SIGALRM, handler)
114         signal.alarm(3)  # In seconds
115
116         stdin = None
117         try:
118             stdin = open(self._in_path, 'w', 0)  # Opening with no buffering, like popen
119         except:
120             # We set self._proc as _reset() and _kill() depend on it.
121             self._proc = SimulatorProcess.Popen(self._pid, stdin, stdout, stderr)
122             if self._proc.poll() is not None:
123                 self._reset()
124                 raise Exception('App {} crashed before stdin could be attached'.format(os.path.basename(self._cmd[0])))
125             self._kill()
126             self._reset()
127             raise
128         signal.alarm(0)  # Cancel alarm
129
130         self._proc = SimulatorProcess.Popen(self._pid, stdin, stdout, stderr)
131
132     def _kill(self):
133         self._device.terminate_app(self._bundle_id)
134         super(SimulatorProcess, self)._kill()