<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[282064] trunk/Tools</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/282064">282064</a></dd>
<dt>Author</dt> <dd>clopez@igalia.com</dd>
<dt>Date</dt> <dd>2021-09-06 12:48:30 -0700 (Mon, 06 Sep 2021)</dd>
</dl>

<h3>Log Message</h3>
<pre>[GTK] The Xvfb display server may fail to start sometimes causing tests to randomly crash
https://bugs.webkit.org/show_bug.cgi?id=229758

Reviewed by Philippe Normand.

Add a new function in XvfbDriver() to ensure that the display server
at a given display_id is replying as expected. Ask it for the screen
size at monitor 0 and compare the result with the one we expect to
have inside Xvfb. For doing this check a external python program is
called which does the query using GTK. Using a external program is
more robust against possible failures calling into GTK and also will
allow re-using this program also to check that the weston server is
also replying as expected for the weston driver (on a future patch).

If the Xvfb driver is not replying as expected then restart it and
try again, up to 3 retries.

Use this also on the weston driver to check that the Xvfb driver is
ready.

The code is both compatible with python2 and python3, when running on
python2 it will try first to use subprocess32 if available, otherwise
will use standard python2 subprocess without using the timeout feature.

* Scripts/webkitpy/common/system/executive_mock.py:
(MockProcess.__init__):
(MockProcess.communicate):
* Scripts/webkitpy/port/westondriver.py:
(WestonDriver._setup_environ_for_test):
* Scripts/webkitpy/port/westondriver_unittest.py:
(WestonXvfbDriverDisplayTest._xvfb_check_if_ready):
* Scripts/webkitpy/port/xvfbdriver.py:
(XvfbDriver):
(XvfbDriver.__init__):
(XvfbDriver.check_driver):
(XvfbDriver._xvfb_run):
(XvfbDriver._xvfb_screen_size):
(XvfbDriver._xvfb_stop):
(XvfbDriver._xvfb_check_if_ready):
(XvfbDriver._setup_environ_for_test):
(XvfbDriver.has_crashed):
(XvfbDriver.stop):
* Scripts/webkitpy/port/xvfbdriver_unittest.py:
(XvfbDriverTest.make_driver):
(XvfbDriverTest.assertDriverStartSuccessful):
(XvfbDriverTest.test_xvfb_start_and_ready):
(XvfbDriverTest.test_xvfb_start_arbitrary_worker_number):
(XvfbDriverTest.test_xvfb_not_replying):
* gtk/print-screen-size: Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkToolsChangeLog">trunk/Tools/ChangeLog</a></li>
<li><a href="#trunkToolsScriptswebkitpycommonsystemexecutive_mockpy">trunk/Tools/Scripts/webkitpy/common/system/executive_mock.py</a></li>
<li><a href="#trunkToolsScriptswebkitpyportwestondriverpy">trunk/Tools/Scripts/webkitpy/port/westondriver.py</a></li>
<li><a href="#trunkToolsScriptswebkitpyportwestondriver_unittestpy">trunk/Tools/Scripts/webkitpy/port/westondriver_unittest.py</a></li>
<li><a href="#trunkToolsScriptswebkitpyportxvfbdriverpy">trunk/Tools/Scripts/webkitpy/port/xvfbdriver.py</a></li>
<li><a href="#trunkToolsScriptswebkitpyportxvfbdriver_unittestpy">trunk/Tools/Scripts/webkitpy/port/xvfbdriver_unittest.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkToolsgtkprintscreensize">trunk/Tools/gtk/print-screen-size</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkToolsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Tools/ChangeLog (282063 => 282064)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/ChangeLog    2021-09-06 19:30:19 UTC (rev 282063)
+++ trunk/Tools/ChangeLog       2021-09-06 19:48:30 UTC (rev 282064)
</span><span class="lines">@@ -1,3 +1,55 @@
</span><ins>+2021-09-06  Carlos Alberto Lopez Perez  <clopez@igalia.com>
+
+        [GTK] The Xvfb display server may fail to start sometimes causing tests to randomly crash
+        https://bugs.webkit.org/show_bug.cgi?id=229758
+
+        Reviewed by Philippe Normand.
+
+        Add a new function in XvfbDriver() to ensure that the display server
+        at a given display_id is replying as expected. Ask it for the screen
+        size at monitor 0 and compare the result with the one we expect to
+        have inside Xvfb. For doing this check a external python program is
+        called which does the query using GTK. Using a external program is
+        more robust against possible failures calling into GTK and also will
+        allow re-using this program also to check that the weston server is
+        also replying as expected for the weston driver (on a future patch).
+
+        If the Xvfb driver is not replying as expected then restart it and
+        try again, up to 3 retries.
+
+        Use this also on the weston driver to check that the Xvfb driver is
+        ready.
+
+        The code is both compatible with python2 and python3, when running on
+        python2 it will try first to use subprocess32 if available, otherwise
+        will use standard python2 subprocess without using the timeout feature.
+
+        * Scripts/webkitpy/common/system/executive_mock.py:
+        (MockProcess.__init__):
+        (MockProcess.communicate):
+        * Scripts/webkitpy/port/westondriver.py:
+        (WestonDriver._setup_environ_for_test):
+        * Scripts/webkitpy/port/westondriver_unittest.py:
+        (WestonXvfbDriverDisplayTest._xvfb_check_if_ready):
+        * Scripts/webkitpy/port/xvfbdriver.py:
+        (XvfbDriver):
+        (XvfbDriver.__init__):
+        (XvfbDriver.check_driver):
+        (XvfbDriver._xvfb_run):
+        (XvfbDriver._xvfb_screen_size):
+        (XvfbDriver._xvfb_stop):
+        (XvfbDriver._xvfb_check_if_ready):
+        (XvfbDriver._setup_environ_for_test):
+        (XvfbDriver.has_crashed):
+        (XvfbDriver.stop):
+        * Scripts/webkitpy/port/xvfbdriver_unittest.py:
+        (XvfbDriverTest.make_driver):
+        (XvfbDriverTest.assertDriverStartSuccessful):
+        (XvfbDriverTest.test_xvfb_start_and_ready):
+        (XvfbDriverTest.test_xvfb_start_arbitrary_worker_number):
+        (XvfbDriverTest.test_xvfb_not_replying):
+        * gtk/print-screen-size: Added.
+
</ins><span class="cx"> 2021-09-06  Simon Fraser  <simon.fraser@apple.com>
</span><span class="cx"> 
</span><span class="cx">         Add a temporarily prefixed property for mask-mode, aliased to -webkit-mask-source-type
</span></span></pre></div>
<a id="trunkToolsScriptswebkitpycommonsystemexecutive_mockpy"></a>
<div class="modfile"><h4>Modified: trunk/Tools/Scripts/webkitpy/common/system/executive_mock.py (282063 => 282064)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/Scripts/webkitpy/common/system/executive_mock.py     2021-09-06 19:30:19 UTC (rev 282063)
+++ trunk/Tools/Scripts/webkitpy/common/system/executive_mock.py        2021-09-06 19:48:30 UTC (rev 282064)
</span><span class="lines">@@ -37,12 +37,12 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class MockProcess(object):
</span><del>-    def __init__(self, stdout='MOCK STDOUT\n', stderr=''):
</del><ins>+    def __init__(self, stdout='MOCK STDOUT\n', stderr='', returncode=0):
</ins><span class="cx">         self.pid = 42
</span><span class="cx">         self.stdout = BytesIO(string_utils.encode(stdout))
</span><span class="cx">         self.stderr = BytesIO(string_utils.encode(stderr))
</span><span class="cx">         self.stdin = BytesIO()
</span><del>-        self.returncode = 0
</del><ins>+        self.returncode = returncode
</ins><span class="cx">         self._is_running = False
</span><span class="cx"> 
</span><span class="cx">     def wait(self):
</span><span class="lines">@@ -49,9 +49,11 @@
</span><span class="cx">         self._is_running = False
</span><span class="cx">         return self.returncode
</span><span class="cx"> 
</span><del>-    def communicate(self, input=None):
</del><ins>+    def communicate(self, input=None, timeout=None):
</ins><span class="cx">         self._is_running = False
</span><del>-        return (self.stdout, self.stderr)
</del><ins>+        stdout = self.stdout.read() if isinstance(self.stdout, BytesIO) else self.stdout
+        stderr = self.stderr.read() if isinstance(self.stderr, BytesIO) else self.stderr
+        return (stdout, stderr)
</ins><span class="cx"> 
</span><span class="cx">     def poll(self):
</span><span class="cx">         if self._is_running:
</span></span></pre></div>
<a id="trunkToolsScriptswebkitpyportwestondriverpy"></a>
<div class="modfile"><h4>Modified: trunk/Tools/Scripts/webkitpy/port/westondriver.py (282063 => 282064)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/Scripts/webkitpy/port/westondriver.py        2021-09-06 19:30:19 UTC (rev 282063)
+++ trunk/Tools/Scripts/webkitpy/port/westondriver.py   2021-09-06 19:48:30 UTC (rev 282064)
</span><span class="lines">@@ -56,7 +56,18 @@
</span><span class="cx"> 
</span><span class="cx">     def _setup_environ_for_test(self):
</span><span class="cx">         driver_environment = super(WestonDriver, self)._setup_environ_for_test()
</span><del>-        driver_environment['DISPLAY'] = ":%d" % self._xvfbdriver._xvfb_run(driver_environment)
</del><ins>+        xvfb_display_id = self._xvfbdriver._xvfb_run(driver_environment)
+        driver_environment['DISPLAY'] = ':%d' % xvfb_display_id
+
+        # Ensure that Xvfb is ready and replying and expected before continuing, give it 3 tries.
+        if not self._xvfbdriver._xvfb_check_if_ready(xvfb_display_id):
+            self._xvfbdriver._current_retry_start_xvfb += 1
+            if self._xvfbdriver._current_retry_start_xvfb > 3:
+                _log.error('Failed to start Xvfb display server ... giving up after 3 retries.')
+                raise RuntimeError('Unable to start Xvfb display server')
+            _log.error('Failed to start Xvfb display server ... retrying [ %s of 3 ].' % self._xvfbdriver._current_retry_start_xvfb)
+            return self._setup_environ_for_test()
+
</ins><span class="cx">         weston_socket = 'WKTesting-weston-%032x' % random.getrandbits(128)
</span><span class="cx">         weston_command = ['weston', '--socket=%s' % weston_socket, '--width=1024', '--height=768', '--use-pixman']
</span><span class="cx">         if self._port._should_use_jhbuild():
</span></span></pre></div>
<a id="trunkToolsScriptswebkitpyportwestondriver_unittestpy"></a>
<div class="modfile"><h4>Modified: trunk/Tools/Scripts/webkitpy/port/westondriver_unittest.py (282063 => 282064)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/Scripts/webkitpy/port/westondriver_unittest.py       2021-09-06 19:30:19 UTC (rev 282063)
+++ trunk/Tools/Scripts/webkitpy/port/westondriver_unittest.py  2021-09-06 19:48:30 UTC (rev 282064)
</span><span class="lines">@@ -50,7 +50,10 @@
</span><span class="cx">     def _xvfb_run(self, environment):
</span><span class="cx">         return self._expected_xvfbdisplay
</span><span class="cx"> 
</span><ins>+    def _xvfb_check_if_ready(self, display_id):
+        return True
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class WestonDriverTest(unittest.TestCase):
</span><span class="cx">     def make_driver(self):
</span><span class="cx">         port = Port(MockSystemHost(log_executive=True), 'westondrivertestport', options=MockOptions(configuration='Release'))
</span></span></pre></div>
<a id="trunkToolsScriptswebkitpyportxvfbdriverpy"></a>
<div class="modfile"><h4>Modified: trunk/Tools/Scripts/webkitpy/port/xvfbdriver.py (282063 => 282064)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/Scripts/webkitpy/port/xvfbdriver.py  2021-09-06 19:30:19 UTC (rev 282063)
+++ trunk/Tools/Scripts/webkitpy/port/xvfbdriver.py     2021-09-06 19:48:30 UTC (rev 282064)
</span><span class="lines">@@ -1,5 +1,5 @@
</span><span class="cx"> # Copyright (C) 2010 Google Inc. All rights reserved.
</span><del>-# Copyright (C) 2014 Igalia S.L.
</del><ins>+# Copyright (C) 2014-2021 Igalia S.L.
</ins><span class="cx"> #
</span><span class="cx"> # Redistribution and use in source and binary forms, with or without
</span><span class="cx"> # modification, are permitted provided that the following conditions are
</span><span class="lines">@@ -33,6 +33,14 @@
</span><span class="cx"> import re
</span><span class="cx"> import time
</span><span class="cx"> 
</span><ins>+if sys.version_info[0] < 3:
+    try:
+        import subprocess32 as subprocess
+    except ImportError:
+        import subprocess
+else:
+    import subprocess
+
</ins><span class="cx"> from webkitcorepy import string_utils
</span><span class="cx"> from webkitpy.port.server_process import ServerProcess
</span><span class="cx"> from webkitpy.port.driver import Driver
</span><span class="lines">@@ -41,6 +49,13 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class XvfbDriver(Driver):
</span><ins>+
+    def __init__(self, *args, **kwargs):
+        Driver.__init__(self, *args, **kwargs)
+        self._xvfb_process = None
+        self._current_retry_start_xvfb = 0
+        self._print_screen_size_process_for_testing = None  # required for unit tests
+
</ins><span class="cx">     @staticmethod
</span><span class="cx">     def check_driver(port):
</span><span class="cx">         xvfb_findcmd = ['which', 'Xvfb']
</span><span class="lines">@@ -48,7 +63,7 @@
</span><span class="cx">                 xvfb_findcmd = port._jhbuild_wrapper + xvfb_findcmd
</span><span class="cx">         xvfb_found = port.host.executive.run_command(xvfb_findcmd, return_exit_code=True) == 0
</span><span class="cx">         if not xvfb_found:
</span><del>-            _log.error("No Xvfb found. Cannot run layout tests.")
</del><ins>+            _log.error('No Xvfb found. Cannot run layout tests.')
</ins><span class="cx">         return xvfb_found
</span><span class="cx"> 
</span><span class="cx">     def _xvfb_pipe(self):
</span><span class="lines">@@ -86,21 +101,78 @@
</span><span class="cx"> 
</span><span class="cx">     def _xvfb_run(self, environment):
</span><span class="cx">         read_fd, write_fd = self._xvfb_pipe()
</span><del>-        run_xvfb = ["Xvfb", "-displayfd", str(write_fd), "-screen",  "0", "1024x768x%s" % self._xvfb_screen_depth(), "-nolisten", "tcp"]
</del><ins>+        run_xvfb = ['Xvfb', '-displayfd', str(write_fd), '-nolisten', 'tcp',
+                    '+extension', 'GLX', '-ac', '-screen', '0',
+                    '%sx%s' % (self._xvfb_screen_size(), self._xvfb_screen_depth())]
</ins><span class="cx">         if self._port._should_use_jhbuild():
</span><span class="cx">             run_xvfb = self._port._jhbuild_wrapper + run_xvfb
</span><del>-        with open(os.devnull, 'w') as devnull:
-            # python3 will try to close the file descriptors by default
-            self._xvfb_process = self._port.host.executive.popen(run_xvfb, stderr=devnull, env=environment, close_fds=False)
-            display_id = self._xvfb_read_display_id(read_fd)
-
</del><ins>+        self._xvfb_process = self._port.host.executive.popen(run_xvfb, stdout=self._port.host.executive.PIPE, stderr=self._port.host.executive.STDOUT, env=environment, close_fds=False)
+        display_id = self._xvfb_read_display_id(read_fd)
</ins><span class="cx">         self._xvfb_close_pipe((read_fd, write_fd))
</span><del>-
</del><span class="cx">         return display_id
</span><span class="cx"> 
</span><span class="cx">     def _xvfb_screen_depth(self):
</span><span class="cx">         return os.environ.get('XVFB_SCREEN_DEPTH', '24')
</span><span class="cx"> 
</span><ins>+    def _xvfb_screen_size(self):
+        return os.environ.get('XVFB_SCREEN_SIZE', '1024x768')
+
+    def _xvfb_stop(self):
+        if self._xvfb_process:
+            self._port.host.executive.kill_process(self._xvfb_process.pid)
+            self._xvfb_process = None
+
+    def _xvfb_check_if_ready(self, display_id):
+        environment_print_screen_size_process = super(XvfbDriver, self)._setup_environ_for_test()
+        environment_print_screen_size_process['DISPLAY'] = ':%d' % display_id
+        environment_print_screen_size_process['GDK_BACKEND'] = 'x11'
+        waited_seconds_for_xvfb_ready = 0
+        xvfb_server_replying_as_expected = False
+        while True:
+            timeout_expired = False
+            query_failed = False
+            print_screen_size_process = self._print_screen_size_process_for_testing if self._print_screen_size_process_for_testing else \
+                subprocess.Popen([self._port.path_from_webkit_base('Tools', 'gtk', 'print-screen-size')],
+                                 stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=environment_print_screen_size_process)
+            # Python2 standard subprocess don't allows setting a timeout
+            if not hasattr(subprocess, 'TimeoutExpired'):
+                stdout, stderr = print_screen_size_process.communicate()
+            else:
+                try:
+                    stdout, stderr = print_screen_size_process.communicate(timeout=2)
+                except subprocess.TimeoutExpired:
+                    _log.debug('Timeout expired trying to query the Xvfb display server.')
+                    timeout_expired = True
+                    print_screen_size_process.kill()
+                    stdout, stderr = print_screen_size_process.communicate()
+                    waited_seconds_for_xvfb_ready += 2
+
+            if not timeout_expired:
+                if print_screen_size_process.returncode == 0:
+                    queried_screen_size = stdout.decode('UTF-8').strip()
+                    if queried_screen_size == self._xvfb_screen_size():
+                        xvfb_server_replying_as_expected = True
+                        _log.debug('The Xvfb display server ":%d" is ready and replying as expected.' % display_id)
+                        break
+                    else:
+                        _log.warning('The queried Xvfb screen size "%s" does not match the expectation "%s".' % (queried_screen_size, self._xvfb_screen_size()))
+                else:
+                    _log.warning('The print-screen-size tool returned non-zero status. stdout is "%s" and stderr is "%s"' % (stdout, stderr))
+                    query_failed = True
+            if timeout_expired or query_failed:
+                if self._xvfb_process.poll():
+                    xvfb_stdout, xvfb_stderr = self._xvfb_process.communicate()
+                    _log.error('The Xvfb display server has exited unexpectedly with a return code of %s. stdout is "%s" and stderr is "%s"' % (self._xvfb_process.poll(), xvfb_stdout, xvfb_stderr))
+                    break
+            if waited_seconds_for_xvfb_ready > 5:
+                _log.error('Timeout reached meanwhile waiting for the Xvfb display server to be ready')
+                break
+            _log.debug('Waiting for Xvfb display server to be ready.')
+            if not self._print_screen_size_process_for_testing:
+                time.sleep(1)  # only wait when not running unit tests
+            waited_seconds_for_xvfb_ready += 1
+        return xvfb_server_replying_as_expected
+
</ins><span class="cx">     def _setup_environ_for_test(self):
</span><span class="cx">         port_server_environment = self._port.setup_environ_for_server(self._server_name)
</span><span class="cx">         driver_environment = super(XvfbDriver, self)._setup_environ_for_test()
</span><span class="lines">@@ -111,10 +183,25 @@
</span><span class="cx">         driver_environment['UNDER_XVFB'] = 'yes'
</span><span class="cx">         driver_environment['GDK_BACKEND'] = 'x11'
</span><span class="cx">         driver_environment['LOCAL_RESOURCE_ROOT'] = self._port.layout_tests_dir()
</span><ins>+
+        # Ensure that Xvfb is ready and replying and expected before continuing, give it 3 tries.
+        if not self._xvfb_check_if_ready(display_id):
+            self._current_retry_start_xvfb += 1
+            if self._current_retry_start_xvfb > 3:
+                _log.error('Failed to start Xvfb display server ... giving up after 3 retries.')
+                raise RuntimeError('Unable to start Xvfb display server')
+            _log.error('Failed to start Xvfb display server ... retrying [ %s of 3 ].' % self._current_retry_start_xvfb)
+            return self._setup_environ_for_test()
+
</ins><span class="cx">         return driver_environment
</span><span class="cx"> 
</span><ins>+    def has_crashed(self):
+        if self._xvfb_process and self._xvfb_process.poll():
+            self._crashed_process_name = 'Xvfb'
+            self._crashed_pid = self._xvfb_process.pid
+            return True
+        return super(XvfbDriver, self).has_crashed()
+
</ins><span class="cx">     def stop(self):
</span><span class="cx">         super(XvfbDriver, self).stop()
</span><del>-        if getattr(self, '_xvfb_process', None):
-            self._port.host.executive.kill_process(self._xvfb_process.pid)
-            self._xvfb_process = None
</del><ins>+        self._xvfb_stop()
</ins></span></pre></div>
<a id="trunkToolsScriptswebkitpyportxvfbdriver_unittestpy"></a>
<div class="modfile"><h4>Modified: trunk/Tools/Scripts/webkitpy/port/xvfbdriver_unittest.py (282063 => 282064)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/Scripts/webkitpy/port/xvfbdriver_unittest.py 2021-09-06 19:30:19 UTC (rev 282063)
+++ trunk/Tools/Scripts/webkitpy/port/xvfbdriver_unittest.py    2021-09-06 19:48:30 UTC (rev 282064)
</span><span class="lines">@@ -31,7 +31,7 @@
</span><span class="cx"> import unittest
</span><span class="cx"> 
</span><span class="cx"> from webkitpy.common.system.filesystem_mock import MockFileSystem
</span><del>-from webkitpy.common.system.executive_mock import MockExecutive2
</del><ins>+from webkitpy.common.system.executive_mock import MockProcess
</ins><span class="cx"> from webkitpy.common.system.systemhost_mock import MockSystemHost
</span><span class="cx"> from webkitpy.port import Port
</span><span class="cx"> from webkitpy.port.server_process_mock import MockServerProcess
</span><span class="lines">@@ -44,7 +44,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class XvfbDriverTest(unittest.TestCase):
</span><del>-    def make_driver(self, worker_number=0, xorg_running=False, executive=None):
</del><ins>+    def make_driver(self, worker_number=0, xorg_running=False, executive=None, print_screen_size_process=None):
</ins><span class="cx">         port = Port(MockSystemHost(log_executive=True, executive=executive), 'xvfbdrivertestport', options=MockOptions(configuration='Release'))
</span><span class="cx">         port._config.build_directory = lambda configuration: "/mock-build"
</span><span class="cx">         port._test_runner_process_constructor = MockServerProcess
</span><span class="lines">@@ -57,6 +57,7 @@
</span><span class="cx">         driver._xvfb_pipe = lambda: (3, 4)
</span><span class="cx">         driver._xvfb_read_display_id = lambda x: 1
</span><span class="cx">         driver._xvfb_close_pipe = lambda p: None
</span><ins>+        driver._print_screen_size_process_for_testing = print_screen_size_process if print_screen_size_process else MockProcess(driver._xvfb_screen_size())
</ins><span class="cx">         driver._port_server_environment = port.setup_environ_for_server(port.driver_name())
</span><span class="cx">         return driver
</span><span class="cx"> 
</span><span class="lines">@@ -67,7 +68,7 @@
</span><span class="cx">         driver._xvfb_process = None
</span><span class="cx"> 
</span><span class="cx">     def assertDriverStartSuccessful(self, driver, expected_logs, expected_display, pixel_tests=False):
</span><del>-        with OutputCapture(level=logging.INFO) as captured:
</del><ins>+        with OutputCapture(level=logging.DEBUG) as captured:
</ins><span class="cx">             driver.start(pixel_tests, [])
</span><span class="cx">         self.assertEqual(captured.root.log.getvalue(), expected_logs)
</span><span class="cx"> 
</span><span class="lines">@@ -75,18 +76,35 @@
</span><span class="cx">         self.assertEqual(driver._server_process.env['DISPLAY'], expected_display)
</span><span class="cx">         self.assertEqual(driver._server_process.env['GDK_BACKEND'], 'x11')
</span><span class="cx"> 
</span><del>-    def test_start(self):
</del><ins>+    def test_xvfb_start_and_ready(self):
</ins><span class="cx">         driver = self.make_driver()
</span><del>-        expected_logs = ("MOCK popen: ['Xvfb', '-displayfd', '4', '-screen', '0', '1024x768x24', '-nolisten', 'tcp'], env=%s\n" % driver._port_server_environment)
-        self.assertDriverStartSuccessful(driver, expected_logs=expected_logs, expected_display=":1")
</del><ins>+        expected_display = ':1'
+        expected_logs = ("MOCK popen: ['Xvfb', '-displayfd', '4', '-nolisten', 'tcp', '+extension', 'GLX', '-ac', '-screen', '0', '1024x768x24'], env=%s\n" % driver._port_server_environment)
+        expected_logs += ('The Xvfb display server "%s" is ready and replying as expected.\n' % expected_display)
+        self.assertDriverStartSuccessful(driver, expected_logs=expected_logs, expected_display=expected_display)
</ins><span class="cx">         self.cleanup_driver(driver)
</span><span class="cx"> 
</span><del>-    def test_start_arbitrary_worker_number(self):
</del><ins>+    def test_xvfb_start_arbitrary_worker_number(self):
</ins><span class="cx">         driver = self.make_driver(worker_number=17)
</span><del>-        expected_logs = ("MOCK popen: ['Xvfb', '-displayfd', '4', '-screen', '0', '1024x768x24', '-nolisten', 'tcp'], env=%s\n" % driver._port_server_environment)
</del><ins>+        expected_display = ':1'
+        expected_logs = ("MOCK popen: ['Xvfb', '-displayfd', '4', '-nolisten', 'tcp', '+extension', 'GLX', '-ac', '-screen', '0', '1024x768x24'], env=%s\n" % driver._port_server_environment)
+        expected_logs += ('The Xvfb display server "%s" is ready and replying as expected.\n' % expected_display)
</ins><span class="cx">         self.assertDriverStartSuccessful(driver, expected_logs=expected_logs, expected_display=":1", pixel_tests=True)
</span><span class="cx">         self.cleanup_driver(driver)
</span><span class="cx"> 
</span><ins>+    def test_xvfb_not_replying(self):
+        failing_print_screen_size_process = MockProcess(returncode=1)
+        driver = self.make_driver(print_screen_size_process=failing_print_screen_size_process)
+        with OutputCapture(level=logging.INFO) as captured:
+            self.assertRaisesRegexp(RuntimeError, 'Unable to start Xvfb display server', driver.start, False, [])
+            captured_log = captured.root.log.getvalue()
+            for retry in [1, 2, 3]:
+                self.assertTrue('Failed to start Xvfb display server ... retrying [ {} of 3 ].'.format(retry) in captured_log)
+                self.assertFalse('Failed to start Xvfb display server ... retrying [ 4 of 3 ].' in captured_log)
+            self.assertTrue('Failed to start Xvfb display server ... giving up after 3 retries.' in captured_log)
+            self.assertTrue('The print-screen-size tool returned non-zero status' in captured_log)
+        self.cleanup_driver(driver)
+
</ins><span class="cx">     def test_stop(self):
</span><span class="cx">         port = Port(MockSystemHost(log_executive=True), 'xvfbdrivertestport', options=MockOptions(configuration='Release'))
</span><span class="cx">         port._executive.kill_process = lambda x: _log.info("MOCK kill_process pid: " + str(x))
</span></span></pre></div>
<a id="trunkToolsgtkprintscreensize"></a>
<div class="addfile"><h4>Added: trunk/Tools/gtk/print-screen-size (0 => 282064)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/gtk/print-screen-size                                (rev 0)
+++ trunk/Tools/gtk/print-screen-size   2021-09-06 19:48:30 UTC (rev 282064)
</span><span class="lines">@@ -0,0 +1,54 @@
</span><ins>+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 Igalia S.L.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import argparse
+import sys
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+from gi.repository import Gtk, Gdk
+
+
+def print_screen_size(monitor_id):
+    display = Gdk.Display.get_default()
+    if not display:
+        print('Unable to find a display')
+        return 1
+    monitor = display.get_monitor(monitor_id)
+    if not monitor:
+        print('Unable to find a monitor')
+        return 1
+    geometry = monitor.get_geometry()
+    print('{}x{}'.format(geometry.width,  geometry.height))
+    return 0
+
+
+# This tool is used from the layout tests when running with the Xvfb driver,
+# in order to check that the Xvfb server is alive and configured as expected.
+# Please, don't modify this tool without checking first how it is used at webkitpy/port/xvfbdriver.py
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='Print the current display size (widthxheight) of a given monitor')
+    parser.add_argument("-n", type=int, help='Number of the monitor the query', default=0)
+    args = parser.parse_args()
+    sys.exit(print_screen_size(args.n))
</ins><span class="cx">Property changes on: trunk/Tools/gtk/print-screen-size
</span><span class="cx">___________________________________________________________________
</span></span></pre></div>
<a id="svnexecutable"></a>
<div class="addfile"><h4>Added: svn:executable</h4></div>
<ins>+*
</ins><span class="cx">\ No newline at end of property
</span></div>

</body>
</html>