2011-04-11 Dirk Pranke <dpranke@chromium.org>
authordpranke@chromium.org <dpranke@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Apr 2011 19:17:57 +0000 (19:17 +0000)
committerdpranke@chromium.org <dpranke@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Apr 2011 19:17:57 +0000 (19:17 +0000)
        Reviewed by Tony Chang.

        new-run-webkit-tests: implement support for audio, take two
        https://bugs.webkit.org/show_bug.cgi?id=58195

        Attempt to re-land the fix for bug 58101 (which was initially
        landed in r83330, but rolled out). This is the same patch but
        fixes crashes in Port.diff_image() caused by the change for
        empty image files being None instead of ''.

        * Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py:
        * Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py:
        * Scripts/webkitpy/layout_tests/layout_package/test_expectations.py:
        * Scripts/webkitpy/layout_tests/layout_package/test_failures.py:
        * Scripts/webkitpy/layout_tests/layout_package/test_result_writer.py:
        * Scripts/webkitpy/layout_tests/port/base.py:
        * Scripts/webkitpy/layout_tests/port/chromium.py:
        * Scripts/webkitpy/layout_tests/port/dryrun.py:
        * Scripts/webkitpy/layout_tests/port/mock_drt.py:
        * Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py:
        * Scripts/webkitpy/layout_tests/port/test.py:
        * Scripts/webkitpy/layout_tests/port/webkit.py:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@83475 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
Tools/ChangeLog
Tools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py
Tools/Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py
Tools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py
Tools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py
Tools/Scripts/webkitpy/layout_tests/layout_package/test_result_writer.py
Tools/Scripts/webkitpy/layout_tests/port/base.py
Tools/Scripts/webkitpy/layout_tests/port/chromium.py
Tools/Scripts/webkitpy/layout_tests/port/dryrun.py
Tools/Scripts/webkitpy/layout_tests/port/mock_drt.py
Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py
Tools/Scripts/webkitpy/layout_tests/port/test.py
Tools/Scripts/webkitpy/layout_tests/port/webkit.py

index b28adae..e7392ff 100644 (file)
@@ -1,3 +1,28 @@
+2011-04-11  Dirk Pranke  <dpranke@chromium.org>
+
+        Reviewed by Tony Chang.
+
+        new-run-webkit-tests: implement support for audio, take two
+        https://bugs.webkit.org/show_bug.cgi?id=58195
+
+        Attempt to re-land the fix for bug 58101 (which was initially
+        landed in r83330, but rolled out). This is the same patch but
+        fixes crashes in Port.diff_image() caused by the change for
+        empty image files being None instead of ''.
+
+        * Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py:
+        * Scripts/webkitpy/layout_tests/layout_package/single_test_runner.py:
+        * Scripts/webkitpy/layout_tests/layout_package/test_expectations.py:
+        * Scripts/webkitpy/layout_tests/layout_package/test_failures.py:
+        * Scripts/webkitpy/layout_tests/layout_package/test_result_writer.py:
+        * Scripts/webkitpy/layout_tests/port/base.py:
+        * Scripts/webkitpy/layout_tests/port/chromium.py:
+        * Scripts/webkitpy/layout_tests/port/dryrun.py:
+        * Scripts/webkitpy/layout_tests/port/mock_drt.py:
+        * Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py:
+        * Scripts/webkitpy/layout_tests/port/test.py:
+        * Scripts/webkitpy/layout_tests/port/webkit.py:
+
 2011-04-11  Mario Sanchez Prada  <msanchez@igalia.com>
 
         Reviewed by Xan Lopez.
index 19b02e8..dbb16c0 100644 (file)
@@ -50,6 +50,7 @@ class JSONLayoutResultsGenerator(json_results_generator.JSONResultsGeneratorBase
                        test_expectations.IMAGE: "I",
                        test_expectations.TEXT: "F",
                        test_expectations.MISSING: "O",
+                       test_expectations.AUDIO: "A",
                        test_expectations.IMAGE_PLUS_TEXT: "Z"}
 
     def __init__(self, port, builder_name, build_name, build_number,
index a8c716f..c33fd80 100644 (file)
@@ -82,7 +82,7 @@ class SingleTestRunner:
             # For example, if 'foo.html' has two expectation files, 'foo-expected.html' and
             # 'foo-expected.txt', we should warn users. One test file must be used exclusively
             # in either layout tests or reftests, but not in both.
-            for suffix in ['.txt', '.checksum', '.png']:
+            for suffix in ('.txt', '.checksum', '.png', '.wav'):
                 expected_filename = self._port.expected_filename(self._filename, suffix)
                 if fs.exists(expected_filename):
                     _log.error('The reftest (%s) can not have an expectation file (%s).'
@@ -91,7 +91,8 @@ class SingleTestRunner:
     def _expected_driver_output(self):
         return base.DriverOutput(self._port.expected_text(self._filename),
                                  self._port.expected_image(self._filename),
-                                 self._port.expected_checksum(self._filename))
+                                 self._port.expected_checksum(self._filename),
+                                 self._port.expected_audio(self._filename))
 
     def _should_fetch_expected_checksum(self):
         return (self._options.pixel_tests and
@@ -142,6 +143,9 @@ class SingleTestRunner:
         # DumpRenderTree may not output utf-8 text (e.g. webarchives).
         self._save_baseline_data(driver_output.text, ".txt",
                                  generate_new_baseline=self._options.new_baseline)
+        if driver_output.audio:
+            self._save_baseline_data(driver_output.audio, '.wav',
+                                     generate_new_baseline=self._options.new_baseline)
         if self._options.pixel_tests and driver_output.image_hash:
             self._save_baseline_data(driver_output.image, ".png",
                                      generate_new_baseline=self._options.new_baseline)
@@ -216,19 +220,28 @@ class SingleTestRunner:
             return TestResult(self._filename, failures, driver_output.test_time)
 
         failures.extend(self._compare_text(driver_output.text, expected_driver_output.text))
+        failures.extend(self._compare_audio(driver_output.audio, expected_driver_output.audio))
         if self._options.pixel_tests:
             failures.extend(self._compare_image(driver_output, expected_driver_output))
         return TestResult(self._filename, failures, driver_output.test_time)
 
     def _compare_text(self, actual_text, expected_text):
         failures = []
-        if self._port.compare_text(self._get_normalized_output_text(actual_text),
-                                   # Assuming expected_text is already normalized.
-                                   expected_text):
-            if expected_text == '':
-                failures.append(test_failures.FailureMissingResult())
-            else:
-                failures.append(test_failures.FailureTextMismatch())
+        if (expected_text and actual_text and
+            # Assuming expected_text is already normalized.
+            self._port.compare_text(self._get_normalized_output_text(actual_text), expected_text)):
+            failures.append(test_failures.FailureTextMismatch())
+        elif actual_text and not expected_text:
+            failures.append(test_failures.FailureMissingResult())
+        return failures
+
+    def _compare_audio(self, actual_audio, expected_audio):
+        failures = []
+        if (expected_audio and actual_audio and
+            self._port.compare_audio(actual_audio, expected_audio)):
+            failures.append(test_failures.FailureAudioMismatch())
+        elif actual_audio and not expected_audio:
+            failures.append(test_failures.FailureMissingAudio())
         return failures
 
     def _get_normalized_output_text(self, output):
index ad50ade..a407ecc 100644 (file)
@@ -41,8 +41,8 @@ _log = logging.getLogger("webkitpy.layout_tests.layout_package."
                          "test_expectations")
 
 # Test expectation and modifier constants.
-(PASS, FAIL, TEXT, IMAGE, IMAGE_PLUS_TEXT, TIMEOUT, CRASH, SKIP, WONTFIX,
- SLOW, REBASELINE, MISSING, FLAKY, NOW, NONE) = range(15)
+(PASS, FAIL, TEXT, IMAGE, IMAGE_PLUS_TEXT, AUDIO, TIMEOUT, CRASH, SKIP, WONTFIX,
+ SLOW, REBASELINE, MISSING, FLAKY, NOW, NONE) = range(16)
 
 # Test expectation file update action constants
 (NO_CHANGE, REMOVE_TEST, REMOVE_PLATFORM, ADD_PLATFORMS_EXCEPT_THIS) = range(4)
@@ -120,7 +120,8 @@ class TestExpectations:
                 self._expected_failures.get_test_set(REBASELINE, IMAGE) |
                 self._expected_failures.get_test_set(REBASELINE, TEXT) |
                 self._expected_failures.get_test_set(REBASELINE,
-                                                     IMAGE_PLUS_TEXT))
+                                                     IMAGE_PLUS_TEXT) |
+                self._expected_failures.get_test_set(REBASELINE, AUDIO))
 
     def get_options(self, test):
         return self._expected_failures.get_options(test)
@@ -244,11 +245,11 @@ class TestExpectationsFile:
 
     Notes:
       -A test cannot be both SLOW and TIMEOUT
-      -A test should only be one of IMAGE, TEXT, IMAGE+TEXT, or FAIL. FAIL is
-       a migratory state that currently means either IMAGE, TEXT, or
-       IMAGE+TEXT. Once we have finished migrating the expectations, we will
-       change FAIL to have the meaning of IMAGE+TEXT and remove the IMAGE+TEXT
-       identifier.
+      -A test should only be one of IMAGE, TEXT, IMAGE+TEXT, AUDIO, or FAIL.
+       FAIL is a legacy value that currently means either IMAGE,
+       TEXT, or IMAGE+TEXT. Once we have finished migrating the expectations,
+       we should change FAIL to have the meaning of IMAGE+TEXT and remove the
+       IMAGE+TEXT identifier.
       -A test can be included twice, but not via the same path.
       -If a test is included twice, then the more precise path wins.
       -CRASH tests cannot be WONTFIX
@@ -259,6 +260,7 @@ class TestExpectationsFile:
                     'text': TEXT,
                     'image': IMAGE,
                     'image+text': IMAGE_PLUS_TEXT,
+                    'audio': AUDIO,
                     'timeout': TIMEOUT,
                     'crash': CRASH,
                     'missing': MISSING}
@@ -271,6 +273,7 @@ class TestExpectationsFile:
                                 IMAGE: ('image mismatch', 'image mismatch'),
                                 IMAGE_PLUS_TEXT: ('image and text mismatch',
                                                   'image and text mismatch'),
+                                AUDIO: ('audio mismatch', 'audio mismatch'),
                                 CRASH: ('DumpRenderTree crash',
                                         'DumpRenderTree crashes'),
                                 TIMEOUT: ('test timed out', 'tests timed out'),
@@ -278,7 +281,7 @@ class TestExpectationsFile:
                                           'no expected results found')}
 
     EXPECTATION_ORDER = (PASS, CRASH, TIMEOUT, MISSING, IMAGE_PLUS_TEXT,
-       TEXT, IMAGE, FAIL, SKIP)
+       TEXT, IMAGE, AUDIO, FAIL, SKIP)
 
     BUILD_TYPES = ('debug', 'release')
 
index 1fad772..f127c2b 100644 (file)
@@ -54,7 +54,8 @@ def determine_result_type(failure_list):
         return test_expectations.TIMEOUT
     elif (FailureMissingResult in failure_types or
           FailureMissingImage in failure_types or
-          FailureMissingImageHash in failure_types):
+          FailureMissingImageHash in failure_types or
+          FailureMissingAudio in failure_types):
         return test_expectations.MISSING
     else:
         is_text_failure = FailureTextMismatch in failure_types
@@ -62,12 +63,15 @@ def determine_result_type(failure_list):
                             FailureImageHashMismatch in failure_types)
         is_reftest_failure = (FailureReftestMismatch in failure_types or
                               FailureReftestMismatchDidNotOccur in failure_types)
+        is_audio_failure = (FailureAudioMismatch in failure_types)
         if is_text_failure and is_image_failure:
             return test_expectations.IMAGE_PLUS_TEXT
         elif is_text_failure:
             return test_expectations.TEXT
         elif is_image_failure or is_reftest_failure:
             return test_expectations.IMAGE
+        elif is_audio_failure:
+            return test_expectations.AUDIO
         else:
             raise ValueError("unclassifiable set of failures: "
                              + str(failure_types))
@@ -331,6 +335,28 @@ class FailureReftestMismatchDidNotOccur(ComparisonTestFailure):
         return ' '.join(links)
 
 
+class FailureMissingAudio(ComparisonTestFailure):
+    """Actual result image was missing."""
+    OUT_FILENAMES = ("-actual.wav",)
+
+    @staticmethod
+    def message():
+        return "No expected audio found"
+
+    def result_html_output(self, filename):
+        return ("<strong>%s</strong>" % self.message() +
+                self.output_links(filename, self.OUT_FILENAMES))
+
+
+class FailureAudioMismatch(ComparisonTestFailure):
+    """Audio files didn't match."""
+    OUT_FILENAMES = ("-actual.wav", "-expected.wav")
+
+    @staticmethod
+    def message():
+        return "Audio mismatch"
+
+
 # Convenient collection of all failure classes for anything that might
 # need to enumerate over them all.
 ALL_FAILURE_CLASSES = (FailureTimeout, FailureCrash, FailureMissingResult,
index e209503..a404bc2 100644 (file)
@@ -63,6 +63,9 @@ def write_test_result(port, root_output_dir, filename, driver_output,
             if not images_are_different:
                 checksums_mismatch_but_images_are_same = True
                 imagehash_mismatch_failure = failure
+        elif isinstance(failure, (test_failures.FailureAudioMismatch,
+                                  test_failures.FailureMissingAudio)):
+            writer.write_audio_files(driver_output.audio, expected_driver_output.audio)
         elif isinstance(failure, test_failures.FailureCrash):
             if failure.reference_filename:
                 writer.write_crash_report(expected_driver_output.error)
@@ -187,6 +190,9 @@ class TestResultWriter(object):
         pretty_patch_filename = self.output_filename(self.FILENAME_SUFFIX_PRETTY_PATCH)
         fs.write_binary_file(pretty_patch_filename, pretty_patch)
 
+    def write_audio_files(self, actual_audio, expected_audio):
+        self.write_output_files('.wav', actual_audio, expected_audio)
+
     def write_image_files(self, actual_image, expected_image):
         self.write_output_files('.png', actual_image, expected_image)
 
index c20486c..7a2e24f 100644 (file)
@@ -206,6 +206,10 @@ class Port(object):
         interface so that it can be overriden for testing purposes."""
         return expected_text != actual_text
 
+    def compare_audio(self, expected_audio, actual_audio):
+        """Return whether the two audio files are *not* equal."""
+        return expected_audio != actual_audio
+
     def diff_image(self, expected_contents, actual_contents,
                    diff_filename=None, tolerance=0):
         """Compare two images and produce a delta image file.
@@ -351,15 +355,22 @@ class Port(object):
             return None
         return self._filesystem.read_binary_file(path)
 
+    def expected_audio(self, test):
+        path = self.expected_filename(test, '.wav')
+        if not self.path_exists(path):
+            return None
+        return self._filesystem.read_binary_file(path)
+
     def expected_text(self, test):
-        """Returns the text output we expect the test to produce.
+        """Returns the text output we expect the test to produce, or None
+        if we don't expect there to be any text output.
         End-of-line characters are normalized to '\n'."""
         # FIXME: DRT output is actually utf-8, but since we don't decode the
         # output from DRT (instead treating it as a binary string), we read the
         # baselines as a binary string, too.
         path = self.expected_filename(test, '.txt')
         if not self.path_exists(path):
-            return ''
+            return None
         text = self._filesystem.read_binary_file(path)
         return text.replace("\r\n", "\n")
 
@@ -867,22 +878,24 @@ class DriverInput(object):
 class DriverOutput(object):
     """Groups information about a output from driver for easy passing of data."""
 
-    def __init__(self, text, image, image_hash,
-                 crash=False, test_time=None, timeout=False, error=''):
+    def __init__(self, text, image, image_hash, audio,
+                 crash=False, test_time=0, timeout=False, error=''):
         """Initializes a TestOutput object.
 
         Args:
           text: a text output
           image: an image output
           image_hash: a string containing the checksum of the image
+          audio: contents of an audio stream, if any (in WAV format)
           crash: a boolean indicating whether the driver crashed on the test
-          test_time: a time which the test has taken
+          test_time: the time the test took to execute
           timeout: a boolean indicating whehter the test timed out
           error: any unexpected or additional (or error) text output
         """
         self.text = text
         self.image = image
         self.image_hash = image_hash
+        self.audio = audio
         self.crash = crash
         self.test_time = test_time
         self.timeout = timeout
index 32da16f..5825013 100644 (file)
@@ -136,7 +136,11 @@ class ChromiumPort(base.Port):
 
     def diff_image(self, expected_contents, actual_contents,
                    diff_filename=None):
-        executable = self._path_to_image_diff()
+        # FIXME: need unit tests for this.
+        if not actual_contents and not expected_contents:
+            return False
+        if not actual_contents or not expected_contents:
+            return True
 
         tempdir = self._filesystem.mkdtemp()
         expected_filename = self._filesystem.join(str(tempdir), "expected.png")
@@ -144,6 +148,7 @@ class ChromiumPort(base.Port):
         actual_filename = self._filesystem.join(str(tempdir), "actual.png")
         self._filesystem.write_binary_file(actual_filename, actual_contents)
 
+        executable = self._path_to_image_diff()
         if diff_filename:
             cmd = [executable, '--diff', expected_filename,
                    actual_filename, diff_filename]
@@ -426,7 +431,7 @@ class ChromiumDriver(base.Driver):
         if png_path and self._port._filesystem.exists(png_path):
             return self._port._filesystem.read_binary_file(png_path)
         else:
-            return ''
+            return None
 
     def _output_image_with_retry(self):
         # Retry a few more times because open() sometimes fails on Windows,
@@ -501,11 +506,16 @@ class ChromiumDriver(base.Driver):
 
             (line, crash) = self._write_command_and_read_line(input=None)
 
+        # FIXME: Add support for audio when we're ready.
+
         run_time = time.time() - start_time
         output_image = self._output_image_with_retry()
-        assert output_image is not None
-        return base.DriverOutput(''.join(output), output_image, actual_checksum,
-                                 crash, run_time, timeout, ''.join(error))
+        text = ''.join(output)
+        if not text:
+            text = None
+
+        return base.DriverOutput(text, output_image, actual_checksum, audio=None,
+            crash=crash, test_time=run_time, timeout=timeout, error=''.join(error))
 
     def stop(self):
         if self._proc:
index 20aa776..ba99636 100644 (file)
@@ -119,29 +119,25 @@ class DryrunDriver(base.Driver):
     def run_test(self, driver_input):
         start_time = time.time()
         fs = self._port._filesystem
-        if fs.exists(self._port.reftest_expected_filename(driver_input.filename)) or \
-            fs.exists(self._port.reftest_expected_mismatch_filename(driver_input.filename)):
-            text_output = 'test-text'
+        if (fs.exists(self._port.reftest_expected_filename(driver_input.filename)) or
+            fs.exists(self._port.reftest_expected_mismatch_filename(driver_input.filename)) or
+            driver_input.filename.endswith('-expected.html')):
+            text = 'test-text'
             image = 'test-image'
-            hash = 'test-checksum'
-        elif driver_input.filename.endswith('-expected.html'):
-            text_output = 'test-text'
-            image = 'test-image'
-            hash = 'test-checksum'
+            checksum = 'test-checksum'
+            audio = None
         elif driver_input.filename.endswith('-expected-mismatch.html'):
-            text_output = 'test-text-mismatch'
+            text = 'test-text-mismatch'
             image = 'test-image-mismatch'
-            hash = 'test-checksum-mismatch'
-        elif driver_input.image_hash is not None:
-            text_output = self._port.expected_text(driver_input.filename)
-            image = self._port.expected_image(driver_input.filename)
-            hash = self._port.expected_checksum(driver_input.filename)
+            checksum = 'test-checksum-mismatch'
+            audio = None
         else:
-            text_output = self._port.expected_text(driver_input.filename)
-            image = None
-            hash = None
-        return base.DriverOutput(text_output, image, hash, False,
-                                 time.time() - start_time, False, '')
+            text = self._port.expected_text(driver_input.filename)
+            image = self._port.expected_image(driver_input.filename)
+            checksum = self._port.expected_checksum(driver_input.filename)
+            audio = self._port.expected_audio(driver_input.filename)
+        return base.DriverOutput(text, image, checksum, audio, crash=False,
+            test_time=time.time() - start_time, timeout=False, error='')
 
     def start(self):
         pass
index 1147846..73967cf 100644 (file)
@@ -32,6 +32,7 @@ This is an implementation of the Port interface that overrides other
 ports and changes the Driver binary to "MockDRT".
 """
 
+import base64
 import logging
 import optparse
 import os
@@ -206,15 +207,23 @@ class MockDRT(object):
             test_path = test_input.uri
 
         actual_text = port.expected_text(test_path)
+        actual_audio = port.expected_audio(test_path)
         if self._options.pixel_tests and test_input.checksum:
             actual_checksum = port.expected_checksum(test_path)
             actual_image = port.expected_image(test_path)
 
-        self._stdout.write('Content-Type: text/plain\n')
+        if actual_audio:
+            self._stdout.write('Content-Type: audio/wav\n')
+            self._stdout.write('Content-Transfer-Encoding: base64\n')
+            output = base64.b64encode(actual_audio)
+            self._stdout.write('Content-Length: %s\n' % len(output))
+            self._stdout.write(output)
+        else:
+            self._stdout.write('Content-Type: text/plain\n')
+            # FIXME: Note that we don't ensure there is a trailing newline!
+            # This mirrors actual (Mac) DRT behavior but is a bug.
+            self._stdout.write(actual_text)
 
-        # FIXME: Note that we don't ensure there is a trailing newline!
-        # This mirrors actual (Mac) DRT behavior but is a bug.
-        self._stdout.write(actual_text)
         self._stdout.write('#EOF\n')
 
         if self._options.pixel_tests and test_input.checksum:
@@ -223,7 +232,7 @@ class MockDRT(object):
             self._stdout.write('ExpectedHash: %s\n' % test_input.checksum)
             if actual_checksum != test_input.checksum:
                 self._stdout.write('Content-Type: image/png\n')
-                self._stdout.write('Content-Length: %s\n\n' % len(actual_image))
+                self._stdout.write('Content-Length: %s\n' % len(actual_image))
                 self._stdout.write(actual_image)
         self._stdout.write('#EOF\n')
         self._stdout.flush()
index b6f6e8a..37608f8 100644 (file)
@@ -200,7 +200,7 @@ class MockDRTTest(unittest.TestCase):
                         'ActualHash: checksum-checksum\n',
                         'ExpectedHash: wrong-checksum\n',
                         'Content-Type: image/png\n',
-                        'Content-Length: 13\n\n',
+                        'Content-Length: 13\n',
                         'checksum\x8a-png',
                         '#EOF\n'])
 
index 30b433e..b1a323f 100644 (file)
@@ -30,6 +30,7 @@
 """Dummy Port implementation used for testing."""
 from __future__ import with_statement
 
+import base64
 import time
 
 from webkitpy.common.system import filesystem_mock
@@ -66,6 +67,8 @@ class TestInstance:
         self.expected_checksum = self.actual_checksum
         self.expected_image = self.actual_image
 
+        self.actual_audio = None
+        self.expected_audio = None
 
 # This is an in-memory list of tests, what we want them to produce, and
 # what we want to claim are the expected results.
@@ -111,11 +114,20 @@ def unit_test_list():
     tests.add('failures/expected/image_checksum.html',
               actual_checksum='image_checksum_fail-checksum',
               actual_image='image_checksum_fail-png')
+    tests.add('failures/expected/audio.html',
+              actual_audio=base64.b64encode('audio_fail-wav'), expected_audio='audio-wav',
+              actual_text=None, expected_text=None,
+              actual_image=None, expected_image=None,
+              actual_checksum=None, expected_checksum=None)
     tests.add('failures/expected/keyboard.html', keyboard=True)
     tests.add('failures/expected/missing_check.html',
               expected_checksum=None,
               expected_image=None)
     tests.add('failures/expected/missing_image.html', expected_image=None)
+    tests.add('failures/expected/missing_audio.html', expected_audio=None,
+              actual_text=None, expected_text=None,
+              actual_image=None, expected_image=None,
+              actual_checksum=None, expected_checksum=None)
     tests.add('failures/expected/missing_text.html', expected_text=None)
     tests.add('failures/expected/newlines_leading.html',
               expected_text="\nfoo\n", actual_text="foo\n")
@@ -134,6 +146,11 @@ def unit_test_list():
     tests.add('http/tests/ssl/text.html')
     tests.add('passes/error.html', error='stuff going to stderr')
     tests.add('passes/image.html')
+    tests.add('passes/audio.html',
+              actual_audio=base64.b64encode('audio-wav'), expected_audio='audio-wav',
+              actual_text=None, expected_text=None,
+              actual_image=None, expected_image=None,
+              actual_checksum=None, expected_checksum=None)
     tests.add('passes/platform_image.html')
     tests.add('passes/checksum_in_image.html',
               expected_checksum=None,
@@ -184,20 +201,27 @@ def unit_test_filesystem(files=None):
         add_file(files, test, '.html', '')
         if test.is_reftest:
             continue
+        if test.actual_audio:
+            add_file(files, test, '-expected.wav', test.expected_audio)
+            continue
+
         add_file(files, test, '-expected.txt', test.expected_text)
         add_file(files, test, '-expected.checksum', test.expected_checksum)
         add_file(files, test, '-expected.png', test.expected_image)
 
+
     # Add the test_expectations file.
     files[LAYOUT_TEST_DIR + '/platform/test/test_expectations.txt'] = """
 WONTFIX : failures/expected/checksum.html = IMAGE
 WONTFIX : failures/expected/crash.html = CRASH
 // This one actually passes because the checksums will match.
 WONTFIX : failures/expected/image.html = PASS
+WONTFIX : failures/expected/audio.html = AUDIO
 WONTFIX : failures/expected/image_checksum.html = IMAGE
 WONTFIX : failures/expected/mismatch.html = IMAGE
 WONTFIX : failures/expected/missing_check.html = MISSING PASS
 WONTFIX : failures/expected/missing_image.html = MISSING PASS
+WONTFIX : failures/expected/missing_audio.html = MISSING PASS
 WONTFIX : failures/expected/missing_text.html = MISSING PASS
 WONTFIX : failures/expected/newlines_leading.html = TEXT
 WONTFIX : failures/expected/newlines_trailing.html = TEXT
@@ -412,10 +436,13 @@ class TestDriver(base.Driver):
             raise ValueError('exception from ' + test_name)
         if test.hang:
             time.sleep((float(test_input.timeout) * 4) / 1000.0)
+
+        audio = None
+        if test.actual_audio:
+            audio = base64.b64decode(test.actual_audio)
         return base.DriverOutput(test.actual_text, test.actual_image,
-                                 test.actual_checksum, test.crash,
-                                 time.time() - start_time, test.timeout,
-                                 test.error)
+            test.actual_checksum, audio, crash=test.crash,
+            test_time=time.time() - start_time, timeout=test.timeout, error=test.error)
 
     def start(self):
         pass
index 0522631..cfd8a34 100644 (file)
@@ -30,7 +30,7 @@
 
 """WebKit implementations of the Port interface."""
 
-
+import base64
 import logging
 import operator
 import os
@@ -109,7 +109,10 @@ class WebKitPort(base.Port):
         image of the two images into |diff_filename| if it is not None."""
 
         # Handle the case where the test didn't actually generate an image.
-        if not actual_contents:
+        # FIXME: need unit tests for this.
+        if not actual_contents and not expected_contents:
+            return False
+        if not actual_contents or not expected_contents:
             return True
 
         sp = self._diff_image_request(expected_contents, actual_contents)
@@ -406,47 +409,24 @@ class WebKitDriver(base.Driver):
         start_time = time.time()
         self._server_process.write(command)
 
-        have_seen_content_type = False
+        text = None
+        image = None
         actual_image_hash = None
-        output = str()  # Use a byte array for output, even though it should be UTF-8.
-        image = str()
+        audio = None
+        deadline = time.time() + int(driver_input.timeout) / 1000.0
 
-        timeout = int(driver_input.timeout) / 1000.0
-        deadline = time.time() + timeout
-        line = self._server_process.read_line(timeout)
-        while (not self._server_process.timed_out
-               and not self._server_process.crashed
-               and line.rstrip() != "#EOF"):
-            if (line.startswith('Content-Type:') and not
-                have_seen_content_type):
-                have_seen_content_type = True
-            else:
-                # Note: Text output from DumpRenderTree is always UTF-8.
-                # However, some tests (e.g. webarchives) spit out binary
-                # data instead of text.  So to make things simple, we
-                # always treat the output as binary.
-                output += line
-            line = self._server_process.read_line(timeout)
-            timeout = deadline - time.time()
+        # First block is either text or audio
+        block = self._read_block(deadline)
+        if block.content_type == 'audio/wav':
+            audio = block.decoded_content
+        else:
+            text = block.decoded_content
 
-        # Now read a second block of text for the optional image data
-        remaining_length = -1
-        HASH_HEADER = 'ActualHash: '
-        LENGTH_HEADER = 'Content-Length: '
-        line = self._server_process.read_line(timeout)
-        while (not self._server_process.timed_out
-               and not self._server_process.crashed
-               and line.rstrip() != "#EOF"):
-            if line.startswith(HASH_HEADER):
-                actual_image_hash = line[len(HASH_HEADER):].strip()
-            elif line.startswith('Content-Type:'):
-                pass
-            elif line.startswith(LENGTH_HEADER):
-                timeout = deadline - time.time()
-                content_length = int(line[len(LENGTH_HEADER):])
-                image = self._server_process.read(timeout, content_length)
-            timeout = deadline - time.time()
-            line = self._server_process.read_line(timeout)
+        # Now read an optional second block of image data
+        block = self._read_block(deadline)
+        if block.content and block.content_type == 'image/png':
+            image = block.decoded_content
+            actual_image_hash = block.content_hash
 
         error_lines = self._server_process.error.splitlines()
         # FIXME: This is a hack.  It is unclear why sometimes
@@ -458,13 +438,59 @@ class WebKitDriver(base.Driver):
         # FIXME: This seems like the wrong section of code to be doing
         # this reset in.
         self._server_process.error = ""
-        return base.DriverOutput(output, image, actual_image_hash,
-                                 self._server_process.crashed,
-                                 time.time() - start_time,
-                                 self._server_process.timed_out,
-                                 error)
+        return base.DriverOutput(text, image, actual_image_hash, audio,
+            crash=self._server_process.crashed, test_time=time.time() - start_time,
+            timeout=self._server_process.timed_out, error=error)
+
+    def _read_block(self, deadline):
+        LENGTH_HEADER = 'Content-Length: '
+        HASH_HEADER = 'ActualHash: '
+        TYPE_HEADER = 'Content-Type: '
+        ENCODING_HEADER = 'Content-Transfer-Encoding: '
+        content_type = None
+        encoding = None
+        content_hash = None
+        content_length = None
+
+        # Content is treated as binary data even though the text output
+        # is usually UTF-8.
+        content = ''
+        timeout = deadline - time.time()
+        line = self._server_process.read_line(timeout)
+        while (not self._server_process.timed_out
+               and not self._server_process.crashed
+               and line.rstrip() != "#EOF"):
+            if line.startswith(TYPE_HEADER) and content_type is None:
+                content_type = line.split()[1]
+            elif line.startswith(ENCODING_HEADER) and encoding is None:
+                encoding = line.split()[1]
+            elif line.startswith(LENGTH_HEADER) and content_length is None:
+                timeout = deadline - time.time()
+                content_length = int(line[len(LENGTH_HEADER):])
+                # FIXME: Technically there should probably be another blank
+                # line here, but DRT doesn't write one.
+                content = self._server_process.read(timeout, content_length)
+            elif line.startswith(HASH_HEADER):
+                content_hash = line.split()[1]
+            else:
+                content += line
+            line = self._server_process.read_line(timeout)
+            timeout = deadline - time.time()
+        return ContentBlock(content_type, encoding, content_hash, content)
 
     def stop(self):
         if self._server_process:
             self._server_process.stop()
             self._server_process = None
+
+
+class ContentBlock(object):
+    def __init__(self, content_type, encoding, content_hash, content):
+        self.content_type = content_type
+        self.encoding = encoding
+        self.content_hash = content_hash
+        self.content = content
+        if self.encoding == 'base64':
+            self.decoded_content = base64.b64decode(content)
+        else:
+            self.decoded_content = content