source: trunk/Tools/Scripts/webkitpy/layout_tests/port/chromium.py @ 114583

Revision 114583, 27.3 KB checked in by dpranke@chromium.org, 2 years ago (diff)

Fix crash in chromium.py after r114877.

Unreviewed, build fix.

Missed updating one call site for get_crash_log() :(.

  • Scripts/webkitpy/layout_tests/port/chromium.py:

(ChromiumDriver.run_test):

  • Property svn:eol-style set to LF
  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2# Copyright (C) 2010 Google Inc. All rights reserved.
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 name of Google Inc. 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"""Chromium implementations of the Port interface."""
31
32import base64
33import errno
34import logging
35import re
36import signal
37import subprocess
38import sys
39import time
40import webbrowser
41
42from webkitpy.common.config import urls
43from webkitpy.common.system import executive
44from webkitpy.common.system.path import cygpath
45from webkitpy.layout_tests.controllers.manager import Manager
46from webkitpy.layout_tests.models import test_expectations
47from webkitpy.layout_tests.models.test_configuration import TestConfiguration
48from webkitpy.layout_tests.port.base import Port, VirtualTestSuite
49from webkitpy.layout_tests.port.driver import Driver, DriverOutput
50from webkitpy.layout_tests.port import builders
51from webkitpy.layout_tests.servers import http_server
52from webkitpy.layout_tests.servers import websocket_server
53
54
55_log = logging.getLogger(__name__)
56
57
58class ChromiumPort(Port):
59    """Abstract base class for Chromium implementations of the Port class."""
60
61    ALL_SYSTEMS = (
62        ('leopard', 'x86'),
63        ('snowleopard', 'x86'),
64        ('lion', 'x86'),
65        ('xp', 'x86'),
66        ('vista', 'x86'),
67        ('win7', 'x86'),
68        ('lucid', 'x86'),
69        ('lucid', 'x86_64'),
70        # FIXME: Technically this should be 'arm', but adding a third architecture type breaks TestConfigurationConverter.
71        # If we need this to be 'arm' in the future, then we first have to fix TestConfigurationConverter.
72        ('icecreamsandwich', 'x86'))
73
74    ALL_BASELINE_VARIANTS = [
75        'chromium-mac-lion', 'chromium-mac-snowleopard', 'chromium-mac-leopard',
76        'chromium-win-win7', 'chromium-win-vista', 'chromium-win-xp',
77        'chromium-linux-x86_64', 'chromium-linux-x86',
78    ]
79
80    CONFIGURATION_SPECIFIER_MACROS = {
81        'mac': ['leopard', 'snowleopard', 'lion'],
82        'win': ['xp', 'vista', 'win7'],
83        'linux': ['lucid'],
84        'android': ['icecreamsandwich'],
85    }
86
87    @classmethod
88    def _chromium_base_dir(cls, filesystem):
89        module_path = filesystem.path_to_module(cls.__module__)
90        offset = module_path.find('third_party')
91        if offset == -1:
92            return filesystem.join(module_path[0:module_path.find('Tools')], 'Source', 'WebKit', 'chromium')
93        else:
94            return module_path[0:offset]
95
96    def __init__(self, host, port_name, **kwargs):
97        Port.__init__(self, host, port_name, **kwargs)
98        # All sub-classes override this, but we need an initial value for testing.
99        self._chromium_base_dir_path = None
100
101    def _check_file_exists(self, path_to_file, file_description,
102                           override_step=None, logging=True):
103        """Verify the file is present where expected or log an error.
104
105        Args:
106            file_name: The (human friendly) name or description of the file
107                you're looking for (e.g., "HTTP Server"). Used for error logging.
108            override_step: An optional string to be logged if the check fails.
109            logging: Whether or not log the error messages."""
110        if not self._filesystem.exists(path_to_file):
111            if logging:
112                _log.error('Unable to find %s' % file_description)
113                _log.error('    at %s' % path_to_file)
114                if override_step:
115                    _log.error('    %s' % override_step)
116                    _log.error('')
117            return False
118        return True
119
120
121    def check_build(self, needs_http):
122        result = True
123
124        dump_render_tree_binary_path = self._path_to_driver()
125        result = self._check_file_exists(dump_render_tree_binary_path,
126                                         'test driver') and result
127        if result and self.get_option('build'):
128            result = self._check_driver_build_up_to_date(
129                self.get_option('configuration'))
130        else:
131            _log.error('')
132
133        helper_path = self._path_to_helper()
134        if helper_path:
135            result = self._check_file_exists(helper_path,
136                                             'layout test helper') and result
137
138        if self.get_option('pixel_tests'):
139            result = self.check_image_diff(
140                'To override, invoke with --no-pixel-tests') and result
141
142        # It's okay if pretty patch isn't available, but we will at
143        # least log a message.
144        self._pretty_patch_available = self.check_pretty_patch()
145
146        return result
147
148    def check_sys_deps(self, needs_http):
149        result = super(ChromiumPort, self).check_sys_deps(needs_http)
150
151        cmd = [self._path_to_driver(), '--check-layout-test-sys-deps']
152
153        local_error = executive.ScriptError()
154
155        def error_handler(script_error):
156            local_error.exit_code = script_error.exit_code
157
158        output = self._executive.run_command(cmd, error_handler=error_handler)
159        if local_error.exit_code:
160            _log.error('System dependencies check failed.')
161            _log.error('To override, invoke with --nocheck-sys-deps')
162            _log.error('')
163            _log.error(output)
164            return False
165        return result
166
167    def check_image_diff(self, override_step=None, logging=True):
168        image_diff_path = self._path_to_image_diff()
169        return self._check_file_exists(image_diff_path, 'image diff exe',
170                                       override_step, logging)
171
172    def diff_image(self, expected_contents, actual_contents, tolerance=None):
173        # FIXME: need unit tests for this.
174
175        # tolerance is not used in chromium. Make sure caller doesn't pass tolerance other than zero or None.
176        assert (tolerance is None) or tolerance == 0
177
178        # If only one of them exists, return that one.
179        if not actual_contents and not expected_contents:
180            return (None, 0)
181        if not actual_contents:
182            return (expected_contents, 0)
183        if not expected_contents:
184            return (actual_contents, 0)
185
186        tempdir = self._filesystem.mkdtemp()
187
188        expected_filename = self._filesystem.join(str(tempdir), "expected.png")
189        self._filesystem.write_binary_file(expected_filename, expected_contents)
190
191        actual_filename = self._filesystem.join(str(tempdir), "actual.png")
192        self._filesystem.write_binary_file(actual_filename, actual_contents)
193
194        diff_filename = self._filesystem.join(str(tempdir), "diff.png")
195
196        native_expected_filename = self._convert_path(expected_filename)
197        native_actual_filename = self._convert_path(actual_filename)
198        native_diff_filename = self._convert_path(diff_filename)
199
200        executable = self._path_to_image_diff()
201        comand = [executable, '--diff', native_actual_filename, native_expected_filename, native_diff_filename]
202
203        result = None
204        try:
205            exit_code = self._executive.run_command(comand, return_exit_code=True)
206            if exit_code == 0:
207                # The images are the same.
208                result = None
209            elif exit_code != 1:
210                _log.error("image diff returned an exit code of %s" % exit_code)
211                # Returning None here causes the script to think that we
212                # successfully created the diff even though we didn't.
213                # FIXME: Consider raising an exception here, so that the error
214                # is not accidentally overlooked while the test passes.
215                result = None
216        except OSError, e:
217            if e.errno == errno.ENOENT or e.errno == errno.EACCES:
218                _compare_available = False
219            else:
220                raise
221        finally:
222            if exit_code == 1:
223                result = self._filesystem.read_binary_file(native_diff_filename)
224            self._filesystem.rmtree(str(tempdir))
225        return (result, 0)  # FIXME: how to get % diff?
226
227    def path_from_chromium_base(self, *comps):
228        """Returns the full path to path made by joining the top of the
229        Chromium source tree and the list of path components in |*comps|."""
230        if self._chromium_base_dir_path is None:
231            self._chromium_base_dir_path = self._chromium_base_dir(self._filesystem)
232        return self._filesystem.join(self._chromium_base_dir_path, *comps)
233
234    def path_to_test_expectations_file(self):
235        return self.path_from_webkit_base('LayoutTests', 'platform', 'chromium', 'test_expectations.txt')
236
237    def default_results_directory(self):
238        try:
239            return self.path_from_chromium_base('webkit', self.get_option('configuration'), 'layout-test-results')
240        except AssertionError:
241            return self._build_path(self.get_option('configuration'), 'layout-test-results')
242
243    def setup_test_run(self):
244        # Delete the disk cache if any to ensure a clean test run.
245        dump_render_tree_binary_path = self._path_to_driver()
246        cachedir = self._filesystem.dirname(dump_render_tree_binary_path)
247        cachedir = self._filesystem.join(cachedir, "cache")
248        if self._filesystem.exists(cachedir):
249            self._filesystem.rmtree(cachedir)
250
251    def _driver_class(self):
252        return ChromiumDriver
253
254    def start_helper(self):
255        helper_path = self._path_to_helper()
256        if helper_path:
257            _log.debug("Starting layout helper %s" % helper_path)
258            # Note: Not thread safe: http://bugs.python.org/issue2320
259            self._helper = subprocess.Popen([helper_path],
260                stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None)
261            is_ready = self._helper.stdout.readline()
262            if not is_ready.startswith('ready'):
263                _log.error("layout_test_helper failed to be ready")
264
265    def stop_helper(self):
266        if self._helper:
267            _log.debug("Stopping layout test helper")
268            try:
269                self._helper.stdin.write("x\n")
270                self._helper.stdin.close()
271                self._helper.wait()
272            except IOError, e:
273                pass
274            finally:
275                self._helper = None
276
277
278    def exit_code_from_summarized_results(self, unexpected_results):
279        # Turn bots red for missing results.
280        return unexpected_results['num_regressions'] + unexpected_results['num_missing']
281
282    def configuration_specifier_macros(self):
283        return self.CONFIGURATION_SPECIFIER_MACROS
284
285    def all_baseline_variants(self):
286        return self.ALL_BASELINE_VARIANTS
287
288    def test_expectations(self):
289        """Returns the test expectations for this port.
290
291        Basically this string should contain the equivalent of a
292        test_expectations file. See test_expectations.py for more details."""
293        expectations_path = self.path_to_test_expectations_file()
294        return self._filesystem.read_text_file(expectations_path)
295
296    def _generate_all_test_configurations(self):
297        """Returns a sequence of the TestConfigurations the port supports."""
298        # By default, we assume we want to test every graphics type in
299        # every configuration on every system.
300        test_configurations = []
301        for version, architecture in self.ALL_SYSTEMS:
302            for build_type in self.ALL_BUILD_TYPES:
303                test_configurations.append(TestConfiguration(version, architecture, build_type))
304        return test_configurations
305
306    try_builder_names = frozenset([
307        'linux_layout',
308        'mac_layout',
309        'win_layout',
310        'linux_layout_rel',
311        'mac_layout_rel',
312        'win_layout_rel',
313    ])
314
315    def test_expectations_overrides(self):
316        # FIXME: It seems bad that run_webkit_tests.py uses a hardcoded dummy
317        # builder string instead of just using None.
318        builder_name = self.get_option('builder_name', 'DUMMY_BUILDER_NAME')
319        base_overrides = super(ChromiumPort, self).test_expectations_overrides()
320        if builder_name != 'DUMMY_BUILDER_NAME' and not '(deps)' in builder_name and not builder_name in self.try_builder_names:
321            return base_overrides
322
323        try:
324            overrides_path = self.path_from_chromium_base('webkit', 'tools', 'layout_tests', 'test_expectations.txt')
325        except AssertionError, e:
326            return base_overrides
327        if not self._filesystem.exists(overrides_path):
328            return base_overrides
329        return self._filesystem.read_text_file(overrides_path) + (base_overrides or '')
330
331    def repository_paths(self):
332        repos = super(ChromiumPort, self).repository_paths()
333        repos.append(('chromium', self.path_from_chromium_base('build')))
334        return repos
335
336    def virtual_test_suites(self):
337        return [
338            VirtualTestSuite('platform/chromium/virtual/gpu/fast/canvas',
339                             'fast/canvas',
340                             ['--enable-accelerated-2d-canvas']),
341            VirtualTestSuite('platform/chromium/virtual/gpu/canvas/philip',
342                             'canvas/philip',
343                             ['--enable-accelerated-2d-canvas']),
344            VirtualTestSuite('platform/chromium/virtual/threaded/compositing/visibility',
345                             'compositing/visibility',
346                             ['--enable-threaded-compositing']),
347        ]
348
349    #
350    # PROTECTED METHODS
351    #
352    # These routines should only be called by other methods in this file
353    # or any subclasses.
354    #
355
356    def _check_driver_build_up_to_date(self, configuration):
357        if configuration in ('Debug', 'Release'):
358            try:
359                debug_path = self._path_to_driver('Debug')
360                release_path = self._path_to_driver('Release')
361
362                debug_mtime = self._filesystem.mtime(debug_path)
363                release_mtime = self._filesystem.mtime(release_path)
364
365                if (debug_mtime > release_mtime and configuration == 'Release' or
366                    release_mtime > debug_mtime and configuration == 'Debug'):
367                    _log.warning('You are not running the most '
368                                 'recent DumpRenderTree binary. You need to '
369                                 'pass --debug or not to select between '
370                                 'Debug and Release.')
371                    _log.warning('')
372            # This will fail if we don't have both a debug and release binary.
373            # That's fine because, in this case, we must already be running the
374            # most up-to-date one.
375            except OSError:
376                pass
377        return True
378
379    def _chromium_baseline_path(self, platform):
380        if platform is None:
381            platform = self.name()
382        return self.path_from_webkit_base('LayoutTests', 'platform', platform)
383
384    def _convert_path(self, path):
385        """Handles filename conversion for subprocess command line args."""
386        # See note above in diff_image() for why we need this.
387        if sys.platform == 'cygwin':
388            return cygpath(path)
389        return path
390
391    def _path_to_image_diff(self):
392        binary_name = 'ImageDiff'
393        return self._build_path(self.get_option('configuration'), binary_name)
394
395
396# FIXME: This should inherit from WebKitDriver now that Chromium has a DumpRenderTree process like the rest of WebKit.
397class ChromiumDriver(Driver):
398    def __init__(self, port, worker_number, pixel_tests, no_timeout=False):
399        Driver.__init__(self, port, worker_number, pixel_tests, no_timeout)
400        self._proc = None
401        self._image_path = None
402
403    def _wrapper_options(self, pixel_tests):
404        cmd = []
405        if pixel_tests or self._pixel_tests:
406            if not self._image_path:
407                self._image_path = self._port._filesystem.join(self._port.results_directory(), 'png_result%s.png' % self._worker_number)
408            # See note above in diff_image() for why we need _convert_path().
409            cmd.append("--pixel-tests=" + self._port._convert_path(self._image_path))
410        # FIXME: This is not None shouldn't be necessary, unless --js-flags="''" changes behavior somehow?
411        if self._port.get_option('js_flags') is not None:
412            cmd.append('--js-flags="' + self._port.get_option('js_flags') + '"')
413        if self._no_timeout:
414            cmd.append("--no-timeout")
415
416        # FIXME: We should be able to build this list using only an array of
417        # option names, the options (optparse.Values) object, and the orignal
418        # list of options from the main method by looking up the option
419        # text from the options list if the value is non-None.
420        # FIXME: How many of these options are still used?
421        option_mappings = {
422            'startup_dialog': '--testshell-startup-dialog',
423            'gp_fault_error_box': '--gp-fault-error-box',
424            'stress_opt': '--stress-opt',
425            'stress_deopt': '--stress-deopt',
426            'threaded_compositing': '--enable-threaded-compositing',
427            'accelerated_2d_canvas': '--enable-accelerated-2d-canvas',
428            'accelerated_painting': '--enable-accelerated-painting',
429            'accelerated_video': '--enable-accelerated-video',
430            'enable_hardware_gpu': '--enable-hardware-gpu',
431            'per_tile_painting': '--enable-per-tile-painting',
432        }
433        for nrwt_option, drt_option in option_mappings.items():
434            if self._port.get_option(nrwt_option):
435                cmd.append(drt_option)
436
437        cmd.extend(self._port.get_option('additional_drt_flag', []))
438        return cmd
439
440    def cmd_line(self, pixel_tests, per_test_args):
441        cmd = self._command_wrapper(self._port.get_option('wrapper'))
442        cmd.append(self._port._path_to_driver())
443        # FIXME: Why does --test-shell exist?  TestShell is dead, shouldn't this be removed?
444        # It seems it's still in use in Tools/DumpRenderTree/chromium/DumpRenderTree.cpp as of 8/10/11.
445        cmd.append('--test-shell')
446        cmd.extend(self._wrapper_options(pixel_tests))
447        cmd.extend(per_test_args)
448
449        return cmd
450
451    def _start(self, pixel_tests, per_test_args):
452        assert not self._proc
453        # FIXME: This should use ServerProcess like WebKitDriver does.
454        # FIXME: We should be reading stderr and stdout separately like how WebKitDriver does.
455        close_fds = sys.platform != 'win32'
456        self._proc = subprocess.Popen(self.cmd_line(pixel_tests, per_test_args), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=close_fds)
457
458    def has_crashed(self):
459        if self._proc is None:
460            return False
461        return self._proc.poll() is not None
462
463    def _write_command_and_read_line(self, input=None):
464        """Returns a tuple: (line, did_crash)"""
465        try:
466            if input:
467                if isinstance(input, unicode):
468                    # DRT expects utf-8
469                    input = input.encode("utf-8")
470                self._proc.stdin.write(input)
471            # DumpRenderTree text output is always UTF-8.  However some tests
472            # (e.g. webarchive) may spit out binary data instead of text so we
473            # don't bother to decode the output.
474            line = self._proc.stdout.readline()
475            # We could assert() here that line correctly decodes as UTF-8.
476            return (line, False)
477        except IOError, e:
478            _log.error("IOError communicating w/ DRT: " + str(e))
479            return (None, True)
480
481    def _test_shell_command(self, uri, timeoutms, checksum):
482        cmd = uri
483        if timeoutms:
484            cmd += ' ' + str(timeoutms)
485        if checksum:
486            cmd += ' ' + checksum
487        cmd += "\n"
488        return cmd
489
490    def _output_image(self):
491        if self._image_path and self._port._filesystem.exists(self._image_path):
492            return self._port._filesystem.read_binary_file(self._image_path)
493        return None
494
495    def _output_image_with_retry(self):
496        # Retry a few more times because open() sometimes fails on Windows,
497        # raising "IOError: [Errno 13] Permission denied:"
498        retry_num = 50
499        timeout_seconds = 5.0
500        for _ in range(retry_num):
501            try:
502                return self._output_image()
503            except IOError, e:
504                if e.errno != errno.EACCES:
505                    raise e
506            # FIXME: We should have a separate retry delay.
507            # This implementation is likely to exceed the timeout before the expected number of retries.
508            time.sleep(timeout_seconds / retry_num)
509        return self._output_image()
510
511    def _clear_output_image(self):
512        if self._image_path and self._port._filesystem.exists(self._image_path):
513            self._port._filesystem.remove(self._image_path)
514
515    def run_test(self, driver_input):
516        if not self._proc:
517            self._start(driver_input.is_reftest or self._pixel_tests, driver_input.args)
518
519        output = []
520        error = []
521        crash = False
522        timeout = False
523        actual_uri = None
524        actual_checksum = None
525        self._clear_output_image()
526        start_time = time.time()
527        has_audio = False
528        has_base64 = False
529
530        uri = self.test_to_uri(driver_input.test_name)
531        cmd = self._test_shell_command(uri, driver_input.timeout, driver_input.image_hash)
532        line, crash = self._write_command_and_read_line(input=cmd)
533
534        while not crash and line.rstrip() != "#EOF":
535            # Make sure we haven't crashed.
536            if line == '' and self._proc.poll() is not None:
537                # This is hex code 0xc000001d, which is used for abrupt
538                # termination. This happens if we hit ctrl+c from the prompt
539                # and we happen to be waiting on DRT.
540                # sdoyon: Not sure for which OS and in what circumstances the
541                # above code is valid. What works for me under Linux to detect
542                # ctrl+c is for the subprocess returncode to be negative
543                # SIGINT. And that agrees with the subprocess documentation.
544                if (-1073741510 == self._proc.returncode or
545                    - signal.SIGINT == self._proc.returncode):
546                    raise KeyboardInterrupt
547                crash = True
548                break
549
550            # Don't include #URL lines in our output
551            if line.startswith("#URL:"):
552                actual_uri = line.rstrip()[5:]
553                if uri != actual_uri:
554                    # GURL capitalizes the drive letter of a file URL.
555                    if (not re.search("^file:///[a-z]:", uri) or uri.lower() != actual_uri.lower()):
556                        _log.fatal("Test got out of sync:\n|%s|\n|%s|" % (uri, actual_uri))
557                        raise AssertionError("test out of sync")
558            elif line.startswith("#MD5:"):
559                actual_checksum = line.rstrip()[5:]
560            elif line.startswith("#TEST_TIMED_OUT"):
561                timeout = True
562                # Test timed out, but we still need to read until #EOF.
563            elif line.startswith("Content-Type: audio/wav"):
564                has_audio = True
565            elif line.startswith("Content-Transfer-Encoding: base64"):
566                has_base64 = True
567            elif line.startswith("Content-Length:"):
568                pass
569            elif actual_uri:
570                output.append(line)
571            else:
572                error.append(line)
573
574            line, crash = self._write_command_and_read_line(input=None)
575
576        if crash and line is not None:
577            error.append(line)
578        run_time = time.time() - start_time
579        output_image = self._output_image_with_retry()
580
581        audio_bytes = None
582        text = None
583        if has_audio:
584            if has_base64:
585                audio_bytes = base64.b64decode(''.join(output))
586            else:
587                audio_bytes = ''.join(output).rstrip()
588        else:
589            text = ''.join(output)
590            if not text:
591                text = None
592
593        error = ''.join(error)
594        # Currently the stacktrace is in the text output, not error, so append the two together so
595        # that we can see stack in the output. See http://webkit.org/b/66806
596        # FIXME: We really should properly handle the stderr output separately.
597        crash_log = ''
598        crashed_process_name = None
599        crashed_pid = None
600        if crash:
601            crashed_process_name = self._port.driver_name()
602            if self._proc:
603                crashed_pid = self._proc.pid
604            crash_log = self._port._get_crash_log(crashed_process_name, crashed_pid, text, error, newer_than=start_time)
605            if text:
606                error = error + text
607
608        return DriverOutput(text, output_image, actual_checksum, audio=audio_bytes,
609            crash=crash, crashed_process_name=crashed_process_name, crashed_pid=crashed_pid, crash_log=crash_log,
610            test_time=run_time, timeout=timeout, error=error)
611
612    def start(self, pixel_tests, per_test_args):
613        if not self._proc:
614            self._start(pixel_tests, per_test_args)
615
616    def stop(self):
617        if not self._proc:
618            return
619        # FIXME: If we used ServerProcess all this would happen for free with ServerProces.stop()
620        self._proc.stdin.close()
621        self._proc.stdout.close()
622        if self._proc.stderr:
623            self._proc.stderr.close()
624        time_out_ms = self._port.get_option('time_out_ms')
625        if time_out_ms and not self._no_timeout:
626            timeout_ratio = float(time_out_ms) / self._port.default_test_timeout_ms()
627            kill_timeout_seconds = 3.0 * timeout_ratio if timeout_ratio > 1.0 else 3.0
628        else:
629            kill_timeout_seconds = 3.0
630
631        # Closing stdin/stdout/stderr hangs sometimes on OS X,
632        # (see __init__(), above), and anyway we don't want to hang
633        # the harness if DRT is buggy, so we wait a couple
634        # seconds to give DRT a chance to clean up, but then
635        # force-kill the process if necessary.
636        timeout = time.time() + kill_timeout_seconds
637        while self._proc.poll() is None and time.time() < timeout:
638            time.sleep(0.1)
639        if self._proc.poll() is None:
640            _log.warning('stopping test driver timed out, killing it')
641            self._port._executive.kill_process(self._proc.pid)
642        # FIXME: This is sometime None. What is wrong? assert self._proc.poll() is not None
643        if self._proc.poll() is not None:
644            self._proc.wait()
645        self._proc = None
Note: See TracBrowser for help on using the repository browser.