Chromium Code Reviews| Index: Tools/Scripts/webkitpy/layout_tests/port/browser_test_driver.py |
| diff --git a/Tools/Scripts/webkitpy/layout_tests/port/browser_test_driver.py b/Tools/Scripts/webkitpy/layout_tests/port/browser_test_driver.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..70e400f093aca37bd2e284f442dc7fc31b2fc437 |
| --- /dev/null |
| +++ b/Tools/Scripts/webkitpy/layout_tests/port/browser_test_driver.py |
| @@ -0,0 +1,203 @@ |
| +# Copyright (C) 2014 Google Inc. All rights reserved. |
| +# |
| +# Redistribution and use in source and binary forms, with or without |
| +# modification, are permitted provided that the following conditions are |
| +# met: |
| +# |
| +# * Redistributions of source code must retain the above copyright |
| +# notice, this list of conditions and the following disclaimer. |
| +# * 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. |
| +# * Neither the Google name nor the names of its |
| +# contributors may be used to endorse or promote products derived from |
| +# this software without specific prior written permission. |
| +# |
| +# 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. |
| + |
| +from webkitpy.layout_tests.port import driver |
| +import time |
| + |
| + |
| +class BrowserTestDriver(driver.Driver): |
| + """Object for running print preview test(s) using browser_tests.""" |
| + def __init__(self, port, worker_number, pixel_tests, no_timeout=False): |
| + """Invokes the constructor of driver.Driver.""" |
| + super(BrowserTestDriver, self).__init__(port, worker_number, pixel_tests, no_timeout) |
| + |
| + def start(self, pixel_tests, per_test_args, deadline): |
| + """Same as Driver.start() however, it has an extra step. It waits for |
| + a path to a file to be used for stdin to be printed by the browser test. |
| + If a path is found by the deadline test test will open the file and |
| + assign it to the stdin of the process that is owned by this driver's |
| + server process. |
| + """ |
| + # FIXME(ivandavid): Need to handle case where the layout test doesn't |
| + # get a file name. |
| + new_cmd_line = self.cmd_line(pixel_tests, per_test_args) |
| + if not self._server_process or new_cmd_line != self._current_cmd_line: |
| + self._start(pixel_tests, per_test_args) |
| + self._run_post_start_tasks() |
| + path, found = self._read_stdin_path(time.time() + int(deadline) / 1000.0) |
| + if found: |
| + self._server_process._proc.stdin = open(path, 'w', 0) |
|
Dirk Pranke
2014/07/10 23:37:13
I am worried that this approach won't actually wor
ivandavid
2014/07/16 21:29:01
I changed it to 'wb' so it can work on windows. Wh
|
| + |
| + def cmd_line(self, pixel_tests, per_test_args): |
| + """Command line arguments to run the browser test.""" |
| + cmd = self._command_wrapper(self._port.get_option('wrapper')) |
| + cmd.append(self._port._path_to_driver()) |
| + cmd.append('--gtest_filter=PrintPreviewPdfGeneratedBrowserTest.MANUAL_DummyTest') |
| + cmd.append('--run-manual') |
| + cmd.append('--single_process') |
| + cmd.extend(per_test_args) |
| + return cmd |
| + |
| + def run_test(self, driver_input, stop_when_done): |
| + """Run a single test and return the results. |
| + |
| + Note that it is okay if a test times out or crashes and leaves |
| + the driver in an indeterminate state. The upper layers of the program |
| + are responsible for cleaning up and ensuring things are okay. |
| + |
| + Returns a DriverOutput object. |
| + |
| + This function was overriden because the new start function had to be |
| + called. |
|
Dirk Pranke
2014/07/10 23:37:13
I would modify Driver.start() to take an (optional
ivandavid
2014/07/16 21:29:01
Done.
|
| + """ |
| + start_time = time.time() |
| + self.start(driver_input.should_run_pixel_test, driver_input.args, int(driver_input.timeout)) |
| + test_begin_time = time.time() |
| + self.error_from_test = str() |
| + self.err_seen_eof = False |
| + |
| + command = self._command_from_driver_input(driver_input) |
| + deadline = test_begin_time + int(driver_input.timeout) / 1000.0 |
| + |
| + self._server_process.write(command) |
| + text, audio = self._read_first_block(deadline) # First block is either text or audio |
| + image, actual_image_hash = self._read_optional_image_block(deadline) # The second (optional) block is image data. |
| + |
| + crashed = self.has_crashed() |
| + timed_out = self._server_process.timed_out |
| + pid = self._server_process.pid() |
| + leaked = self._leaked |
| + |
| + if stop_when_done or crashed or timed_out or leaked: |
| + # We call stop() even if we crashed or timed out in order to get any remaining stdout/stderr output. |
| + # In the timeout case, we kill the hung process as well. |
| + out, err = self._server_process.stop(self._port.driver_stop_timeout() if stop_when_done else 0.0) |
| + if out: |
| + text += out |
| + if err: |
| + self.error_from_test += err |
| + self._server_process = None |
| + |
| + crash_log = None |
| + if crashed: |
| + self.error_from_test, crash_log = self._get_crash_log(text, self.error_from_test, newer_than=start_time) |
| + |
| + # If we don't find a crash log use a placeholder error message instead. |
| + if not crash_log: |
| + pid_str = str(self._crashed_pid) if self._crashed_pid else "unknown pid" |
| + crash_log = 'No crash log found for %s:%s.\n' % (self._crashed_process_name, pid_str) |
| + # If we were unresponsive append a message informing there may not have been a crash. |
| + if self._subprocess_was_unresponsive: |
| + crash_log += 'Process failed to become responsive before timing out.\n' |
| + |
| + # Print stdout and stderr to the placeholder crash log; we want as much context as possible. |
| + if self.error_from_test: |
| + crash_log += '\nstdout:\n%s\nstderr:\n%s\n' % (text, self.error_from_test) |
| + return driver.DriverOutput(text, image, actual_image_hash, audio, |
| + crash=crashed, test_time=time.time() - test_begin_time, measurements=self._measurements, |
| + timeout=timed_out, error=self.error_from_test, |
| + crashed_process_name=self._crashed_process_name, |
| + crashed_pid=self._crashed_pid, crash_log=crash_log, |
| + leak=leaked, leak_log=self._leak_log, |
| + pid=pid) |
| + |
| + def _read_stdin_path(self, deadline): |
| + """Reads the file path to be used for stdin.""" |
| + block = self._read_block(deadline) |
| + if block.stdin_path: |
| + return (block.stdin_path, True) |
| + return (None, False) |
| + |
| + def _process_stdout_line(self, block, line): |
| + """Additional header read to look for the stdin path.""" |
| + if (self._read_header(block, line, 'StdinPath: ', 'stdin_path')): |
| + return |
| + super(BrowserTestDriver, self)._process_stdout_line(block, line) |
| + |
| + def _read_block(self, deadline, wait_for_stderr_eof=False): |
| + """Same as Driver._read_block() however rather than using ContentBlock, |
| + ContentBlockStdin is used, as ContentBlock does not have the member |
| + variable to store the stdin path. |
| + """ |
| + block = ContentBlockStdin() |
|
Dirk Pranke
2014/07/10 23:37:13
If this is the only difference from Driver._read_b
ivandavid
2014/07/16 21:29:01
Done.
|
| + out_seen_eof = False |
| + |
| + while not self.has_crashed(): |
| + if out_seen_eof and (self.err_seen_eof or not wait_for_stderr_eof): |
| + break |
| + |
| + if self.err_seen_eof: |
| + out_line = self._server_process.read_stdout_line(deadline) |
| + err_line = None |
| + elif out_seen_eof: |
| + out_line = None |
| + err_line = self._server_process.read_stderr_line(deadline) |
| + else: |
| + out_line, err_line = self._server_process.read_either_stdout_or_stderr_line(deadline) |
| + if self._server_process.timed_out or self.has_crashed(): |
| + break |
| + |
| + if out_line: |
| + assert not out_seen_eof |
| + out_line, out_seen_eof = self._strip_eof(out_line) |
| + if err_line: |
| + assert not self.err_seen_eof |
| + err_line, self.err_seen_eof = self._strip_eof(err_line) |
| + |
| + if out_line: |
| + if out_line[-1] != "\n": |
| + driver._log.error("Last character read from DRT stdout line was not a newline! This indicates either a NRWT or DRT bug.") |
| + content_length_before_header_check = block._content_length |
| + self._process_stdout_line(block, out_line) |
| + # FIXME: Unlike HTTP, DRT dumps the content right after printing a Content-Length header. |
| + # Don't wait until we're done with headers, just read the binary blob right now. |
| + if content_length_before_header_check != block._content_length: |
| + if block._content_length > 0: |
| + block.content = self._server_process.read_stdout(deadline, block._content_length) |
| + else: |
| + driver._log.error("Received content of type %s with Content-Length of 0! This indicates a bug in %s.", |
| + block.content_type, self._server_process.name()) |
| + |
| + if err_line: |
| + if self._check_for_driver_crash(err_line): |
| + break |
| + if self._check_for_leak(err_line): |
| + break |
| + self.error_from_test += err_line |
| + |
| + block.decode_content() |
| + return block |
| + |
| + |
| +class ContentBlockStdin(driver.ContentBlock): |
| + """Extends ContentBlock by adding another member variable that stores a path |
| + for stdin. |
| + """ |
| + def __init__(self): |
| + self.stdin_path = None |
| + super(ContentBlockStdin, self).__init__() |