Takuto Ikuta | 38ebd0e | 2022-01-19 17:56:22 | [diff] [blame] | 1 | #!/usr/bin/env vpython3 |
[email protected] | 0a88a65 | 2012-03-09 00:34:45 | [diff] [blame] | 2 | # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 6 | """Runs tests with Xvfb and Openbox or Weston on Linux and normally on other |
| 7 | platforms.""" |
[email protected] | 0a88a65 | 2012-03-09 00:34:45 | [diff] [blame] | 8 | |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 9 | from __future__ import print_function |
| 10 | |
Brian Sheedy | 78bd6f4 | 2021-03-18 00:49:53 | [diff] [blame] | 11 | import copy |
[email protected] | 0a88a65 | 2012-03-09 00:34:45 | [diff] [blame] | 12 | import os |
carlosk | cac17251a | 2017-03-15 02:21:07 | [diff] [blame] | 13 | import os.path |
Ilia Samsonov | a0083530 | 2019-04-19 17:37:59 | [diff] [blame] | 14 | import random |
Tom Anderson | b910016 | 2019-12-02 22:42:43 | [diff] [blame] | 15 | import re |
[email protected] | 0a88a65 | 2012-03-09 00:34:45 | [diff] [blame] | 16 | import signal |
| 17 | import subprocess |
| 18 | import sys |
msw | 76cf5fe1 | 2015-07-16 23:48:57 | [diff] [blame] | 19 | import threading |
Ilia Samsonov | a0083530 | 2019-04-19 17:37:59 | [diff] [blame] | 20 | import time |
Takuto Ikuta | 38ebd0e | 2022-01-19 17:56:22 | [diff] [blame] | 21 | |
| 22 | import psutil |
| 23 | |
[email protected] | 0a88a65 | 2012-03-09 00:34:45 | [diff] [blame] | 24 | import test_env |
| 25 | |
Takuto Ikuta | 38ebd0e | 2022-01-19 17:56:22 | [diff] [blame] | 26 | |
Ilia Samsonov | a0083530 | 2019-04-19 17:37:59 | [diff] [blame] | 27 | class _XvfbProcessError(Exception): |
| 28 | """Exception raised when Xvfb cannot start.""" |
| 29 | pass |
| 30 | |
| 31 | |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 32 | class _WestonProcessError(Exception): |
| 33 | """Exception raised when Weston cannot start.""" |
| 34 | pass |
| 35 | |
| 36 | |
Jochen Eisinger | c322f4e9 | 2019-06-19 20:34:32 | [diff] [blame] | 37 | def kill(proc, name, timeout_in_seconds=10): |
msw | 76cf5fe1 | 2015-07-16 23:48:57 | [diff] [blame] | 38 | """Tries to kill |proc| gracefully with a timeout for each signal.""" |
Jochen Eisinger | c322f4e9 | 2019-06-19 20:34:32 | [diff] [blame] | 39 | if not proc: |
msw | 76cf5fe1 | 2015-07-16 23:48:57 | [diff] [blame] | 40 | return |
| 41 | |
Brian Sheedy | 1dfcb5e | 2021-03-30 00:43:21 | [diff] [blame] | 42 | proc.terminate() |
msw | 76cf5fe1 | 2015-07-16 23:48:57 | [diff] [blame] | 43 | thread = threading.Thread(target=proc.wait) |
| 44 | thread.start() |
| 45 | |
| 46 | thread.join(timeout_in_seconds) |
| 47 | if thread.is_alive(): |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 48 | print('%s running after SIGTERM, trying SIGKILL.\n' % name, file=sys.stderr) |
Jochen Eisinger | c322f4e9 | 2019-06-19 20:34:32 | [diff] [blame] | 49 | proc.kill() |
msw | 76cf5fe1 | 2015-07-16 23:48:57 | [diff] [blame] | 50 | |
| 51 | thread.join(timeout_in_seconds) |
| 52 | if thread.is_alive(): |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 53 | print('%s running after SIGTERM and SIGKILL; good luck!\n' % name, |
| 54 | file=sys.stderr) |
msw | 76cf5fe1 | 2015-07-16 23:48:57 | [diff] [blame] | 55 | |
| 56 | |
Tom Anderson | b910016 | 2019-12-02 22:42:43 | [diff] [blame] | 57 | def launch_dbus(env): |
| 58 | """Starts a DBus session. |
| 59 | |
| 60 | Works around a bug in GLib where it performs operations which aren't |
| 61 | async-signal-safe (in particular, memory allocations) between fork and exec |
| 62 | when it spawns subprocesses. This causes threads inside Chrome's browser and |
| 63 | utility processes to get stuck, and this harness to hang waiting for those |
| 64 | processes, which will never terminate. This doesn't happen on users' |
| 65 | machines, because they have an active desktop session and the |
| 66 | DBUS_SESSION_BUS_ADDRESS environment variable set, but it can happen on |
| 67 | headless environments. This is fixed by glib commit [1], but this workaround |
| 68 | will be necessary until the fix rolls into Chromium's CI. |
| 69 | |
Tom Anderson | 5cc50bc | 2019-12-03 20:16:20 | [diff] [blame] | 70 | [1] f2917459f745bebf931bccd5cc2c33aa81ef4d12 |
Tom Anderson | b910016 | 2019-12-02 22:42:43 | [diff] [blame] | 71 | |
| 72 | Modifies the passed in environment with at least DBUS_SESSION_BUS_ADDRESS and |
| 73 | DBUS_SESSION_BUS_PID set. |
Tom Anderson | 5cc50bc | 2019-12-03 20:16:20 | [diff] [blame] | 74 | |
| 75 | Returns the pid of the dbus-daemon if started, or None otherwise. |
Tom Anderson | b910016 | 2019-12-02 22:42:43 | [diff] [blame] | 76 | """ |
| 77 | if 'DBUS_SESSION_BUS_ADDRESS' in os.environ: |
| 78 | return |
| 79 | try: |
Stephen McGruer | 367e9b20 | 2021-03-19 13:32:18 | [diff] [blame] | 80 | dbus_output = subprocess.check_output( |
| 81 | ['dbus-launch'], env=env).decode('utf-8').split('\n') |
Tom Anderson | b910016 | 2019-12-02 22:42:43 | [diff] [blame] | 82 | for line in dbus_output: |
| 83 | m = re.match(r'([^=]+)\=(.+)', line) |
| 84 | if m: |
| 85 | env[m.group(1)] = m.group(2) |
Tom Anderson | 5cc50bc | 2019-12-03 20:16:20 | [diff] [blame] | 86 | return int(env['DBUS_SESSION_BUS_PID']) |
| 87 | except (subprocess.CalledProcessError, OSError, KeyError, ValueError) as e: |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 88 | print('Exception while running dbus_launch: %s' % e) |
Tom Anderson | b910016 | 2019-12-02 22:42:43 | [diff] [blame] | 89 | |
| 90 | |
Ilia Samsonov | a0083530 | 2019-04-19 17:37:59 | [diff] [blame] | 91 | # TODO(crbug.com/949194): Encourage setting flags to False. |
| 92 | def run_executable( |
| 93 | cmd, env, stdoutfile=None, use_openbox=True, use_xcompmgr=True): |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 94 | """Runs an executable within Weston or Xvfb on Linux or normally on other |
| 95 | platforms. |
msw | 11ac9a2 | 2015-07-14 23:36:04 | [diff] [blame] | 96 | |
Ilia Samsonov | a0083530 | 2019-04-19 17:37:59 | [diff] [blame] | 97 | The method sets SIGUSR1 handler for Xvfb to return SIGUSR1 |
| 98 | when it is ready for connections. |
| 99 | https://www.x.org/archive/X11R7.5/doc/man/man1/Xserver.1.html under Signals. |
Trent Apted | b185243 | 2018-01-25 02:15:10 | [diff] [blame] | 100 | |
Ilia Samsonov | a0083530 | 2019-04-19 17:37:59 | [diff] [blame] | 101 | Args: |
| 102 | cmd: Command to be executed. |
Tom Anderson | b936950 | 2021-05-19 01:06:40 | [diff] [blame] | 103 | env: A copy of environment variables. "DISPLAY" and will be set if Xvfb is |
| 104 | used. "WAYLAND_DISPLAY" will be set if Weston is used. |
Ilia Samsonov | a0083530 | 2019-04-19 17:37:59 | [diff] [blame] | 105 | stdoutfile: If provided, symbolization via script is disabled and stdout |
| 106 | is written to this file as well as to stdout. |
| 107 | use_openbox: A flag to use openbox process. |
| 108 | Some ChromeOS tests need a window manager. |
| 109 | use_xcompmgr: A flag to use xcompmgr process. |
| 110 | Some tests need a compositing wm to make use of transparent visuals. |
| 111 | |
| 112 | Returns: |
| 113 | the exit code of the specified commandline, or 1 on failure. |
msw | 11ac9a2 | 2015-07-14 23:36:04 | [diff] [blame] | 114 | """ |
dpranke | 7dad468 | 2017-04-26 23:14:55 | [diff] [blame] | 115 | |
| 116 | # It might seem counterintuitive to support a --no-xvfb flag in a script |
| 117 | # whose only job is to start xvfb, but doing so allows us to consolidate |
| 118 | # the logic in the layers of buildbot scripts so that we *always* use |
| 119 | # xvfb by default and don't have to worry about the distinction, it |
| 120 | # can remain solely under the control of the test invocation itself. |
| 121 | use_xvfb = True |
| 122 | if '--no-xvfb' in cmd: |
| 123 | use_xvfb = False |
| 124 | cmd.remove('--no-xvfb') |
| 125 | |
Maksim Sisov | 7eea23e | 2019-06-12 04:54:05 | [diff] [blame] | 126 | # Tests that run on Linux platforms with Ozone/Wayland backend require |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 127 | # a Weston instance. However, it is also required to disable xvfb so |
| 128 | # that Weston can run in a pure headless environment. |
Maksim Sisov | 7eea23e | 2019-06-12 04:54:05 | [diff] [blame] | 129 | use_weston = False |
| 130 | if '--use-weston' in cmd: |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 131 | if use_xvfb: |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 132 | print('Unable to use Weston with xvfb.\n', file=sys.stderr) |
Maksim Sisov | 7eea23e | 2019-06-12 04:54:05 | [diff] [blame] | 133 | return 1 |
| 134 | use_weston = True |
| 135 | cmd.remove('--use-weston') |
| 136 | |
Stephen McGruer | 367e9b20 | 2021-03-19 13:32:18 | [diff] [blame] | 137 | if sys.platform.startswith('linux') and use_xvfb: |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 138 | return _run_with_xvfb(cmd, env, stdoutfile, use_openbox, use_xcompmgr) |
| 139 | elif use_weston: |
| 140 | return _run_with_weston(cmd, env, stdoutfile) |
thomasanderson | 8c5f703 | 2016-11-28 22:59:16 | [diff] [blame] | 141 | else: |
Trent Apted | b185243 | 2018-01-25 02:15:10 | [diff] [blame] | 142 | return test_env.run_executable(cmd, env, stdoutfile) |
[email protected] | 0a88a65 | 2012-03-09 00:34:45 | [diff] [blame] | 143 | |
| 144 | |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 145 | def _run_with_xvfb(cmd, env, stdoutfile, use_openbox, use_xcompmgr): |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 146 | openbox_proc = None |
Maksim Sisov | 9739e17 | 2022-01-21 17:53:53 | [diff] [blame] | 147 | openbox_ready = MutableBoolean() |
| 148 | def set_openbox_ready(*_): |
| 149 | openbox_ready.setvalue(True) |
| 150 | |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 151 | xcompmgr_proc = None |
| 152 | xvfb_proc = None |
| 153 | xvfb_ready = MutableBoolean() |
| 154 | def set_xvfb_ready(*_): |
| 155 | xvfb_ready.setvalue(True) |
| 156 | |
Stephen McGruer | 367e9b20 | 2021-03-19 13:32:18 | [diff] [blame] | 157 | dbus_pid = None |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 158 | try: |
| 159 | signal.signal(signal.SIGTERM, raise_xvfb_error) |
| 160 | signal.signal(signal.SIGINT, raise_xvfb_error) |
| 161 | |
Tom Anderson | 9ae5991 | 2021-04-13 21:35:55 | [diff] [blame] | 162 | # Before [1], the maximum number of X11 clients was 256. After, the default |
| 163 | # limit is 256 with a configurable maximum of 512. On systems with a large |
| 164 | # number of CPUs, the old limit of 256 may be hit for certain test suites |
| 165 | # [2] [3], so we set the limit to 512 when possible. This flag is not |
| 166 | # available on Ubuntu 16.04 or 18.04, so a feature check is required. Xvfb |
| 167 | # does not have a '-version' option, so checking the '-help' output is |
| 168 | # required. |
| 169 | # |
| 170 | # [1] d206c240c0b85c4da44f073d6e9a692afb6b96d2 |
| 171 | # [2] https://crbug.com/1187948 |
| 172 | # [3] https://crbug.com/1120107 |
| 173 | xvfb_help = subprocess.check_output( |
| 174 | ['Xvfb', '-help'], stderr=subprocess.STDOUT).decode('utf8') |
| 175 | |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 176 | # Due to race condition for display number, Xvfb might fail to run. |
| 177 | # If it does fail, try again up to 10 times, similarly to xvfb-run. |
| 178 | for _ in range(10): |
| 179 | xvfb_ready.setvalue(False) |
| 180 | display = find_display() |
| 181 | |
Tom Anderson | 9ae5991 | 2021-04-13 21:35:55 | [diff] [blame] | 182 | xvfb_cmd = ['Xvfb', display, '-screen', '0', '1280x800x24', '-ac', |
| 183 | '-nolisten', 'tcp', '-dpi', '96', '+extension', 'RANDR'] |
| 184 | if '-maxclients' in xvfb_help: |
| 185 | xvfb_cmd += ['-maxclients', '512'] |
| 186 | |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 187 | # Sets SIGUSR1 to ignore for Xvfb to signal current process |
| 188 | # when it is ready. Due to race condition, USR1 signal could be sent |
| 189 | # before the process resets the signal handler, we cannot rely on |
| 190 | # signal handler to change on time. |
| 191 | signal.signal(signal.SIGUSR1, signal.SIG_IGN) |
Tom Anderson | 9ae5991 | 2021-04-13 21:35:55 | [diff] [blame] | 192 | xvfb_proc = subprocess.Popen(xvfb_cmd, stderr=subprocess.STDOUT, env=env) |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 193 | signal.signal(signal.SIGUSR1, set_xvfb_ready) |
| 194 | for _ in range(10): |
| 195 | time.sleep(.1) # gives Xvfb time to start or fail. |
| 196 | if xvfb_ready.getvalue() or xvfb_proc.poll() is not None: |
| 197 | break # xvfb sent ready signal, or already failed and stopped. |
| 198 | |
| 199 | if xvfb_proc.poll() is None: |
| 200 | break # xvfb is running, can proceed. |
| 201 | if xvfb_proc.poll() is not None: |
| 202 | raise _XvfbProcessError('Failed to start after 10 tries') |
| 203 | |
| 204 | env['DISPLAY'] = display |
Camillo Bruni | e73e2eb8 | 2021-11-03 15:59:39 | [diff] [blame] | 205 | # Set dummy variable for scripts. |
| 206 | env['XVFB_DISPLAY'] = display |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 207 | |
| 208 | dbus_pid = launch_dbus(env) |
| 209 | |
| 210 | if use_openbox: |
Maksim Sisov | 9739e17 | 2022-01-21 17:53:53 | [diff] [blame] | 211 | # Openbox will send a SIGUSR1 signal to the current process notifying the |
| 212 | # script it has started up. |
| 213 | current_proc_id = os.getpid() |
Maksim Sisov | a4d1cfbe | 2020-06-16 07:58:37 | [diff] [blame] | 214 | |
Maksim Sisov | 9739e17 | 2022-01-21 17:53:53 | [diff] [blame] | 215 | # The CMD that is passed via the --startup flag. |
| 216 | openbox_startup_cmd = 'kill --signal SIGUSR1 %s' % str(current_proc_id) |
| 217 | # Setup the signal handlers before starting the openbox instance. |
| 218 | signal.signal(signal.SIGUSR1, signal.SIG_IGN) |
| 219 | signal.signal(signal.SIGUSR1, set_openbox_ready) |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 220 | openbox_proc = subprocess.Popen( |
Maksim Sisov | 9739e17 | 2022-01-21 17:53:53 | [diff] [blame] | 221 | ['openbox', '--sm-disable', '--startup', |
| 222 | openbox_startup_cmd], stderr=subprocess.STDOUT, env=env) |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 223 | |
Maksim Sisov | 9739e17 | 2022-01-21 17:53:53 | [diff] [blame] | 224 | for _ in range(10): |
| 225 | time.sleep(.1) # gives Openbox time to start or fail. |
| 226 | if openbox_ready.getvalue() or openbox_proc.poll() is not None: |
| 227 | break # openbox sent ready signal, or failed and stopped. |
| 228 | |
| 229 | if openbox_proc.poll() is not None: |
| 230 | raise _XvfbProcessError('Failed to start OpenBox.') |
Maksim Sisov | a4d1cfbe | 2020-06-16 07:58:37 | [diff] [blame] | 231 | |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 232 | if use_xcompmgr: |
| 233 | xcompmgr_proc = subprocess.Popen( |
| 234 | 'xcompmgr', stderr=subprocess.STDOUT, env=env) |
| 235 | |
| 236 | return test_env.run_executable(cmd, env, stdoutfile) |
| 237 | except OSError as e: |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 238 | print('Failed to start Xvfb or Openbox: %s\n' % str(e), file=sys.stderr) |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 239 | return 1 |
| 240 | except _XvfbProcessError as e: |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 241 | print('Xvfb fail: %s\n' % str(e), file=sys.stderr) |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 242 | return 1 |
| 243 | finally: |
| 244 | kill(openbox_proc, 'openbox') |
| 245 | kill(xcompmgr_proc, 'xcompmgr') |
| 246 | kill(xvfb_proc, 'Xvfb') |
| 247 | |
| 248 | # dbus-daemon is not a subprocess, so we can't SIGTERM+waitpid() on it. |
| 249 | # To ensure it exits, use SIGKILL which should be safe since all other |
| 250 | # processes that it would have been servicing have exited. |
| 251 | if dbus_pid: |
| 252 | os.kill(dbus_pid, signal.SIGKILL) |
| 253 | |
| 254 | |
| 255 | # TODO(https://crbug.com/1060466): Write tests. |
| 256 | def _run_with_weston(cmd, env, stdoutfile): |
| 257 | weston_proc = None |
| 258 | |
| 259 | try: |
| 260 | signal.signal(signal.SIGTERM, raise_weston_error) |
| 261 | signal.signal(signal.SIGINT, raise_weston_error) |
| 262 | |
Maksim Sisov | 6f007d6 | 2021-03-05 18:11:33 | [diff] [blame] | 263 | dbus_pid = launch_dbus(env) |
| 264 | |
Maksim Sisov | 724f3aa | 2021-02-18 08:28:01 | [diff] [blame] | 265 | # The bundled weston (//third_party/weston) is used by Linux Ozone Wayland |
| 266 | # CI and CQ testers and compiled by //ui/ozone/platform/wayland whenever |
| 267 | # there is a dependency on the Ozone/Wayland and use_bundled_weston is set |
| 268 | # in gn args. However, some tests do not require Wayland or do not use |
| 269 | # //ui/ozone at all, but still have --use-weston flag set by the |
| 270 | # OZONE_WAYLAND variant (see //testing/buildbot/variants.pyl). This results |
| 271 | # in failures and those tests cannot be run because of the exception that |
| 272 | # informs about missing weston binary. Thus, to overcome the issue before |
| 273 | # a better solution is found, add a check for the "weston" binary here and |
| 274 | # run tests without Wayland compositor if the weston binary is not found. |
| 275 | # TODO(https://1178788): find a better solution. |
| 276 | if not os.path.isfile("./weston"): |
| 277 | print('Weston is not available. Starting without Wayland compositor') |
| 278 | return test_env.run_executable(cmd, env, stdoutfile) |
| 279 | |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 280 | # Set $XDG_RUNTIME_DIR if it is not set. |
| 281 | _set_xdg_runtime_dir(env) |
| 282 | |
Brian Sheedy | 781c8ca4 | 2021-03-08 22:03:21 | [diff] [blame] | 283 | # Weston is compiled along with the Ozone/Wayland platform, and is |
| 284 | # fetched as data deps. Thus, run it from the current directory. |
| 285 | # |
| 286 | # Weston is used with the following flags: |
| 287 | # 1) --backend=headless-backend.so - runs Weston in a headless mode |
| 288 | # that does not require a real GPU card. |
| 289 | # 2) --idle-time=0 - disables idle timeout, which prevents Weston |
| 290 | # to enter idle state. Otherwise, Weston stops to send frame callbacks, |
| 291 | # and tests start to time out (this typically happens after 300 seconds - |
| 292 | # the default time after which Weston enters the idle state). |
| 293 | # 3) --width && --height set size of a virtual display: we need to set |
| 294 | # an adequate size so that tests can have more room for managing size |
| 295 | # of windows. |
| 296 | # 4) --use-gl - Runs Weston using hardware acceleration instead of |
| 297 | # SwiftShader. |
| 298 | weston_cmd = ['./weston', '--backend=headless-backend.so', '--idle-time=0', |
| 299 | '--width=1024', '--height=768', '--modules=test-plugin.so'] |
| 300 | |
| 301 | if '--weston-use-gl' in cmd: |
| 302 | weston_cmd.append('--use-gl') |
| 303 | cmd.remove('--weston-use-gl') |
| 304 | |
Brian Sheedy | 78bd6f4 | 2021-03-18 00:49:53 | [diff] [blame] | 305 | if '--weston-debug-logging' in cmd: |
| 306 | cmd.remove('--weston-debug-logging') |
| 307 | env = copy.deepcopy(env) |
| 308 | env['WAYLAND_DEBUG'] = '1' |
| 309 | |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 310 | weston_proc_display = None |
| 311 | for _ in range(10): |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 312 | weston_proc = subprocess.Popen( |
Brian Sheedy | 781c8ca4 | 2021-03-08 22:03:21 | [diff] [blame] | 313 | weston_cmd, |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 314 | stderr=subprocess.STDOUT, env=env) |
| 315 | |
| 316 | # Get the $WAYLAND_DISPLAY set by Weston and pass it to the test launcher. |
| 317 | # Please note that this env variable is local for the process. That's the |
| 318 | # reason we have to read it from Weston separately. |
| 319 | weston_proc_display = _get_display_from_weston(weston_proc.pid) |
| 320 | if weston_proc_display is not None: |
| 321 | break # Weston could launch and we found the display. |
| 322 | |
| 323 | # If we couldn't find the display after 10 tries, raise an exception. |
| 324 | if weston_proc_display is None: |
| 325 | raise _WestonProcessError('Failed to start Weston.') |
| 326 | env['WAYLAND_DISPLAY'] = weston_proc_display |
| 327 | return test_env.run_executable(cmd, env, stdoutfile) |
| 328 | except OSError as e: |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 329 | print('Failed to start Weston: %s\n' % str(e), file=sys.stderr) |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 330 | return 1 |
| 331 | except _WestonProcessError as e: |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 332 | print('Weston fail: %s\n' % str(e), file=sys.stderr) |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 333 | return 1 |
| 334 | finally: |
| 335 | kill(weston_proc, 'weston') |
| 336 | |
Maksim Sisov | 6f007d6 | 2021-03-05 18:11:33 | [diff] [blame] | 337 | # dbus-daemon is not a subprocess, so we can't SIGTERM+waitpid() on it. |
| 338 | # To ensure it exits, use SIGKILL which should be safe since all other |
| 339 | # processes that it would have been servicing have exited. |
| 340 | if dbus_pid: |
| 341 | os.kill(dbus_pid, signal.SIGKILL) |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 342 | |
| 343 | def _get_display_from_weston(weston_proc_pid): |
| 344 | """Retrieves $WAYLAND_DISPLAY set by Weston. |
| 345 | |
| 346 | Searches for the child "weston-desktop-shell" process, takes its |
| 347 | environmental variables, and returns $WAYLAND_DISPLAY variable set |
| 348 | by that process. If the variable is not set, tries up to 10 times |
| 349 | and then gives up. |
| 350 | |
| 351 | Args: |
| 352 | weston_proc_pid: The process of id of the main Weston process. |
| 353 | |
| 354 | Returns: |
| 355 | the display set by Wayland, which clients can use to connect to. |
| 356 | |
| 357 | TODO(https://crbug.com/1060469): This is potentially error prone |
| 358 | function. See the bug for further details. |
| 359 | """ |
| 360 | |
Brian Sheedy | 298e8be | 2021-03-30 21:30:02 | [diff] [blame] | 361 | # Try 100 times as it is not known when Weston spawn child desktop shell |
| 362 | # process. The most seen so far is ~50 checks/~2.5 seconds, but startup |
| 363 | # is usually almost instantaneous. |
| 364 | for _ in range(100): |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 365 | # gives weston time to start or fail. |
| 366 | time.sleep(.05) |
| 367 | # Take the parent process. |
| 368 | parent = psutil.Process(weston_proc_pid) |
| 369 | if parent is None: |
| 370 | break # The process is not found. Give up. |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 371 | |
| 372 | # Traverse through all the children processes and find the |
| 373 | # "weston-desktop-shell" process that sets local to process env variables |
| 374 | # including the $WAYLAND_DISPLAY. |
| 375 | children = parent.children(recursive=True) |
| 376 | for process in children: |
| 377 | if process.name() == "weston-desktop-shell": |
| 378 | weston_proc_display = process.environ().get('WAYLAND_DISPLAY') |
| 379 | # If display is set, Weston could start successfully and we can use |
| 380 | # that display for Wayland connection in Chromium. |
| 381 | if weston_proc_display is not None: |
| 382 | return weston_proc_display |
| 383 | return None |
| 384 | |
| 385 | |
Ilia Samsonov | a0083530 | 2019-04-19 17:37:59 | [diff] [blame] | 386 | class MutableBoolean(object): |
| 387 | """Simple mutable boolean class. Used to be mutated inside an handler.""" |
| 388 | |
| 389 | def __init__(self): |
| 390 | self._val = False |
| 391 | |
| 392 | def setvalue(self, val): |
| 393 | assert isinstance(val, bool) |
| 394 | self._val = val |
| 395 | |
| 396 | def getvalue(self): |
| 397 | return self._val |
| 398 | |
| 399 | |
| 400 | def raise_xvfb_error(*_): |
| 401 | raise _XvfbProcessError('Terminated') |
| 402 | |
| 403 | |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 404 | def raise_weston_error(*_): |
| 405 | raise _WestonProcessError('Terminated') |
| 406 | |
| 407 | |
Ilia Samsonov | a0083530 | 2019-04-19 17:37:59 | [diff] [blame] | 408 | def find_display(): |
| 409 | """Iterates through X-lock files to find an available display number. |
| 410 | |
| 411 | The lower bound follows xvfb-run standard at 99, and the upper bound |
| 412 | is set to 119. |
| 413 | |
| 414 | Returns: |
| 415 | A string of a random available display number for Xvfb ':{99-119}'. |
| 416 | |
| 417 | Raises: |
| 418 | _XvfbProcessError: Raised when displays 99 through 119 are unavailable. |
| 419 | """ |
| 420 | |
| 421 | available_displays = [ |
| 422 | d for d in range(99, 120) |
| 423 | if not os.path.isfile('/tmp/.X{}-lock'.format(d)) |
| 424 | ] |
| 425 | if available_displays: |
| 426 | return ':{}'.format(random.choice(available_displays)) |
| 427 | raise _XvfbProcessError('Failed to find display number') |
| 428 | |
| 429 | |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 430 | def _set_xdg_runtime_dir(env): |
| 431 | """Sets the $XDG_RUNTIME_DIR variable if it hasn't been set before.""" |
| 432 | runtime_dir = env.get('XDG_RUNTIME_DIR') |
| 433 | if not runtime_dir: |
| 434 | runtime_dir = '/tmp/xdg-tmp-dir/' |
| 435 | if not os.path.exists(runtime_dir): |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 436 | os.makedirs(runtime_dir, 0o700) |
Maksim Sisov | eb30eff | 2020-03-11 09:25:58 | [diff] [blame] | 437 | env['XDG_RUNTIME_DIR'] = runtime_dir |
| 438 | |
| 439 | |
[email protected] | 0a88a65 | 2012-03-09 00:34:45 | [diff] [blame] | 440 | def main(): |
Shahbaz Youssefi | 1b47318 | 2020-06-10 18:05:25 | [diff] [blame] | 441 | usage = 'Usage: xvfb.py [command [--no-xvfb or --use-weston] args...]' |
thomasanderson | 3d07428 | 2016-12-06 18:21:12 | [diff] [blame] | 442 | if len(sys.argv) < 2: |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 443 | print(usage + '\n', file=sys.stderr) |
[email protected] | 0a88a65 | 2012-03-09 00:34:45 | [diff] [blame] | 444 | return 2 |
carlosk | cac17251a | 2017-03-15 02:21:07 | [diff] [blame] | 445 | |
| 446 | # If the user still thinks the first argument is the execution directory then |
| 447 | # print a friendly error message and quit. |
| 448 | if os.path.isdir(sys.argv[1]): |
Luke Zielinski | b57f7da | 2021-02-12 00:13:09 | [diff] [blame] | 449 | print('Invalid command: \"%s\" is a directory\n' % sys.argv[1], |
| 450 | file=sys.stderr) |
| 451 | print(usage + '\n', file=sys.stderr) |
carlosk | cac17251a | 2017-03-15 02:21:07 | [diff] [blame] | 452 | return 3 |
| 453 | |
Ilia Samsonov | a0083530 | 2019-04-19 17:37:59 | [diff] [blame] | 454 | return run_executable(sys.argv[1:], os.environ.copy()) |
[email protected] | 0a88a65 | 2012-03-09 00:34:45 | [diff] [blame] | 455 | |
| 456 | |
Ilia Samsonov | a0083530 | 2019-04-19 17:37:59 | [diff] [blame] | 457 | if __name__ == '__main__': |
[email protected] | 0a88a65 | 2012-03-09 00:34:45 | [diff] [blame] | 458 | sys.exit(main()) |