ab33d3a27c44a41c75c39a45e6d19915f157506a
[WebKit-https.git] / Tools / Scripts / webkitpy / port / xvfbdriver.py
1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 # Copyright (C) 2014 Igalia S.L.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the Google name nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 import logging
31 import os
32 import re
33 import time
34
35 from webkitpy.port.server_process import ServerProcess
36 from webkitpy.port.driver import Driver
37
38 _log = logging.getLogger(__name__)
39
40
41 class XvfbDriver(Driver):
42     @staticmethod
43     def check_driver(port):
44         xvfb_findcmd = ['which', 'Xvfb']
45         if port._should_use_jhbuild():
46                 xvfb_findcmd = port._jhbuild_wrapper + xvfb_findcmd
47         xvfb_found = port.host.executive.run_command(xvfb_findcmd, return_exit_code=True) is 0
48         if not xvfb_found:
49             _log.error("No Xvfb found. Cannot run layout tests.")
50         return xvfb_found
51
52     def _xvfb_pipe(self):
53         return os.pipe()
54
55     def _xvfb_read_display_id(self, read_fd):
56         import errno
57         import select
58
59         fd_set = [read_fd]
60         while fd_set:
61             try:
62                 fd_list = select.select(fd_set, [], [])[0]
63             except select.error as e:
64                 if e.args[0] == errno.EINTR:
65                     continue
66                 raise
67
68             if read_fd in fd_list:
69                 # We only expect a number, so first read should be enough.
70                 display_id = os.read(read_fd, 256).strip('\n')
71                 fd_set = []
72
73         return int(display_id)
74
75     def _xvfb_close_pipe(self, pipe_fds):
76         os.close(pipe_fds[0])
77         os.close(pipe_fds[1])
78
79     def _xvfb_run(self, environment):
80         read_fd, write_fd = self._xvfb_pipe()
81         run_xvfb = ["Xvfb", "-displayfd", str(write_fd), "-screen",  "0", "1024x768x%s" % self._xvfb_screen_depth(), "-nolisten", "tcp"]
82         if self._port._should_use_jhbuild():
83             run_xvfb = self._port._jhbuild_wrapper + run_xvfb
84         with open(os.devnull, 'w') as devnull:
85             self._xvfb_process = self._port.host.executive.popen(run_xvfb, stderr=devnull, env=environment)
86             display_id = self._xvfb_read_display_id(read_fd)
87
88         self._xvfb_close_pipe((read_fd, write_fd))
89
90         return display_id
91
92     def _xvfb_screen_depth(self):
93         return os.environ.get('XVFB_SCREEN_DEPTH', '24')
94
95     def _setup_environ_for_test(self):
96         environment = self._port.setup_environ_for_server(self._server_name)
97         display_id = self._xvfb_run(environment)
98
99         # We must do this here because the DISPLAY number depends on _worker_number
100         environment['DISPLAY'] = ":%d" % display_id
101         environment['GDK_BACKEND'] = 'x11'
102         environment['LOCAL_RESOURCE_ROOT'] = self._port.layout_tests_dir()
103         if self._driver_tempdir is not None:
104             environment['DUMPRENDERTREE_TEMP'] = str(self._driver_tempdir)
105             # Currently on WebKit2, there is no API for setting the application
106             # cache directory. Each worker should have it's own and it should be
107             # cleaned afterwards, so we set it to inside the temporary folder by
108             # prepending XDG_CACHE_HOME with DUMPRENDERTREE_TEMP.
109             environment['XDG_CACHE_HOME'] = self._port.host.filesystem.join(str(self._driver_tempdir), 'appcache')
110         return environment
111
112     def _start(self, pixel_tests, per_test_args):
113         self.stop()
114         self._driver_tempdir = self._port._driver_tempdir(self._target_host)
115         self._crashed_process_name = None
116         self._crashed_pid = None
117         self._server_process = self._port._server_process_constructor(self._port, self._server_name, self.cmd_line(pixel_tests, per_test_args), self._setup_environ_for_test())
118         self._server_process.start()
119
120     def stop(self):
121         super(XvfbDriver, self).stop()
122         if getattr(self, '_xvfb_process', None):
123             self._port.host.executive.kill_process(self._xvfb_process.pid)
124             self._xvfb_process = None