Source/WebCore: IndexedDB: Introduce putWithIndexKeys and calculate them in the renderer
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Jul 2012 00:03:48 +0000 (00:03 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Jul 2012 00:03:48 +0000 (00:03 +0000)
https://bugs.webkit.org/show_bug.cgi?id=90923

Patch by Alec Flett <alecflett@chromium.org> on 2012-07-16
Reviewed by Darin Fisher.

Refactor IndexWriter to depend only on IDBIndexMetadata and on
(databaseId, objectStoreId, indexId) so that it can talk directly
to the backing store, and also eventually be moved into the renderer.

This also introduces IDBObjectStoreBackendInterface::putWithIndexKeys
as a replacement for IDBObjectStoreBackendInterface::put, already
stubbed out in the chromium port. It will fully replace put()
after both chromium and webkit sides have reached alignment.

No new tests as this is just a refactor and existing tests cover
correctness.

* Modules/indexeddb/IDBCursor.cpp:
(WebCore::IDBCursor::setValueReady):
* Modules/indexeddb/IDBIndexBackendImpl.cpp:
* Modules/indexeddb/IDBIndexBackendImpl.h:
* Modules/indexeddb/IDBObjectStore.h:
(IDBObjectStore):
* Modules/indexeddb/IDBObjectStoreBackendImpl.cpp:
(WebCore::IDBObjectStoreBackendImpl::put):
(WebCore):
(WebCore::IDBObjectStoreBackendImpl::putWithIndexKeys):
(WebCore::IDBObjectStoreBackendImpl::putInternal):
(WebCore::IDBObjectStoreBackendImpl::populateIndex):
* Modules/indexeddb/IDBObjectStoreBackendImpl.h:
(IDBObjectStoreBackendImpl):
* Modules/indexeddb/IDBObjectStoreBackendInterface.h:
* Modules/indexeddb/IDBRequest.cpp:
(WebCore::IDBRequest::onSuccess):

Source/WebKit/chromium: IndexedDB: Introduce putWithIndexKeys and calculate them in the renderer
https://bugs.webkit.org/show_bug.cgi?id=90923

Patch by Alec Flett <alecflett@chromium.org> on 2012-07-16
Reviewed by Darin Fisher.

Stub out implementations of putWithIndexKeys(), already implemented
on the chromium side.

* public/WebIDBObjectStore.h:
(WebKit::WebIDBObjectStore::putWithIndexKeys):
* src/IDBObjectStoreBackendProxy.cpp:
(WebKit::IDBObjectStoreBackendProxy::putWithIndexKeys):
(WebKit):
* src/IDBObjectStoreBackendProxy.h:
(IDBObjectStoreBackendProxy):

Tools: [Chromium-android] Don't use test_shell mode of DRT
https://bugs.webkit.org/show_bug.cgi?id=88542

Patch by Xianzhu Wang <wangxianzhu@chromium.org> on 2012-07-16
Reviewed by Dirk Pranke.

Test shell mode is about to be removed.
Switch to use DRT mode for chromium-android.

Summary of changes:
1. ChromiumAndroidDriver now inherits from WebKitDriver instead of ChromiumDriver (to be deprecated).
2. Conforms to the DRT mode protocol for input/output of DumpRenderTree.
3. Added support for Android 'adb shell' input/output (base64, newline mode, etc.)

* DumpRenderTree/chromium/TestEventPrinter.cpp:
(DRTPrinter::handleImage): Outputs base64 on Android.
* DumpRenderTree/chromium/TestShellAndroid.cpp:
(platformInit): Changed err_file to err_fifo, required by python ServerProcess.
* Scripts/webkitpy/layout_tests/port/chromium_android.py:
(ChromiumAndroidPort.__init__):
(ChromiumAndroidPort.create_driver): Override to create driver without DriverProxy to ensure 1 Driver per run.
(ChromiumAndroidDriver):
(ChromiumAndroidDriver.__init__):
(ChromiumAndroidDriver.cmd_line):
(ChromiumAndroidDriver._deadlock_detector):
(ChromiumAndroidDriver._drt_cmd_line):
(ChromiumAndroidDriver.start):
(ChromiumAndroidDriver._start):
(ChromiumAndroidDriver._start_once):
(ChromiumAndroidDriver.run_test):
(ChromiumAndroidDriver.stop):
(ChromiumAndroidDriver._command_from_driver_input):
(ChromiumAndroidDriver._read_prompt):
* Scripts/webkitpy/layout_tests/port/chromium_android_unittest.py:
(ChromiumAndroidPortTest.test_driver_cmd_line):
(ChromiumAndroidDriverTest.test_cmd_line):
(ChromiumAndroidDriverTest):
(ChromiumAndroidDriverTest.test_drt_cmd_line):
(ChromiumAndroidDriverTest.test_read_prompt):
(ChromiumAndroidDriverTest.test_command_from_driver_input):
(ChromiumAndroidDriverTest.test_write_command_and_read_line):
* Scripts/webkitpy/layout_tests/port/server_process.py:
(ServerProcess.__init__): Added universal_newlines to handle Android 'adb shell' line ends.
(ServerProcess._start):
(ServerProcess._wait_for_data_and_update_buffers_using_select): Handles unexpected EOF which indicates crash on Android.
(ServerProcess.stop): Added kill_directly parameter to kill the process without waiting it (which always timeouts for Android).
(ServerProcess.replace_outputs): Added to combine different input/output pipes into one ServerProcess.
* Scripts/webkitpy/layout_tests/port/webkit.py:
(WebKitDriver._command_from_driver_input):
(WebKitDriver.run_test): Changed timeout origin so that slow start() on Android won't cause timeout of layout test case.
* Scripts/webkitpy/layout_tests/port/webkit_unittest.py:
(MockServerProcess.read_stdout):
(MockServerProcess.start):
(MockServerProcess):
(MockServerProcess.stop):
(MockServerProcess.kill):
(WebKitDriverTest.test_read_block):
(WebKitDriverTest.test_read_binary_block):
(WebKitDriverTest.test_read_base64_block):

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

Source/WebCore/ChangeLog
Source/WebKit/chromium/ChangeLog
Tools/ChangeLog
Tools/DumpRenderTree/chromium/TestEventPrinter.cpp
Tools/DumpRenderTree/chromium/TestShellAndroid.cpp
Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py
Tools/Scripts/webkitpy/layout_tests/port/chromium_android_unittest.py
Tools/Scripts/webkitpy/layout_tests/port/server_process.py
Tools/Scripts/webkitpy/layout_tests/port/webkit.py
Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py

index f8466ea..3082005 100644 (file)
         * platform/graphics/chromium/cc/CCVideoLayerImpl.cpp:
         (WebCore::CCVideoLayerImpl::appendQuads):
 
+2012-07-16  Adrienne Walker  <enne@google.com>
+
+        [chromium] Unify compositor quad transforms into content space
+        https://bugs.webkit.org/show_bug.cgi?id=91350
+
+        Reviewed by Kenneth Russell.
+
+        For the purpose of simplification and as a first step towards removing
+        any transform that takes a centered rect, remove the ability of layers
+        to override the quad transform. All quads and quad transforms operate
+        on content space with the origin in the top left.
+
+        The gutter quads used to use the root layer (that doesn't draw
+        content) as the layer to create the shared quad state from. This is
+        now created manually as a layer without bounds should never in general
+        need a shared quad state created for it.
+
+        No change in functionality; tested by existing layout and unit tests.
+
+        * platform/graphics/chromium/cc/CCIOSurfaceLayerImpl.cpp:
+        (WebCore::CCIOSurfaceLayerImpl::appendQuads):
+        * platform/graphics/chromium/cc/CCLayerImpl.cpp:
+        (WebCore::CCLayerImpl::createSharedQuadState):
+        * platform/graphics/chromium/cc/CCLayerImpl.h:
+        (CCLayerImpl):
+        * platform/graphics/chromium/cc/CCRenderPass.cpp:
+        (WebCore::CCRenderPass::appendQuadsToFillScreen):
+        * platform/graphics/chromium/cc/CCSolidColorLayerImpl.cpp:
+        (WebCore::CCSolidColorLayerImpl::appendQuads):
+        * platform/graphics/chromium/cc/CCSolidColorLayerImpl.h:
+        (CCSolidColorLayerImpl):
+        * platform/graphics/chromium/cc/CCTextureLayerImpl.cpp:
+        (WebCore::CCTextureLayerImpl::appendQuads):
+        * platform/graphics/chromium/cc/CCTiledLayerImpl.cpp:
+        * platform/graphics/chromium/cc/CCTiledLayerImpl.h:
+        (CCTiledLayerImpl):
+        * platform/graphics/chromium/cc/CCVideoLayerImpl.cpp:
+        (WebCore::CCVideoLayerImpl::appendQuads):
+
 2012-07-16  Joshua Bell  <jsbell@chromium.org>
 
         IndexedDB: Resolve test and IDL FIXMEs for a handful of landed patches
index d067300..d5a0857 100644 (file)
         * tests/CCTiledLayerImplTest.cpp:
         (CCLayerTestCommon::createLayer):
 
+2012-07-16  Adrienne Walker  <enne@google.com>
+
+        [chromium] Unify compositor quad transforms into content space
+        https://bugs.webkit.org/show_bug.cgi?id=91350
+
+        Reviewed by Kenneth Russell.
+
+        Update tests to add bounds/contentBounds properties to layers. This
+        exposed a bug in the quad culler tests where the draw transform was
+        incorrectly being set to the origin transform rather than being a
+        transform that operates on centered layer rects. Fixed this bug.
+
+        * tests/CCQuadCullerTest.cpp:
+        * tests/CCSolidColorLayerImplTest.cpp:
+        (CCLayerTestCommon::TEST):
+        * tests/CCTiledLayerImplTest.cpp:
+        (CCLayerTestCommon::createLayer):
+
 2012-07-16  Dana Jansens  <danakj@chromium.org>
 
         [chromium] Remove targetRenderSurface concept, give layers a renderTarget which is the layer whose coordinate space they draw into
index 140544f..55f8958 100644 (file)
@@ -1,3 +1,64 @@
+2012-07-16  Xianzhu Wang  <wangxianzhu@chromium.org>
+
+        [Chromium-android] Don't use test_shell mode of DRT
+        https://bugs.webkit.org/show_bug.cgi?id=88542
+
+        Reviewed by Dirk Pranke.
+
+        Test shell mode is about to be removed.
+        Switch to use DRT mode for chromium-android.
+
+        Summary of changes:
+        1. ChromiumAndroidDriver now inherits from WebKitDriver instead of ChromiumDriver (to be deprecated).
+        2. Conforms to the DRT mode protocol for input/output of DumpRenderTree.
+        3. Added support for Android 'adb shell' input/output (base64, newline mode, etc.)
+
+        * DumpRenderTree/chromium/TestEventPrinter.cpp:
+        (DRTPrinter::handleImage): Outputs base64 on Android.
+        * DumpRenderTree/chromium/TestShellAndroid.cpp:
+        (platformInit): Changed err_file to err_fifo, required by python ServerProcess.
+        * Scripts/webkitpy/layout_tests/port/chromium_android.py:
+        (ChromiumAndroidPort.__init__):
+        (ChromiumAndroidPort.create_driver): Override to create driver without DriverProxy to ensure 1 Driver per run.
+        (ChromiumAndroidDriver):
+        (ChromiumAndroidDriver.__init__):
+        (ChromiumAndroidDriver.cmd_line):
+        (ChromiumAndroidDriver._deadlock_detector):
+        (ChromiumAndroidDriver._drt_cmd_line):
+        (ChromiumAndroidDriver.start):
+        (ChromiumAndroidDriver._start):
+        (ChromiumAndroidDriver._start_once):
+        (ChromiumAndroidDriver.run_test):
+        (ChromiumAndroidDriver.stop):
+        (ChromiumAndroidDriver._command_from_driver_input):
+        (ChromiumAndroidDriver._read_prompt):
+        * Scripts/webkitpy/layout_tests/port/chromium_android_unittest.py:
+        (ChromiumAndroidPortTest.test_driver_cmd_line):
+        (ChromiumAndroidDriverTest.test_cmd_line):
+        (ChromiumAndroidDriverTest):
+        (ChromiumAndroidDriverTest.test_drt_cmd_line):
+        (ChromiumAndroidDriverTest.test_read_prompt):
+        (ChromiumAndroidDriverTest.test_command_from_driver_input):
+        (ChromiumAndroidDriverTest.test_write_command_and_read_line):
+        * Scripts/webkitpy/layout_tests/port/server_process.py:
+        (ServerProcess.__init__): Added universal_newlines to handle Android 'adb shell' line ends.
+        (ServerProcess._start):
+        (ServerProcess._wait_for_data_and_update_buffers_using_select): Handles unexpected EOF which indicates crash on Android.
+        (ServerProcess.stop): Added kill_directly parameter to kill the process without waiting it (which always timeouts for Android).
+        (ServerProcess.replace_outputs): Added to combine different input/output pipes into one ServerProcess.
+        * Scripts/webkitpy/layout_tests/port/webkit.py:
+        (WebKitDriver._command_from_driver_input):
+        (WebKitDriver.run_test): Changed timeout origin so that slow start() on Android won't cause timeout of layout test case.
+        * Scripts/webkitpy/layout_tests/port/webkit_unittest.py:
+        (MockServerProcess.read_stdout):
+        (MockServerProcess.start):
+        (MockServerProcess):
+        (MockServerProcess.stop):
+        (MockServerProcess.kill):
+        (WebKitDriverTest.test_read_block):
+        (WebKitDriverTest.test_read_binary_block):
+        (WebKitDriverTest.test_read_base64_block):
+
 2012-07-16  Dirk Pranke  <dpranke@chromium.org>
 
         nrwt: move a bunch of printing code from manager.py to printing.py
index 730d76b..bb46506 100644 (file)
@@ -34,6 +34,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <wtf/Assertions.h>
+#include <wtf/text/Base64.h>
 
 class DRTPrinter : public TestEventPrinter {
 public:
@@ -119,6 +120,16 @@ void DRTPrinter::handleImage(const char* actualHash, const char* expectedHash, c
         printf("\nExpectedHash: %s\n", expectedHash);
     if (imageData && imageSize) {
         printf("Content-Type: image/png\n");
+#if OS(ANDROID)
+        // On Android, the layout test driver needs to read the image data through 'adb shell' which can't
+        // handle binary data properly. Need to encode the binary data into base64.
+        // FIXME: extract this into a function so that we can also use it to output audio data. Will do when removing test_shell mode.
+        Vector<char> base64;
+        base64Encode(reinterpret_cast<const char*>(imageData), imageSize, base64, Base64InsertLFs);
+        imageData = reinterpret_cast<const unsigned char*>(base64.data());
+        imageSize = base64.size();
+        printf("Content-Transfer-Encoding: base64\n");
+#endif
         // Printf formatting for size_t on 32-bit, 64-bit, and on Windows is hard so just cast to an int.
         printf("Content-Length: %d\n", static_cast<int>(imageSize));
         if (fwrite(imageData, 1, imageSize, stdout) != imageSize) {
index bc0acaa..5f04fff 100644 (file)
@@ -50,7 +50,7 @@ const char fontsDir[] = "/data/drt/fonts/";
 
 const char optionInFIFO[] = "--in-fifo=";
 const char optionOutFIFO[] = "--out-fifo=";
-const char optionErrFile[] = "--err-file=";
+const char optionErrFIFO[] = "--err-fifo=";
 
 void androidLogError(const char* format, ...) WTF_ATTRIBUTE_PRINTF(1, 2);
 
@@ -78,18 +78,7 @@ void createFIFO(const char* fifoPath)
     }
 }
 
-void createFile(const char* filePath)
-{
-    unlink(filePath);
-    int fd = creat(filePath, 0600);
-    if (fd < 0) {
-        androidLogError("Failed to create file %s: %s\n", filePath, strerror(errno));
-        exit(EXIT_FAILURE);
-    }
-    close(fd);
-}
-
-void redirectToFile(FILE* stream, const char* path, const char* mode)
+void redirect(FILE* stream, const char* path, const char* mode)
 {
     if (!freopen(path, mode, stream)) {
         androidLogError("Failed to redirect stream to file: %s: %s\n", path, strerror(errno));
@@ -106,7 +95,7 @@ void platformInit(int* argc, char*** argv)
 
     const char* inFIFO = 0;
     const char* outFIFO = 0;
-    const char* errFile = 0;
+    const char* errFIFO = 0;
     for (int i = 1; i < *argc; ) {
         const char* argument = (*argv)[i];
         if (strstr(argument, optionInFIFO) == argument) {
@@ -117,9 +106,9 @@ void platformInit(int* argc, char*** argv)
             outFIFO = argument + WTF_ARRAY_LENGTH(optionOutFIFO) - 1;
             createFIFO(outFIFO);
             removeArg(i, argc, argv);
-        } else if (strstr(argument, optionErrFile) == argument) {
-            errFile = argument + WTF_ARRAY_LENGTH(optionErrFile) - 1;
-            createFile(errFile);
+        } else if (strstr(argument, optionErrFIFO) == argument) {
+            errFIFO = argument + WTF_ARRAY_LENGTH(optionErrFIFO) - 1;
+            createFIFO(errFIFO);
             removeArg(i, argc, argv);
         } else
             ++i;
@@ -127,11 +116,11 @@ void platformInit(int* argc, char*** argv)
 
     // The order of createFIFO() and redirectToFIFO() is important to avoid deadlock.
     if (outFIFO)
-        redirectToFile(stdout, outFIFO, "w");
+        redirect(stdout, outFIFO, "w");
     if (inFIFO)
-        redirectToFile(stdin, inFIFO, "r");
-    if (errFile)
-        redirectToFile(stderr, errFile, "w");
+        redirect(stdin, inFIFO, "r");
+    if (errFIFO)
+        redirect(stderr, errFIFO, "w");
     else {
         // Redirect stderr to stdout.
         dup2(1, 2);
index 2240657..567f3ae 100644 (file)
 
 import logging
 import os
-import re
-import signal
 import shlex
-import shutil
 import threading
 import time
 
 from webkitpy.layout_tests.port import chromium
 from webkitpy.layout_tests.port import factory
+from webkitpy.layout_tests.port import server_process
+from webkitpy.layout_tests.port import webkit
 
 
 _log = logging.getLogger(__name__)
@@ -123,8 +122,7 @@ DEVICE_FONTS_DIR = DEVICE_DRT_DIR + 'fonts/'
 # 1. as a virtual path in file urls that will be bridged to HTTP.
 # 2. pointing to some files that are pushed to the device for tests that
 # don't work on file-over-http (e.g. blob protocol tests).
-DEVICE_LAYOUT_TESTS_DIR = (DEVICE_SOURCE_ROOT_DIR + 'third_party/WebKit/LayoutTests/')
-FILE_TEST_URI_PREFIX = 'file://' + DEVICE_LAYOUT_TESTS_DIR
+DEVICE_LAYOUT_TESTS_DIR = DEVICE_SOURCE_ROOT_DIR + 'third_party/WebKit/LayoutTests/'
 
 # Test resources that need to be accessed as files directly.
 # Each item can be the relative path of a directory or a file.
@@ -155,13 +153,7 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
     ]
 
     def __init__(self, host, port_name, **kwargs):
-        chromium.ChromiumPort.__init__(self, host, port_name, **kwargs)
-
-        # FIXME: Stop using test_shell mode: https://bugs.webkit.org/show_bug.cgi?id=88542
-        if not hasattr(self._options, 'additional_drt_flag'):
-            self._options.additional_drt_flag = []
-        if not '--test-shell' in self._options.additional_drt_flag:
-            self._options.additional_drt_flag.append('--test-shell')
+        super(ChromiumAndroidPort, self).__init__(host, port_name, **kwargs)
 
         # The Chromium port for Android always uses the hardware GPU path.
         self._options.enable_hardware_gpu = True
@@ -170,7 +162,6 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
         self._version = 'icecreamsandwich'
         self._original_governor = None
         self._android_base_dir = None
-        self._read_fifo_proc = None
 
         self._host_port = factory.PortFactory(host).get('chromium', **kwargs)
 
@@ -197,7 +188,7 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
         return self._host_port.check_wdiff(logging)
 
     def check_build(self, needs_http):
-        result = chromium.ChromiumPort.check_build(self, needs_http)
+        result = super(ChromiumAndroidPort, self).check_build(needs_http)
         result = self.check_wdiff() and result
         if not result:
             _log.error('For complete Android build requirements, please see:')
@@ -226,7 +217,7 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
         # FIXME: This is a temporary measure to reduce the manual work when
         # updating WebKit. This method should be removed when we merge
         # test_expectations_android.txt into TestExpectations.
-        expectations = chromium.ChromiumPort.test_expectations(self)
+        expectations = super(ChromiumAndroidPort, self).test_expectations()
         return expectations.replace('LINUX ', 'LINUX ANDROID ')
 
     def start_http_server(self, additional_dirs=None, number_of_servers=0):
@@ -256,7 +247,7 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
         self._run_adb_command(['shell', 'rm', '-r', DRT_APP_CACHE_DIR])
 
         # Start the HTTP server so that the device can access the test cases.
-        chromium.ChromiumPort.start_http_server(self, additional_dirs={TEST_PATH_PREFIX: self.layout_tests_dir()})
+        super(ChromiumAndroidPort, self).start_http_server(additional_dirs={TEST_PATH_PREFIX: self.layout_tests_dir()})
 
         _log.debug('Starting forwarder')
         self._run_adb_command(['shell', '%s %s' % (DEVICE_FORWARDER_PATH, FORWARD_PORTS)])
@@ -273,6 +264,11 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
             'canvas/philip',
         ])
 
+    def create_driver(self, worker_number, no_timeout=False):
+        # We don't want the default DriverProxy which is not compatible with our driver.
+        # See comments in ChromiumAndroidDriver.start().
+        return ChromiumAndroidDriver(self, worker_number, pixel_tests=self.get_option('pixel_tests'), no_timeout=no_timeout)
+
     # Overridden private functions.
 
     def _build_path(self, *comps):
@@ -323,7 +319,7 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
         if not stderr:
             stderr = ''
         stderr += '********* Tombstone file:\n' + self._get_last_stacktrace()
-        return chromium.ChromiumPort._get_crash_log(self, name, pid, stdout, stderr, newer_than)
+        return super(ChromiumAndroidPort, self)._get_crash_log(name, pid, stdout, stderr, newer_than)
 
     # Local private functions.
 
@@ -453,62 +449,57 @@ class ChromiumAndroidPort(chromium.ChromiumPort):
         self._original_governor = None
 
 
-class ChromiumAndroidDriver(chromium.ChromiumDriver):
-    # The controller may start multiple drivers during test, but for now we
-    # don't support multiple Android activities, so only one driver can be
-    # started at a time.
-    _started_driver = None
-
+class ChromiumAndroidDriver(webkit.WebKitDriver):
     def __init__(self, port, worker_number, pixel_tests, no_timeout=False):
-        chromium.ChromiumDriver.__init__(self, port, worker_number, pixel_tests, no_timeout)
+        webkit.WebKitDriver.__init__(self, port, worker_number, pixel_tests, no_timeout)
+        self._pixel_tests = pixel_tests
         self._in_fifo_path = DRT_APP_FILES_DIR + 'DumpRenderTree.in'
         self._out_fifo_path = DRT_APP_FILES_DIR + 'DumpRenderTree.out'
-        self._err_file_path = DRT_APP_FILES_DIR + 'DumpRenderTree.err'
+        self._err_fifo_path = DRT_APP_FILES_DIR + 'DumpRenderTree.err'
         self._restart_after_killed = False
-        self._read_fifo_proc = None
+        self._read_stdout_process = None
+        self._read_stderr_process = None
 
     def _command_wrapper(cls, wrapper_option):
         # Ignore command wrapper which is not applicable on Android.
         return []
 
     def cmd_line(self, pixel_tests, per_test_args):
-        original_cmd = chromium.ChromiumDriver.cmd_line(self, pixel_tests, per_test_args)
-        cmd = []
-        for param in original_cmd:
-            if param.startswith('--pixel-tests='):
-                self._device_image_path = DRT_APP_FILES_DIR + self._port.host.filesystem.basename(self._image_path)
-                param = '--pixel-tests=' + self._device_image_path
-            cmd.append(param)
-
-        cmd.append('--in-fifo=' + self._in_fifo_path)
-        cmd.append('--out-fifo=' + self._out_fifo_path)
-        cmd.append('--err-file=' + self._err_file_path)
-        return cmd
+        return self._port._adb_command + ['shell']
 
     def _file_exists_on_device(self, full_file_path):
         assert full_file_path.startswith('/')
         return self._port._run_adb_command(['shell', 'ls', full_file_path]).strip() == full_file_path
 
-    def _deadlock_detector(self, pids, normal_startup_event):
+    def _deadlock_detector(self, processes, normal_startup_event):
         time.sleep(DRT_START_STOP_TIMEOUT_SECS)
         if not normal_startup_event.is_set():
             # If normal_startup_event is not set in time, the main thread must be blocked at
             # reading/writing the fifo. Kill the fifo reading/writing processes to let the
             # main thread escape from the deadlocked state. After that, the main thread will
             # treat this as a crash.
-            for i in pids:
-                self._port._executive.kill_process(i)
+            for i in processes:
+                i.kill()
         # Otherwise the main thread has been proceeded normally. This thread just exits silently.
 
-    def _start(self, pixel_tests, per_test_args):
-        if ChromiumAndroidDriver._started_driver:
-            ChromiumAndroidDriver._started_driver.stop()
-
-        ChromiumAndroidDriver._started_driver = self
+    def _drt_cmd_line(self, pixel_tests, per_test_args):
+        return webkit.WebKitDriver.cmd_line(self, pixel_tests, per_test_args) + [
+            '--in-fifo=' + self._in_fifo_path,
+            '--out-fifo=' + self._out_fifo_path,
+            '--err-fifo=' + self._err_fifo_path,
+        ]
+
+    def start(self, pixel_tests, per_test_args):
+        # Only one driver instance is allowed because of the nature of Android activity.
+        # The single driver needs to switch between pixel test and no pixel test mode by itself.
+        if pixel_tests != self._pixel_tests:
+            self.stop()
+        super(ChromiumAndroidDriver, self).start(pixel_tests, per_test_args)
 
+    def _start(self, pixel_tests, per_test_args):
         retries = 0
         while not self._start_once(pixel_tests, per_test_args):
-            _log.error('Failed to start DumpRenderTree application. Log:\n' + self._port._get_logcat())
+            _log.error('Failed to start DumpRenderTree application. Retries=%d. Log:%s' % (retries, self._port._get_logcat()))
             retries += 1
             if retries >= 3:
                 raise AssertionError('Failed to start DumpRenderTree application multiple times. Give up.')
@@ -516,8 +507,10 @@ class ChromiumAndroidDriver(chromium.ChromiumDriver):
             time.sleep(2)
 
     def _start_once(self, pixel_tests, per_test_args):
+        super(ChromiumAndroidDriver, self)._start(pixel_tests, per_test_args)
+
         self._port._run_adb_command(['logcat', '-c'])
-        self._port._run_adb_command(['shell', 'echo'] + self.cmd_line(pixel_tests, per_test_args) + ['>', COMMAND_LINE_FILE])
+        self._port._run_adb_command(['shell', 'echo'] + self._drt_cmd_line(pixel_tests, per_test_args) + ['>', COMMAND_LINE_FILE])
         start_result = self._port._run_adb_command(['shell', 'am', 'start', '-e', 'RunInSubThread', '-n', DRT_ACTIVITY_FULL_NAME])
         if start_result.find('Exception') != -1:
             _log.error('Failed to start DumpRenderTree application. Exception:\n' + start_result)
@@ -526,57 +519,59 @@ class ChromiumAndroidDriver(chromium.ChromiumDriver):
         seconds = 0
         while (not self._file_exists_on_device(self._in_fifo_path) or
                not self._file_exists_on_device(self._out_fifo_path) or
-               not self._file_exists_on_device(self._err_file_path)):
+               not self._file_exists_on_device(self._err_fifo_path)):
             time.sleep(1)
             seconds += 1
             if seconds >= DRT_START_STOP_TIMEOUT_SECS:
                 return False
 
-        shell_cmd = self._port._adb_command + ['shell']
-        executive = self._port._executive
-        # Start a process to send command through the input fifo of the DumpRenderTree app.
-        # This process must be run as an interactive adb shell because the normal adb shell doesn't support stdin.
-        self._proc = executive.popen(shell_cmd, stdin=executive.PIPE, stdout=executive.PIPE, universal_newlines=True)
         # Read back the shell prompt to ensure adb shell ready.
-        self._read_prompt()
+        deadline = time.time() + DRT_START_STOP_TIMEOUT_SECS
+        self._server_process.start()
+        self._read_prompt(deadline)
         _log.debug('Interactive shell started')
 
-        # Start a process to read from the output fifo of the DumpRenderTree app and print to stdout.
+        # Start a process to read from the stdout fifo of the DumpRenderTree app and print to stdout.
         _log.debug('Redirecting stdout to ' + self._out_fifo_path)
-        self._read_fifo_proc = executive.popen(shell_cmd + ['cat', self._out_fifo_path],
-                                               stdout=executive.PIPE, universal_newlines=True)
+        self._read_stdout_process = server_process.ServerProcess(
+            self._port, 'ReadStdout', self._port._adb_command + ['shell', 'cat', self._out_fifo_path], universal_newlines=True)
+        self._read_stdout_process.start()
+
+        # Start a process to read from the stderr fifo of the DumpRenderTree app and print to stdout.
+        _log.debug('Redirecting stderr to ' + self._err_fifo_path)
+        self._read_stderr_process = server_process.ServerProcess(
+            self._port, 'ReadStderr', self._port._adb_command + ['shell', 'cat', self._err_fifo_path], universal_newlines=True)
+        self._read_stderr_process.start()
 
         _log.debug('Redirecting stdin to ' + self._in_fifo_path)
-        (line, crash) = self._write_command_and_read_line('cat >%s\n' % self._in_fifo_path)
+        self._server_process.write('cat >%s\n' % self._in_fifo_path)
 
-        # Combine the two unidirectional pipes into one bidirectional pipe to make _write_command_and_read_line() etc
-        # work with self._proc.
-        self._proc.stdout.close()
-        self._proc.stdout = self._read_fifo_proc.stdout
+        # Combine the stdout and stderr pipes into self._server_process.
+        self._server_process.replace_outputs(self._read_stdout_process._proc.stdout, self._read_stderr_process._proc.stdout)
 
         # Start a thread to kill the pipe reading/writing processes on deadlock of the fifos during startup.
         normal_startup_event = threading.Event()
         threading.Thread(target=self._deadlock_detector,
-                         args=([self._proc.pid, self._read_fifo_proc.pid], normal_startup_event)).start()
+                         args=([self._server_process, self._read_stdout_process, self._read_stderr_process], normal_startup_event)).start()
 
         output = ''
-        while not crash and line.rstrip() != '#READY':
+        line = self._server_process.read_stdout_line(deadline)
+        while not self._server_process.timed_out and not self.has_crashed() and line.rstrip() != '#READY':
             output += line
-            (line, crash) = self._write_command_and_read_line()
+            line = self._server_process.read_stdout_line(deadline)
 
-        if crash:
+        if self._server_process.timed_out and not self.has_crashed():
             # DumpRenderTree crashes during startup, or when the deadlock detector detected
             # deadlock and killed the fifo reading/writing processes.
-            _log.error('Failed to start DumpRenderTree: \n%s\nLog:\n%s' % (output, self._port._get_logcat()))
-            self.stop()
-            raise AssertionError('Failed to start DumpRenderTree application')
+            _log.error('Failed to start DumpRenderTree: \n%s' % output)
+            return False
         else:
             # Inform the deadlock detector that the startup is successful without deadlock.
             normal_startup_event.set()
             return True
 
     def run_test(self, driver_input):
-        driver_output = chromium.ChromiumDriver.run_test(self, driver_input)
+        driver_output = super(ChromiumAndroidDriver, self).run_test(driver_input)
         if driver_output.crash:
             # When Android is OOM, DRT process may be killed by ActivityManager or system OOM.
             # It looks like a crash but there is no fatal signal logged. Re-run the test for
@@ -595,78 +590,48 @@ class ChromiumAndroidDriver(chromium.ChromiumDriver):
                 return self.run_test(driver_input)
 
         self._restart_after_killed = False
-        driver_output.error += self._get_stderr()
         return driver_output
 
     def stop(self):
-        if ChromiumAndroidDriver._started_driver != self:
-            return
-        ChromiumAndroidDriver._started_driver = None
-
         self._port._run_adb_command(['shell', 'am', 'force-stop', DRT_APP_PACKAGE])
 
-        if self._read_fifo_proc:
-            self._port._executive.kill_process(self._read_fifo_proc.pid)
-            self._read_fifo_proc = None
-
-        # Here duplicate some logic in ChromiumDriver.stop() instead of directly calling it,
-        # because our pipe reading/writing processes won't quit by itself on close of the pipes.
-        if self._proc:
-            self._proc.stdin.close()
-            self._proc.stdout.close()
-            if self._proc.stderr:
-                self._proc.stderr.close()
-            self._port._executive.kill_process(self._proc.pid)
-            if self._proc.poll() is not None:
-                self._proc.wait()
-            self._proc = None
+        if self._read_stdout_process:
+            self._read_stdout_process.kill()
+            self._read_stdout_process = None
+
+        if self._read_stderr_process:
+            self._read_stderr_process.kill()
+            self._read_stderr_process = None
+
+        # Stop and kill server_process because our pipe reading/writing processes won't quit
+        # by itself on close of the pipes.
+        if self._server_process:
+            self._server_process.stop(kill_directly=True)
+            self._server_process = None
+        super(ChromiumAndroidDriver, self).stop()
 
         seconds = 0
         while (self._file_exists_on_device(self._in_fifo_path) or
                self._file_exists_on_device(self._out_fifo_path) or
-               self._file_exists_on_device(self._err_file_path)):
+               self._file_exists_on_device(self._err_fifo_path)):
             time.sleep(1)
-            self._port._run_adb_command(['shell', 'rm', self._in_fifo_path, self._out_fifo_path, self._err_file_path])
+            self._port._run_adb_command(['shell', 'rm', self._in_fifo_path, self._out_fifo_path, self._err_fifo_path])
             seconds += 1
             if seconds >= DRT_START_STOP_TIMEOUT_SECS:
                 raise AssertionError('Failed to remove fifo files. May be locked.')
 
-    def _test_shell_command(self, uri, timeout_ms, checksum):
-        if uri.startswith('file:///'):
-            # Convert the host uri to a device uri. See comment of
+    def _command_from_driver_input(self, driver_input):
+        command = super(ChromiumAndroidDriver, self)._command_from_driver_input(driver_input)
+        if command.startswith('/'):
+            # Convert the host file path to a device file path. See comment of
             # DEVICE_LAYOUT_TESTS_DIR for details.
-            # Not overriding Port.filename_to_uri() because we don't want the
-            # links in the html report point to device paths.
-            uri = FILE_TEST_URI_PREFIX + self.uri_to_test(uri)
-        return chromium.ChromiumDriver._test_shell_command(self, uri, timeout_ms, checksum)
-
-    def _write_command_and_read_line(self, input=None):
-        (line, crash) = chromium.ChromiumDriver._write_command_and_read_line(self, input)
-        url_marker = '#URL:'
-        if not crash:
-            if line.startswith(url_marker) and line.find(FILE_TEST_URI_PREFIX) == len(url_marker):
-                # Convert the device test uri back to host uri otherwise
-                # chromium.ChromiumDriver.run_test() will complain.
-                line = '#URL:file://%s/%s' % (self._port.layout_tests_dir(), line[len(url_marker) + len(FILE_TEST_URI_PREFIX):])
-            # chromium.py uses "line == '' and self._proc.poll() is not None" to detect crash,
-            # but on Android "not line" is enough because self._proc.poll() seems not reliable.
-            if not line:
-                crash = True
-        return (line, crash)
-
-    def _output_image(self):
-        if self._image_path:
-            _log.debug('Pulling from device: %s to %s' % (self._device_image_path, self._image_path))
-            self._port._pull_from_device(self._device_image_path, self._image_path, ignore_error=True)
-        return chromium.ChromiumDriver._output_image(self)
-
-    def _get_stderr(self):
-        return self._port._run_adb_command(['shell', 'cat', self._err_file_path], ignore_error=True)
-
-    def _read_prompt(self):
+            command = DEVICE_LAYOUT_TESTS_DIR + self._port.relative_test_filename(command)
+        return command
+
+    def _read_prompt(self, deadline):
         last_char = ''
         while True:
-            current_char = self._proc.stdout.read(1)
+            current_char = self._server_process.read_stdout(deadline, 1)
             if current_char == ' ':
                 if last_char == '#':
                     return
index 8544b02..a3a3aae 100644 (file)
 
 import optparse
 import StringIO
+import time
 import unittest
 
 from webkitpy.common.system import executive_mock
 from webkitpy.common.system.executive_mock import MockExecutive2
 from webkitpy.common.system.systemhost_mock import MockSystemHost
-from webkitpy.thirdparty.mock import Mock
 
 from webkitpy.layout_tests.port import chromium_android
 from webkitpy.layout_tests.port import chromium_port_testcase
-from webkitpy.layout_tests.port import Port
+from webkitpy.layout_tests.port import driver
+from webkitpy.layout_tests.port import webkit_unittest
 
 
 class ChromiumAndroidPortTest(chromium_port_testcase.ChromiumPortTestCase):
@@ -124,6 +125,10 @@ class ChromiumAndroidPortTest(chromium_port_testcase.ChromiumPortTestCase):
              u'STDERR: /data/tombstones/tombstone_03\n'
              u'STDERR: mock_contents\n'))
 
+    def test_driver_cmd_line(self):
+        # Overriding PortTestCase.test_cmd_line(). Use ChromiumAndroidDriverTest.test_cmd_line() instead.
+        return
+
 
 class ChromiumAndroidDriverTest(unittest.TestCase):
     def setUp(self):
@@ -131,34 +136,30 @@ class ChromiumAndroidDriverTest(unittest.TestCase):
         self.driver = chromium_android.ChromiumAndroidDriver(mock_port, worker_number=0, pixel_tests=True)
 
     def test_cmd_line(self):
-        cmd_line = self.driver.cmd_line(True, ['--a'])
+        cmd_line = self.driver.cmd_line(True, ['anything'])
+        self.assertEquals(['adb', 'shell'], cmd_line)
+
+    def test_drt_cmd_line(self):
+        cmd_line = self.driver._drt_cmd_line(True, ['--a'])
         self.assertTrue('--a' in cmd_line)
         self.assertTrue('--in-fifo=' + chromium_android.DRT_APP_FILES_DIR + 'DumpRenderTree.in' in cmd_line)
         self.assertTrue('--out-fifo=' + chromium_android.DRT_APP_FILES_DIR + 'DumpRenderTree.out' in cmd_line)
-        self.assertTrue('--err-file=' + chromium_android.DRT_APP_FILES_DIR + 'DumpRenderTree.err' in cmd_line)
+        self.assertTrue('--err-fifo=' + chromium_android.DRT_APP_FILES_DIR + 'DumpRenderTree.err' in cmd_line)
 
     def test_read_prompt(self):
-        self.driver._proc = Mock()  # FIXME: This should use a tighter mock.
-        self.driver._proc.stdout = StringIO.StringIO("root@android:/ # ")
-        self.assertEquals(self.driver._read_prompt(), None)
-        self.driver._proc.stdout = StringIO.StringIO("$ ")
-        self.assertRaises(AssertionError, self.driver._read_prompt)
-
-    def test_test_shell_command(self):
-        uri = 'file://%s/test.html' % self.driver._port.layout_tests_dir()
-        self.assertEquals(uri, 'file:///mock-checkout/LayoutTests/test.html')
-        expected_command = 'file:///data/local/tmp/third_party/WebKit/LayoutTests/test.html 2 checksum\n'
-        self.assertEquals(self.driver._test_shell_command(uri, 2, 'checksum'), expected_command)
-        self.assertEquals(self.driver._test_shell_command('http://test.html', 2, 'checksum'), 'http://test.html 2 checksum\n')
-
-    def test_write_command_and_read_line(self):
-        self.driver._proc = Mock()  # FIXME: This should use a tighter mock.
-        self.driver._proc.stdout = StringIO.StringIO("#URL:file:///data/local/tmp/third_party/WebKit/LayoutTests/test.html\noutput\n\n")
-        self.assertEquals(self.driver._write_command_and_read_line(), ('#URL:file:///mock-checkout/LayoutTests/test.html\n', False))
-        self.assertEquals(self.driver._write_command_and_read_line(), ('output\n', False))
-        self.assertEquals(self.driver._write_command_and_read_line(), ('\n', False))
-        # Unexpected EOF is treated as crash.
-        self.assertEquals(self.driver._write_command_and_read_line(), ('', True))
+        self.driver._server_process = webkit_unittest.MockServerProcess(['root@android:/ # '])
+        self.assertEquals(self.driver._read_prompt(time.time() + 1), None)
+        self.driver._server_process = webkit_unittest.MockServerProcess(['$ '])
+        self.assertRaises(AssertionError, self.driver._read_prompt, time.time() + 1)
+
+    def test_command_from_driver_input(self):
+        driver_input = driver.DriverInput('foo/bar/test.html', 10, 'checksum', True)
+        expected_command = "/data/local/tmp/third_party/WebKit/LayoutTests/foo/bar/test.html'checksum\n"
+        self.assertEquals(self.driver._command_from_driver_input(driver_input), expected_command)
+
+        driver_input = driver.DriverInput('http/tests/foo/bar/test.html', 10, 'checksum', True)
+        expected_command = "http://127.0.0.1:8000/foo/bar/test.html'checksum\n"
+        self.assertEquals(self.driver._command_from_driver_input(driver_input), expected_command)
 
 
 if __name__ == '__main__':
index 7e467dc..f36f5c1 100644 (file)
@@ -61,11 +61,14 @@ class ServerProcess(object):
     indefinitely. The class also handles transparently restarting processes
     as necessary to keep issuing commands."""
 
-    def __init__(self, port_obj, name, cmd, env=None):
+    def __init__(self, port_obj, name, cmd, env=None, universal_newlines=False):
         self._port = port_obj
         self._name = name  # Should be the command name (e.g. DumpRenderTree, ImageDiff)
         self._cmd = cmd
         self._env = env
+        # Set if the process outputs non-standard newlines like '\r\n' or '\r'.
+        # Don't set if there will be binary data or the data must be ASCII encoded.
+        self._universal_newlines = universal_newlines
         self._host = self._port.host
         self._pid = None
         self._reset()
@@ -100,7 +103,8 @@ class ServerProcess(object):
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE,
                                       close_fds=close_fds,
-                                      env=self._env)
+                                      env=self._env,
+                                      universal_newlines=self._universal_newlines)
         self._pid = self._proc.pid
         fd = self._proc.stdout.fileno()
         if not self._use_win32_apis:
@@ -225,9 +229,18 @@ class ServerProcess(object):
 
         try:
             if out_fd in read_fds:
-                self._output += self._proc.stdout.read()
+                data = self._proc.stdout.read()
+                if not data:
+                    _log.warning('unexpected EOF of stdout')
+                    self._crashed = True
+                self._output += data
+
             if err_fd in read_fds:
-                self._error += self._proc.stderr.read()
+                data = self._proc.stderr.read()
+                if not data:
+                    _log.warning('unexpected EOF of stderr')
+                    self._crashed = True
+                self._error += data
         except IOError, e:
             # We can ignore the IOErrors because we will detect if the subporcess crashed
             # the next time through the loop in _read()
@@ -295,7 +308,7 @@ class ServerProcess(object):
         if not self._proc:
             self._start()
 
-    def stop(self):
+    def stop(self, kill_directly=False):
         if not self._proc:
             return
 
@@ -307,12 +320,14 @@ class ServerProcess(object):
         self._proc.stdout.close()
         if self._proc.stderr:
             self._proc.stderr.close()
-        if not self._host.platform.is_win():
+
+        if kill_directly:
+            self.kill()
+        elif not self._host.platform.is_win():
             # Closing stdin/stdout/stderr hangs sometimes on OS X,
-            # (see restart(), above), and anyway we don't want to hang
-            # the harness if DumpRenderTree is buggy, so we wait a couple
-            # seconds to give DumpRenderTree a chance to clean up, but then
-            # force-kill the process if necessary.
+            # and anyway we don't want to hang the harness if DumpRenderTree
+            # is buggy, so we wait a couple seconds to give DumpRenderTree a
+            # chance to clean up, but then force-kill the process if necessary.
             KILL_TIMEOUT = 3.0
             timeout = time.time() + KILL_TIMEOUT
             while self._proc.poll() is None and time.time() < timeout:
@@ -329,3 +344,12 @@ class ServerProcess(object):
             if self._proc.poll() is not None:
                 self._proc.wait()
             self._reset()
+
+    def replace_outputs(self, stdout, stderr):
+        assert self._proc
+        if stdout:
+            self._proc.stdout.close()
+            self._proc.stdout = stdout
+        if stderr:
+            self._proc.stderr.close()
+            self._proc.stderr = stderr
index fad6f7a..d376e77 100755 (executable)
@@ -531,7 +531,7 @@ class WebKitDriver(Driver):
                 command = cygpath(command)
 
         if driver_input.image_hash:
-            # FIXME: Why the leading quote?
+            # "'" is the separator of command fields.
             command += "'" + driver_input.image_hash
         return command + "\n"
 
@@ -552,11 +552,12 @@ class WebKitDriver(Driver):
     def run_test(self, driver_input):
         start_time = time.time()
         self.start(driver_input.should_run_pixel_test, driver_input.args)
+        test_begin_time = time.time()
         self.error_from_test = str()
         self.err_seen_eof = False
 
         command = self._command_from_driver_input(driver_input)
-        deadline = start_time + int(driver_input.timeout) / 1000.0
+        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
@@ -587,7 +588,7 @@ class WebKitDriver(Driver):
             self._server_process.kill()
 
         return DriverOutput(text, image, actual_image_hash, audio,
-            crash=self.has_crashed(), test_time=time.time() - start_time,
+            crash=self.has_crashed(), test_time=time.time() - test_begin_time,
             timeout=timeout, error=self.error_from_test,
             crashed_process_name=self._crashed_process_name,
             crashed_pid=self._crashed_pid, crash_log=crash_log)
index cfec29b..54ad319 100755 (executable)
@@ -246,15 +246,30 @@ class MockServerProcess(object):
         return self.lines.pop(0) + "\n"
 
     def read_stdout(self, deadline, size):
-        # read_stdout doesn't actually function on lines, but this is sufficient for our testing.
-        line = self.lines.pop(0)
-        assert len(line) == size
-        return line
+        first_line = self.lines[0]
+        if size > len(first_line):
+            self.lines.pop(0)
+            remaining_size = size - len(first_line) - 1
+            if not remaining_size:
+                return first_line + "\n"
+            return first_line + "\n" + self.read_stdout(deadline, remaining_size)
+        result = self.lines[0][:size]
+        self.lines[0] = self.lines[0][size:]
+        return result
 
     def read_either_stdout_or_stderr_line(self, deadline):
         # FIXME: We should have tests which intermix stderr and stdout lines.
         return self.read_stdout_line(deadline), None
 
+    def start(self):
+        return
+
+    def stop(self, kill_directly=False):
+        return
+
+    def kill(self):
+        return
+
 
 class WebKitDriverTest(unittest.TestCase):
     def test_read_block(self):
@@ -279,17 +294,35 @@ class WebKitDriverTest(unittest.TestCase):
             'ActualHash: actual',
             'ExpectedHash: expected',
             'Content-Type: image/png',
-            'Content-Length: 8',
+            'Content-Length: 9',
             "12345678",
             "#EOF",
         ])
         content_block = driver._read_block(0)
         self.assertEquals(content_block.content_type, 'image/png')
         self.assertEquals(content_block.content_hash, 'actual')
-        self.assertEquals(content_block.content, '12345678')
-        self.assertEquals(content_block.decoded_content, '12345678')
+        self.assertEquals(content_block.content, '12345678\n')
+        self.assertEquals(content_block.decoded_content, '12345678\n')
         driver._server_process = None
 
+    def test_read_base64_block(self):
+        port = TestWebKitPort()
+        driver = WebKitDriver(port, 0, pixel_tests=True)
+        driver._server_process = MockServerProcess([
+            'ActualHash: actual',
+            'ExpectedHash: expected',
+            'Content-Type: image/png',
+            'Content-Transfer-Encoding: base64',
+            'Content-Length: 12',
+            'MTIzNDU2NzgK#EOF',
+        ])
+        content_block = driver._read_block(0)
+        self.assertEquals(content_block.content_type, 'image/png')
+        self.assertEquals(content_block.content_hash, 'actual')
+        self.assertEquals(content_block.encoding, 'base64')
+        self.assertEquals(content_block.content, 'MTIzNDU2NzgK')
+        self.assertEquals(content_block.decoded_content, '12345678\n')
+
     def test_no_timeout(self):
         port = TestWebKitPort()
         driver = WebKitDriver(port, 0, pixel_tests=True, no_timeout=True)