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__() |