1#!/usr/bin/env vpython3 2# Copyright 2012 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Runs tests with Xvfb or Xorg and Openbox or Weston on Linux and normally on 6other platforms.""" 7 8from __future__ import print_function 9 10import copy 11import os 12import os.path 13import random 14import re 15import signal 16import socket 17import subprocess 18import sys 19import tempfile 20import threading 21import time 22import uuid 23 24import psutil 25 26import test_env 27 28DEFAULT_XVFB_WHD = '1280x800x24' 29 30# pylint: disable=useless-object-inheritance 31 32class _X11ProcessError(Exception): 33 """Exception raised when Xvfb or Xorg cannot start.""" 34 35 36class _WestonProcessError(Exception): 37 """Exception raised when Weston cannot start.""" 38 39 40def kill(proc, name, timeout_in_seconds=10): 41 """Tries to kill |proc| gracefully with a timeout for each signal.""" 42 if not proc: 43 return 44 45 thread = threading.Thread(target=proc.wait) 46 try: 47 proc.terminate() 48 thread.start() 49 50 thread.join(timeout_in_seconds) 51 if thread.is_alive(): 52 print('%s running after SIGTERM, trying SIGKILL.\n' % name, 53 file=sys.stderr) 54 proc.kill() 55 except OSError as e: 56 # proc.terminate()/kill() can raise, not sure if only ProcessLookupError 57 # which is explained in https://bugs.python.org/issue40550#msg382427 58 print('Exception while killing process %s: %s' % (name, e), file=sys.stderr) 59 60 thread.join(timeout_in_seconds) 61 if thread.is_alive(): 62 print('%s running after SIGTERM and SIGKILL; good luck!\n' % name, 63 file=sys.stderr) 64 65 66def launch_dbus(env): # pylint: disable=inconsistent-return-statements 67 """Starts a DBus session. 68 69 Works around a bug in GLib where it performs operations which aren't 70 async-signal-safe (in particular, memory allocations) between fork and exec 71 when it spawns subprocesses. This causes threads inside Chrome's browser and 72 utility processes to get stuck, and this harness to hang waiting for those 73 processes, which will never terminate. This doesn't happen on users' 74 machines, because they have an active desktop session and the 75 DBUS_SESSION_BUS_ADDRESS environment variable set, but it can happen on 76 headless environments. This is fixed by glib commit [1], but this workaround 77 will be necessary until the fix rolls into Chromium's CI. 78 79 [1] f2917459f745bebf931bccd5cc2c33aa81ef4d12 80 81 Modifies the passed in environment with at least DBUS_SESSION_BUS_ADDRESS and 82 DBUS_SESSION_BUS_PID set. 83 84 Returns the pid of the dbus-daemon if started, or None otherwise. 85 """ 86 if 'DBUS_SESSION_BUS_ADDRESS' in os.environ: 87 return 88 try: 89 dbus_output = subprocess.check_output(['dbus-launch'], 90 env=env).decode('utf-8').split('\n') 91 for line in dbus_output: 92 m = re.match(r'([^=]+)\=(.+)', line) 93 if m: 94 env[m.group(1)] = m.group(2) 95 return int(env['DBUS_SESSION_BUS_PID']) 96 except (subprocess.CalledProcessError, OSError, KeyError, ValueError) as e: 97 print('Exception while running dbus_launch: %s' % e) 98 99 100# TODO(crbug.com/40621504): Encourage setting flags to False. 101def run_executable(cmd, 102 env, 103 stdoutfile=None, 104 use_openbox=True, 105 use_xcompmgr=True, 106 xvfb_whd=None, 107 cwd=None): 108 """Runs an executable within Weston, Xvfb or Xorg on Linux or normally on 109 other platforms. 110 111 The method sets SIGUSR1 handler for Xvfb to return SIGUSR1 112 when it is ready for connections. 113 https://www.x.org/archive/X11R7.5/doc/man/man1/Xserver.1.html under Signals. 114 115 Args: 116 cmd: Command to be executed. 117 env: A copy of environment variables. "DISPLAY" and will be set if Xvfb is 118 used. "WAYLAND_DISPLAY" will be set if Weston is used. 119 stdoutfile: If provided, symbolization via script is disabled and stdout 120 is written to this file as well as to stdout. 121 use_openbox: A flag to use openbox process. 122 Some ChromeOS tests need a window manager. 123 use_xcompmgr: A flag to use xcompmgr process. 124 Some tests need a compositing wm to make use of transparent visuals. 125 xvfb_whd: WxHxD to pass to xvfb or DEFAULT_XVFB_WHD if None 126 cwd: Current working directory. 127 128 Returns: 129 the exit code of the specified commandline, or 1 on failure. 130 """ 131 132 # It might seem counterintuitive to support a --no-xvfb flag in a script 133 # whose only job is to start xvfb, but doing so allows us to consolidate 134 # the logic in the layers of buildbot scripts so that we *always* use 135 # this script by default and don't have to worry about the distinction, it 136 # can remain solely under the control of the test invocation itself. 137 # Historically, this flag turned off xvfb, but now turns off both X11 backings 138 # (xvfb/Xorg). As of crrev.com/c/5631242, Xorg became the default backing when 139 # no flags are supplied. Xorg is mostly a drop in replacement to Xvfb but has 140 # better support for dummy drivers and multi-screen testing (See: 141 # crbug.com/40257169 and http://tinyurl.com/4phsuupf). Requires Xorg binaries 142 # (package: xserver-xorg-core) 143 use_xvfb = False 144 use_xorg = True 145 146 if '--no-xvfb' in cmd: 147 use_xvfb = False 148 use_xorg = False # Backwards compatibly turns off all X11 backings. 149 cmd.remove('--no-xvfb') 150 151 # Support forcing legacy xvfb backing. 152 if '--use-xvfb' in cmd: 153 if not use_xorg and not use_xvfb: 154 print('Conflicting flags --use-xvfb and --no-xvfb\n', file=sys.stderr) 155 return 1 156 use_xvfb = True 157 use_xorg = False 158 cmd.remove('--use-xvfb') 159 160 # Tests that run on Linux platforms with Ozone/Wayland backend require 161 # a Weston instance. However, it is also required to disable xvfb so 162 # that Weston can run in a pure headless environment. 163 use_weston = False 164 if '--use-weston' in cmd: 165 if use_xvfb or use_xorg: 166 print('Unable to use Weston with xvfb or Xorg.\n', file=sys.stderr) 167 return 1 168 use_weston = True 169 cmd.remove('--use-weston') 170 171 if sys.platform.startswith('linux') and (use_xvfb or use_xorg): 172 return _run_with_x11(cmd, env, stdoutfile, use_openbox, use_xcompmgr, 173 use_xorg, xvfb_whd or DEFAULT_XVFB_WHD, cwd) 174 if use_weston: 175 return _run_with_weston(cmd, env, stdoutfile, cwd) 176 return test_env.run_executable(cmd, env, stdoutfile, cwd) 177 178 179def _re_search_command(regex, args, **kwargs): 180 """Runs a subprocess defined by `args` and returns a regex match for the 181 given expression on the output.""" 182 return re.search( 183 regex, 184 subprocess.check_output(args, 185 stderr=subprocess.STDOUT, 186 text=True, 187 **kwargs), re.IGNORECASE) 188 189 190def _make_xorg_modeline(width, height, refresh): 191 """Generates a tuple of a modeline (list of parameters) and label based off a 192 specified width, height and refresh rate. 193 See: https://www.x.org/archive/X11R7.0/doc/html/chips4.html""" 194 re_matches = _re_search_command( 195 r'Modeline "(.*)"\s+(.*)', 196 ['cvt', str(width), str(height), 197 str(refresh)], 198 ) 199 modeline_label = re_matches.group(1) 200 modeline = re_matches.group(2) 201 # Split the modeline string on spaces, and filter out empty element (cvt adds 202 # double spaces between in some parts). 203 return (modeline_label, list(filter(lambda a: a != '', modeline.split(' ')))) 204 205 206def _get_supported_virtual_sizes(default_whd): 207 """Returns a list of tuples (width, height) for supported monitor resolutions. 208 The list will always include the default size defined in `default_whd`""" 209 # Note: 4K resolution 3840x2160 doesn't seem to be supported and the mode 210 # silently gets dropped which makes subsequent calls to xrandr --addmode fail. 211 (default_width, default_height, _) = default_whd.split('x') 212 default_size = (int(default_width), int(default_height)) 213 return sorted( 214 set([default_size, (800, 600), (1024, 768), (1920, 1080), (1600, 1200)])) 215 216 217def _make_xorg_config(default_whd): 218 """Generates an Xorg config file and returns the file path. See: 219 https://www.x.org/releases/current/doc/man/man5/xorg.conf.5.xhtml""" 220 (_, _, depth) = default_whd.split('x') 221 mode_sizes = _get_supported_virtual_sizes(default_whd) 222 modelines = [] 223 mode_labels = [] 224 for width, height in mode_sizes: 225 (modeline_label, modeline) = _make_xorg_modeline(width, height, 60) 226 modelines.append('Modeline "%s" %s' % (modeline_label, ' '.join(modeline))) 227 mode_labels.append('"%s"' % modeline_label) 228 config = """ 229Section "Monitor" 230 Identifier "Monitor0" 231 HorizSync 5.0 - 1000.0 232 VertRefresh 5.0 - 200.0 233 %s 234EndSection 235Section "Device" 236 Identifier "Device0" 237 # Dummy driver requires package `xserver-xorg-video-dummy`. 238 Driver "dummy" 239 VideoRam 256000 240EndSection 241Section "Screen" 242 Identifier "Screen0" 243 Device "Device0" 244 Monitor "Monitor0" 245 SubSection "Display" 246 Depth %s 247 Modes %s 248 EndSubSection 249EndSection 250 """ % ('\n'.join(modelines), depth, ' '.join(mode_labels)) 251 config_file = os.path.join(tempfile.gettempdir(), 252 'xorg-%s.config' % uuid.uuid4().hex) 253 with open(config_file, 'w') as f: 254 f.write(config) 255 return config_file 256 257def _setup_xrandr(env, default_whd): 258 """Configures xrandr display(s)""" 259 260 # Calls xrandr with the provided argument array 261 def call_xrandr(args): 262 subprocess.check_call(['xrandr'] + args, 263 env=env, 264 stdout=subprocess.DEVNULL, 265 stderr=subprocess.STDOUT) 266 267 (default_width, default_height, _) = default_whd.split('x') 268 default_size = (int(default_width), int(default_height)) 269 270 # The minimum version of xserver-xorg-video-dummy is 0.4.0-1 which adds 271 # XRANDR support. Older versions will be missing the "DUMMY" outputs. 272 # Reliably checking the version is difficult, so check if the xrandr output 273 # includes the DUMMY displays before trying to configure them. 274 dummy_displays_available = _re_search_command('DUMMY[0-9]', ['xrandr', '-q'], 275 env=env) 276 if dummy_displays_available: 277 screen_sizes = _get_supported_virtual_sizes(default_whd) 278 output_names = ['DUMMY0', 'DUMMY1', 'DUMMY2', 'DUMMY3', 'DUMMY4'] 279 refresh_rate = 60 280 for width, height in screen_sizes: 281 (modeline_label, _) = _make_xorg_modeline(width, height, 60) 282 for output_name in output_names: 283 call_xrandr(['--addmode', output_name, modeline_label]) 284 (default_mode_label, _) = _make_xorg_modeline(*default_size, refresh_rate) 285 # Set the mode of all monitors to connect and activate them. 286 for i, name in enumerate(output_names): 287 args = ['--output', name, '--mode', default_mode_label] 288 if i > 0: 289 args += ['--right-of', output_names[i - 1]] 290 call_xrandr(args) 291 292 # Sets the primary monitor to the default size and marks the rest as disabled. 293 call_xrandr(['-s', '%dx%d' % default_size]) 294 # Set the DPI to something realistic (as required by some desktops). 295 call_xrandr(['--dpi', '96']) 296 297 298def _run_with_x11(cmd, env, stdoutfile, use_openbox, use_xcompmgr, use_xorg, 299 xvfb_whd, cwd): 300 """Runs with an X11 server. Uses Xvfb by default and Xorg when use_xorg is 301 True.""" 302 openbox_proc = None 303 openbox_ready = MutableBoolean() 304 305 def set_openbox_ready(*_): 306 openbox_ready.setvalue(True) 307 308 xcompmgr_proc = None 309 x11_proc = None 310 x11_ready = MutableBoolean() 311 312 def set_x11_ready(*_): 313 x11_ready.setvalue(True) 314 315 dbus_pid = None 316 x11_binary = 'Xorg' if use_xorg else 'Xvfb' 317 xorg_config_file = _make_xorg_config(xvfb_whd) if use_xorg else None 318 try: 319 signal.signal(signal.SIGTERM, raise_x11_error) 320 signal.signal(signal.SIGINT, raise_x11_error) 321 322 # Due to race condition for display number, Xvfb/Xorg might fail to run. 323 # If it does fail, try again up to 10 times, similarly to xvfb-run. 324 for _ in range(10): 325 x11_ready.setvalue(False) 326 display = find_display() 327 328 x11_cmd = None 329 if use_xorg: 330 x11_cmd = ['Xorg', display, '-noreset', '-config', xorg_config_file] 331 else: 332 x11_cmd = [ 333 'Xvfb', display, '-screen', '0', xvfb_whd, '-ac', '-nolisten', 334 'tcp', '-dpi', '96', '+extension', 'RANDR', '-maxclients', '512' 335 ] 336 337 # Sets SIGUSR1 to ignore for Xvfb/Xorg to signal current process 338 # when it is ready. Due to race condition, USR1 signal could be sent 339 # before the process resets the signal handler, we cannot rely on 340 # signal handler to change on time. 341 signal.signal(signal.SIGUSR1, signal.SIG_IGN) 342 x11_proc = subprocess.Popen(x11_cmd, stderr=subprocess.STDOUT, env=env) 343 signal.signal(signal.SIGUSR1, set_x11_ready) 344 for _ in range(30): 345 time.sleep(.1) # gives Xvfb/Xorg time to start or fail. 346 if x11_ready.getvalue() or x11_proc.poll() is not None: 347 break # xvfb/xorg sent ready signal, or already failed and stopped. 348 349 if x11_proc.poll() is None: 350 if x11_ready.getvalue(): 351 break # xvfb/xorg is ready 352 kill(x11_proc, x11_binary) # still not ready, give up and retry 353 354 if x11_proc.poll() is not None: 355 raise _X11ProcessError('Failed to start after 10 tries') 356 357 env['DISPLAY'] = display 358 # Set dummy variable for scripts. 359 env['XVFB_DISPLAY'] = display 360 361 dbus_pid = launch_dbus(env) 362 363 if use_openbox: 364 # Openbox will send a SIGUSR1 signal to the current process notifying the 365 # script it has started up. 366 current_proc_id = os.getpid() 367 368 # The CMD that is passed via the --startup flag. 369 openbox_startup_cmd = 'kill --signal SIGUSR1 %s' % str(current_proc_id) 370 # Setup the signal handlers before starting the openbox instance. 371 signal.signal(signal.SIGUSR1, signal.SIG_IGN) 372 signal.signal(signal.SIGUSR1, set_openbox_ready) 373 # Retry up to 10 times due to flaky fails (crbug.com/349187865) 374 for _ in range(10): 375 openbox_ready.setvalue(False) 376 openbox_proc = subprocess.Popen( 377 ['openbox', '--sm-disable', '--startup', openbox_startup_cmd], 378 stderr=subprocess.STDOUT, 379 env=env) 380 for _ in range(30): 381 time.sleep(.1) # gives Openbox time to start or fail. 382 if openbox_ready.getvalue() or openbox_proc.poll() is not None: 383 break # openbox sent ready signal, or failed and stopped. 384 385 if openbox_proc.poll() is None: 386 if openbox_ready.getvalue(): 387 break # openbox is ready 388 kill(openbox_proc, 'openbox') # still not ready, give up and retry 389 print('Openbox failed to start. Retrying.', file=sys.stderr) 390 391 if openbox_proc.poll() is not None: 392 raise _X11ProcessError('Failed to start openbox after 10 tries') 393 394 if use_xcompmgr: 395 xcompmgr_proc = subprocess.Popen('xcompmgr', 396 stderr=subprocess.STDOUT, 397 env=env) 398 399 if use_xorg: 400 _setup_xrandr(env, xvfb_whd) 401 402 return test_env.run_executable(cmd, env, stdoutfile, cwd) 403 except OSError as e: 404 print('Failed to start %s or Openbox: %s\n' % (x11_binary, str(e)), 405 file=sys.stderr) 406 return 1 407 except _X11ProcessError as e: 408 print('%s fail: %s\n' % (x11_binary, str(e)), file=sys.stderr) 409 return 1 410 finally: 411 kill(openbox_proc, 'openbox') 412 kill(xcompmgr_proc, 'xcompmgr') 413 kill(x11_proc, x11_binary) 414 if xorg_config_file is not None: 415 os.remove(xorg_config_file) 416 417 # dbus-daemon is not a subprocess, so we can't SIGTERM+waitpid() on it. 418 # To ensure it exits, use SIGKILL which should be safe since all other 419 # processes that it would have been servicing have exited. 420 if dbus_pid: 421 os.kill(dbus_pid, signal.SIGKILL) 422 423 424# TODO(crbug.com/40122046): Write tests. 425def _run_with_weston(cmd, env, stdoutfile, cwd): 426 weston_proc = None 427 428 try: 429 signal.signal(signal.SIGTERM, raise_weston_error) 430 signal.signal(signal.SIGINT, raise_weston_error) 431 432 dbus_pid = launch_dbus(env) 433 434 # The bundled weston (//third_party/weston) is used by Linux Ozone Wayland 435 # CI and CQ testers and compiled by //ui/ozone/platform/wayland whenever 436 # there is a dependency on the Ozone/Wayland and use_bundled_weston is set 437 # in gn args. However, some tests do not require Wayland or do not use 438 # //ui/ozone at all, but still have --use-weston flag set by the 439 # OZONE_WAYLAND variant (see //testing/buildbot/variants.pyl). This results 440 # in failures and those tests cannot be run because of the exception that 441 # informs about missing weston binary. Thus, to overcome the issue before 442 # a better solution is found, add a check for the "weston" binary here and 443 # run tests without Wayland compositor if the weston binary is not found. 444 # TODO(https://1178788): find a better solution. 445 if not os.path.isfile('./weston'): 446 print('Weston is not available. Starting without Wayland compositor') 447 return test_env.run_executable(cmd, env, stdoutfile, cwd) 448 449 # Set $XDG_RUNTIME_DIR if it is not set. 450 _set_xdg_runtime_dir(env) 451 452 # Write options that can't be passed via CLI flags to the config file. 453 # 1) panel-position=none - disables the panel, which might interfere with 454 # the tests by blocking mouse input. 455 with open(_weston_config_file_path(), 'w') as weston_config_file: 456 weston_config_file.write('[shell]\npanel-position=none') 457 458 # Weston is compiled along with the Ozone/Wayland platform, and is 459 # fetched as data deps. Thus, run it from the current directory. 460 # 461 # Weston is used with the following flags: 462 # 1) --backend=headless-backend.so - runs Weston in a headless mode 463 # that does not require a real GPU card. 464 # 2) --idle-time=0 - disables idle timeout, which prevents Weston 465 # to enter idle state. Otherwise, Weston stops to send frame callbacks, 466 # and tests start to time out (this typically happens after 300 seconds - 467 # the default time after which Weston enters the idle state). 468 # 3) --modules=ui-controls.so,systemd-notify.so - enables support for the 469 # ui-controls Wayland protocol extension and the systemd-notify protocol. 470 # 4) --width && --height set size of a virtual display: we need to set 471 # an adequate size so that tests can have more room for managing size 472 # of windows. 473 # 5) --config=... - tells Weston to use our custom config. 474 weston_cmd = [ 475 './weston', '--backend=headless-backend.so', '--idle-time=0', 476 '--modules=ui-controls.so,systemd-notify.so', '--width=1280', 477 '--height=800', '--config=' + _weston_config_file_path() 478 ] 479 480 if '--weston-use-gl' in cmd: 481 # Runs Weston using hardware acceleration instead of SwiftShader. 482 weston_cmd.append('--use-gl') 483 cmd.remove('--weston-use-gl') 484 485 if '--weston-debug-logging' in cmd: 486 cmd.remove('--weston-debug-logging') 487 env = copy.deepcopy(env) 488 env['WAYLAND_DEBUG'] = '1' 489 490 # We use the systemd-notify protocol to detect whether weston has launched 491 # successfully. We listen on a unix socket and set the NOTIFY_SOCKET 492 # environment variable to the socket's path. If we tell it to load its 493 # systemd-notify module, weston will send a 'READY=1' message to the socket 494 # once it has loaded that module. 495 # See the sd_notify(3) man page and weston's compositor/systemd-notify.c for 496 # more details. 497 with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM 498 | socket.SOCK_NONBLOCK) as notify_socket: 499 notify_socket.bind(_weston_notify_socket_address()) 500 env['NOTIFY_SOCKET'] = _weston_notify_socket_address() 501 502 weston_proc_display = None 503 for _ in range(10): 504 weston_proc = subprocess.Popen(weston_cmd, 505 stderr=subprocess.STDOUT, 506 env=env) 507 508 for _ in range(25): 509 time.sleep(0.1) # Gives weston some time to start. 510 try: 511 if notify_socket.recv(512) == b'READY=1': 512 break 513 except BlockingIOError: 514 continue 515 516 for _ in range(25): 517 # The 'READY=1' message is sent as soon as weston loads the 518 # systemd-notify module. This happens shortly before spawning its 519 # subprocesses (e.g. desktop-shell). Wait some more to ensure they 520 # have been spawned. 521 time.sleep(0.1) 522 523 # Get the $WAYLAND_DISPLAY set by Weston and pass it to the test 524 # launcher. Please note that this env variable is local for the 525 # process. That's the reason we have to read it from Weston 526 # separately. 527 weston_proc_display = _get_display_from_weston(weston_proc.pid) 528 if weston_proc_display is not None: 529 break # Weston could launch and we found the display. 530 531 # Also break from the outer loop. 532 if weston_proc_display is not None: 533 break 534 535 # If we couldn't find the display after 10 tries, raise an exception. 536 if weston_proc_display is None: 537 raise _WestonProcessError('Failed to start Weston.') 538 539 env.pop('NOTIFY_SOCKET') 540 541 env['WAYLAND_DISPLAY'] = weston_proc_display 542 if '--chrome-wayland-debugging' in cmd: 543 cmd.remove('--chrome-wayland-debugging') 544 env['WAYLAND_DEBUG'] = '1' 545 else: 546 env['WAYLAND_DEBUG'] = '0' 547 548 return test_env.run_executable(cmd, env, stdoutfile, cwd) 549 except OSError as e: 550 print('Failed to start Weston: %s\n' % str(e), file=sys.stderr) 551 return 1 552 except _WestonProcessError as e: 553 print('Weston fail: %s\n' % str(e), file=sys.stderr) 554 return 1 555 finally: 556 kill(weston_proc, 'weston') 557 558 if os.path.exists(_weston_notify_socket_address()): 559 os.remove(_weston_notify_socket_address()) 560 561 if os.path.exists(_weston_config_file_path()): 562 os.remove(_weston_config_file_path()) 563 564 # dbus-daemon is not a subprocess, so we can't SIGTERM+waitpid() on it. 565 # To ensure it exits, use SIGKILL which should be safe since all other 566 # processes that it would have been servicing have exited. 567 if dbus_pid: 568 os.kill(dbus_pid, signal.SIGKILL) 569 570 571def _weston_notify_socket_address(): 572 return os.path.join(tempfile.gettempdir(), '.xvfb.py-weston-notify.sock') 573 574 575def _weston_config_file_path(): 576 return os.path.join(tempfile.gettempdir(), '.xvfb.py-weston.ini') 577 578 579def _get_display_from_weston(weston_proc_pid): 580 """Retrieves $WAYLAND_DISPLAY set by Weston. 581 582 Returns the $WAYLAND_DISPLAY variable from one of weston's subprocesses. 583 584 Weston updates this variable early in its startup in the main process, but we 585 can only read the environment variables as they were when the process was 586 created. Therefore we must use one of weston's subprocesses, which are all 587 spawned with the new value for $WAYLAND_DISPLAY. Any of them will do, as they 588 all have the same value set. 589 590 Args: 591 weston_proc_pid: The process of id of the main Weston process. 592 593 Returns: 594 the display set by Wayland, which clients can use to connect to. 595 """ 596 597 # Take the parent process. 598 parent = psutil.Process(weston_proc_pid) 599 if parent is None: 600 return None # The process is not found. Give up. 601 602 # Traverse through all the children processes and find one that has 603 # $WAYLAND_DISPLAY set. 604 children = parent.children(recursive=True) 605 for process in children: 606 weston_proc_display = process.environ().get('WAYLAND_DISPLAY') 607 # If display is set, Weston could start successfully and we can use 608 # that display for Wayland connection in Chromium. 609 if weston_proc_display is not None: 610 return weston_proc_display 611 return None 612 613 614class MutableBoolean(object): 615 """Simple mutable boolean class. Used to be mutated inside an handler.""" 616 617 def __init__(self): 618 self._val = False 619 620 def setvalue(self, val): 621 assert isinstance(val, bool) 622 self._val = val 623 624 def getvalue(self): 625 return self._val 626 627 628def raise_x11_error(*_): 629 raise _X11ProcessError('Terminated') 630 631 632def raise_weston_error(*_): 633 raise _WestonProcessError('Terminated') 634 635 636def find_display(): 637 """Iterates through X-lock files to find an available display number. 638 639 The lower bound follows xvfb-run standard at 99, and the upper bound 640 is set to 119. 641 642 Returns: 643 A string of a random available display number for Xvfb ':{99-119}'. 644 645 Raises: 646 _X11ProcessError: Raised when displays 99 through 119 are unavailable. 647 """ 648 649 available_displays = [ 650 d for d in range(99, 120) 651 if not os.path.isfile('/tmp/.X{}-lock'.format(d)) 652 ] 653 if available_displays: 654 return ':{}'.format(random.choice(available_displays)) 655 raise _X11ProcessError('Failed to find display number') 656 657 658def _set_xdg_runtime_dir(env): 659 """Sets the $XDG_RUNTIME_DIR variable if it hasn't been set before.""" 660 runtime_dir = env.get('XDG_RUNTIME_DIR') 661 if not runtime_dir: 662 runtime_dir = '/tmp/xdg-tmp-dir/' 663 if not os.path.exists(runtime_dir): 664 os.makedirs(runtime_dir, 0o700) 665 env['XDG_RUNTIME_DIR'] = runtime_dir 666 667 668def main(): 669 usage = ('[command [--no-xvfb or --use-xvfb or --use-weston] args...]\n' 670 '\t --no-xvfb\t\tTurns off all X11 backings (Xvfb and Xorg).\n' 671 '\t --use-xvfb\t\tForces legacy Xvfb backing instead of Xorg.\n' 672 '\t --use-weston\t\tEnable Wayland server.') 673 # TODO(crbug.com/326283384): Argparse-ify this. 674 if len(sys.argv) < 2: 675 print(usage + '\n', file=sys.stderr) 676 return 2 677 678 # If the user still thinks the first argument is the execution directory then 679 # print a friendly error message and quit. 680 if os.path.isdir(sys.argv[1]): 681 print('Invalid command: \"%s\" is a directory\n' % sys.argv[1], 682 file=sys.stderr) 683 print(usage + '\n', file=sys.stderr) 684 return 3 685 686 return run_executable(sys.argv[1:], os.environ.copy()) 687 688 689if __name__ == '__main__': 690 sys.exit(main()) 691