Title: [98034] trunk/Tools
Revision
98034
Author
e...@webkit.org
Date
2011-10-20 16:17:42 -0700 (Thu, 20 Oct 2011)

Log Message

REGRESSION(97879): Pixel tests no longer work with NRWT on Mac
https://bugs.webkit.org/show_bug.cgi?id=70492

Reviewed by Adam Barth.

The bug turned out to be that I was assuming the block.content
would be empty before the binary content following Content-Length
was read inside _read_block.  Turns out its not, due to extra newlines
and "ExpectedHash" header.

In the process of trying to figure out what was going wrong I ended up
cleaning up our newline usage in DumpRenderTree a little.  Moved
two error messages from stdout to stderr, and fixed a little code indent/whitespace.

I also fixed ServerProcess to use "deadline" everywhere instead of timeout
per Adam's request in the original bug.

* DumpRenderTree/PixelDumpSupport.cpp:
(dumpWebViewAsPixelsAndCompareWithExpected):
* DumpRenderTree/cg/ImageDiffCG.cpp:
(main):
* DumpRenderTree/mac/DumpRenderTree.mm:
(dump):
* DumpRenderTree/mac/PixelDumpSupportMac.mm:
(restoreMainDisplayColorProfile):
(setupMainDisplayColorProfile):
* Scripts/webkitpy/layout_tests/port/server_process.py:
* Scripts/webkitpy/layout_tests/port/webkit.py:
* Scripts/webkitpy/layout_tests/port/webkit_unittest.py:

Modified Paths

Diff

Modified: trunk/Tools/ChangeLog (98033 => 98034)


--- trunk/Tools/ChangeLog	2011-10-20 22:55:49 UTC (rev 98033)
+++ trunk/Tools/ChangeLog	2011-10-20 23:17:42 UTC (rev 98034)
@@ -1,3 +1,35 @@
+2011-10-20  Eric Seidel  <e...@webkit.org>
+
+        REGRESSION(97879): Pixel tests no longer work with NRWT on Mac
+        https://bugs.webkit.org/show_bug.cgi?id=70492
+
+        Reviewed by Adam Barth.
+
+        The bug turned out to be that I was assuming the block.content
+        would be empty before the binary content following Content-Length
+        was read inside _read_block.  Turns out its not, due to extra newlines
+        and "ExpectedHash" header.
+
+        In the process of trying to figure out what was going wrong I ended up
+        cleaning up our newline usage in DumpRenderTree a little.  Moved
+        two error messages from stdout to stderr, and fixed a little code indent/whitespace.
+
+        I also fixed ServerProcess to use "deadline" everywhere instead of timeout
+        per Adam's request in the original bug.
+
+        * DumpRenderTree/PixelDumpSupport.cpp:
+        (dumpWebViewAsPixelsAndCompareWithExpected):
+        * DumpRenderTree/cg/ImageDiffCG.cpp:
+        (main):
+        * DumpRenderTree/mac/DumpRenderTree.mm:
+        (dump):
+        * DumpRenderTree/mac/PixelDumpSupportMac.mm:
+        (restoreMainDisplayColorProfile):
+        (setupMainDisplayColorProfile):
+        * Scripts/webkitpy/layout_tests/port/server_process.py:
+        * Scripts/webkitpy/layout_tests/port/webkit.py:
+        * Scripts/webkitpy/layout_tests/port/webkit_unittest.py:
+
 2011-10-20  Tony Chang  <t...@chromium.org>
 
         [chromium] Remove <stdint.h> from ImageDiff and use

Modified: trunk/Tools/DumpRenderTree/PixelDumpSupport.cpp (98033 => 98034)


--- trunk/Tools/DumpRenderTree/PixelDumpSupport.cpp	2011-10-20 22:55:49 UTC (rev 98033)
+++ trunk/Tools/DumpRenderTree/PixelDumpSupport.cpp	2011-10-20 23:17:42 UTC (rev 98034)
@@ -57,21 +57,21 @@
     // Compute the hash of the bitmap context pixels
     char actualHash[33];
     computeMD5HashStringForBitmapContext(context.get(), actualHash);
-    printf("\nActualHash: %s\n", actualHash);
-    
+    printf("\nActualHash: %s\n", actualHash); // FIXME: No need for the leading newline.
+
     // Check the computed hash against the expected one and dump image on mismatch
     bool dumpImage = true;
     if (expectedHash.length() > 0) {
         ASSERT(expectedHash.length() == 32);
+
+        printf("\nExpectedHash: %s\n", expectedHash.c_str()); // FIXME: No need for the leading newline.
         
-        printf("\nExpectedHash: %s\n", expectedHash.c_str());
-        
-        if (expectedHash == actualHash)     // FIXME: do case insensitive compare
+        if (expectedHash == actualHash) // FIXME: do case insensitive compare
             dumpImage = false;
     }
-    
+
     if (dumpImage)
-      dumpBitmap(context.get(), actualHash);
+        dumpBitmap(context.get(), actualHash);
 }
 
 static void appendIntToVector(unsigned number, Vector<unsigned char>& vector)

Modified: trunk/Tools/DumpRenderTree/cg/ImageDiffCG.cpp (98033 => 98034)


--- trunk/Tools/DumpRenderTree/cg/ImageDiffCG.cpp	2011-10-20 22:55:49 UTC (rev 98033)
+++ trunk/Tools/DumpRenderTree/cg/ImageDiffCG.cpp	2011-10-20 23:17:42 UTC (rev 98034)
@@ -211,13 +211,13 @@
             else if (imageSize > 0 && !baselineImage)
                 baselineImage = createImageFromStdin(imageSize);
             else
-                fputs("error, image size must be specified.\n", stdout);
+                fputs("error, image size must be specified.\n", stderr);
         }
 
         if (actualImage && baselineImage) {
             RetainPtr<CGImageRef> diffImage;
             float difference = 100.0f;
-            
+
             if ((CGImageGetWidth(actualImage.get()) == CGImageGetWidth(baselineImage.get())) && (CGImageGetHeight(actualImage.get()) == CGImageGetHeight(baselineImage.get())) && (imageHasAlpha(actualImage.get()) == imageHasAlpha(baselineImage.get()))) {
                 diffImage = createDifferenceImage(actualImage.get(), baselineImage.get(), difference); // difference is passed by reference
                 if (difference <= tolerance)
@@ -228,7 +228,7 @@
                 }
             } else
                 fputs("error, test and reference image have different properties.\n", stderr);
-                
+
             if (difference > 0.0f) {
                 if (diffImage) {
                     RetainPtr<CFMutableDataRef> imageData(AdoptCF, CFDataCreateMutable(0, 0));
@@ -238,7 +238,7 @@
                     printf("Content-Length: %lu\n", CFDataGetLength(imageData.get()));
                     fwrite(CFDataGetBytePtr(imageData.get()), 1, CFDataGetLength(imageData.get()), stdout);
                 }
-                
+
                 fprintf(stdout, "diff: %01.2f%% failed\n", difference);
             } else
                 fprintf(stdout, "diff: %01.2f%% passed\n", difference);

Modified: trunk/Tools/DumpRenderTree/mac/DumpRenderTree.mm (98033 => 98034)


--- trunk/Tools/DumpRenderTree/mac/DumpRenderTree.mm	2011-10-20 22:55:49 UTC (rev 98033)
+++ trunk/Tools/DumpRenderTree/mac/DumpRenderTree.mm	2011-10-20 23:17:42 UTC (rev 98034)
@@ -1083,8 +1083,8 @@
         printf("Content-Type: %s\n", [resultMimeType UTF8String]);
 
         if (gLayoutTestController->dumpAsAudio())
-            printf("Content-Transfer-Encoding: base64\n");            
-        
+            printf("Content-Transfer-Encoding: base64\n");
+
         if (resultData) {
             fwrite([resultData bytes], 1, [resultData length], stdout);
 

Modified: trunk/Tools/DumpRenderTree/mac/PixelDumpSupportMac.mm (98033 => 98034)


--- trunk/Tools/DumpRenderTree/mac/PixelDumpSupportMac.mm	2011-10-20 22:55:49 UTC (rev 98033)
+++ trunk/Tools/DumpRenderTree/mac/PixelDumpSupportMac.mm	2011-10-20 23:17:42 UTC (rev 98034)
@@ -62,7 +62,7 @@
         const CMDeviceScope scope = { kCFPreferencesCurrentUser, kCFPreferencesCurrentHost };
         int error = CMSetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)kCGDirectMainDisplay, &scope, cmDefaultProfileID, &sInitialProfileLocation);
         if (error)
-            fprintf(stderr, "Failed to restore initial color profile for main display! Open System Preferences > Displays > Color and manually re-select the profile.  (Error: %i)", error);
+            fprintf(stderr, "Failed to restore initial color profile for main display! Open System Preferences > Displays > Color and manually re-select the profile.  (Error: %i)\n", error);
         sInitialProfileLocation.locType = cmNoProfileBase;
     }
 }
@@ -80,7 +80,7 @@
         CMCloseProfile(profile);
     }
     if (error) {
-        fprintf(stderr, "Failed to retrieve current color profile for main display, thus it won't be changed.  Many pixel tests may fail as a result.  (Error: %i)", error);
+        fprintf(stderr, "Failed to retrieve current color profile for main display, thus it won't be changed.  Many pixel tests may fail as a result.  (Error: %i)\n", error);
         sInitialProfileLocation.locType = cmNoProfileBase;
         return;
     }
@@ -90,7 +90,7 @@
     strcpy(location.u.pathLoc.path, PROFILE_PATH);
     error = CMSetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)kCGDirectMainDisplay, &scope, cmDefaultProfileID, &location);
     if (error) {
-        fprintf(stderr, "Failed to set color profile for main display!  Many pixel tests may fail as a result.  (Error: %i)", error);
+        fprintf(stderr, "Failed to set color profile for main display!  Many pixel tests may fail as a result.  (Error: %i)\n", error);
         sInitialProfileLocation.locType = cmNoProfileBase;
         return;
     }

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/port/server_process.py (98033 => 98034)


--- trunk/Tools/Scripts/webkitpy/layout_tests/port/server_process.py	2011-10-20 22:55:49 UTC (rev 98033)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/port/server_process.py	2011-10-20 23:17:42 UTC (rev 98034)
@@ -48,7 +48,7 @@
 class ServerProcess:
     """This class provides a wrapper around a subprocess that
     implements a simple request/response usage model. The primary benefit
-    is that reading responses takes a timeout, so that we don't ever block
+    is that reading responses takes a deadline, so that we don't ever block
     indefinitely. The class also handles transparently restarting processes
     as necessary to keep issuing commands."""
 
@@ -118,13 +118,13 @@
             return self._proc.poll()
         return None
 
-    def write(self, input):
+    def write(self, bytes):
         """Write a request to the subprocess. The subprocess is (re-)start()'ed
         if is not already running."""
         if not self._proc:
             self._start()
         try:
-            self._proc.stdin.write(input)
+            self._proc.stdin.write(bytes)
         except IOError, e:
             self.stop()
             # stop() calls _reset(), so we have to set crashed to True after calling stop().
@@ -142,13 +142,13 @@
             return self._pop_error_bytes(index_after_newline)
         return None
 
-    def read_stdout_line(self, timeout_seconds):
-        return self._read(timeout_seconds, self._pop_stdout_line_if_ready)
+    def read_stdout_line(self, deadline):
+        return self._read(deadline, self._pop_stdout_line_if_ready)
 
-    def read_stderr_line(self, timeout_seconds):
-        return self._read(timeout_seconds, self._pop_stderr_line_if_ready)
+    def read_stderr_line(self, deadline):
+        return self._read(deadline, self._pop_stderr_line_if_ready)
 
-    def read_either_stdout_or_stderr_line(self, timeout_seconds):
+    def read_either_stdout_or_stderr_line(self, deadline):
         def retrieve_bytes_from_buffers():
             stdout_line = self._pop_stdout_line_if_ready()
             if stdout_line:
@@ -158,13 +158,13 @@
                 return None, stderr_line
             return None  # Instructs the caller to keep waiting.
 
-        return_value = self._read(timeout_seconds, retrieve_bytes_from_buffers)
+        return_value = self._read(deadline, retrieve_bytes_from_buffers)
         # FIXME: This is a bit of a hack around the fact that _read normally only returns one value, but this caller wants it to return two.
         if return_value is None:
             return None, None
         return return_value
 
-    def read_stdout(self, timeout_seconds, size):
+    def read_stdout(self, deadline, size):
         if size <= 0:
             raise ValueError('ServerProcess.read() called with a non-positive size: %d ' % size)
 
@@ -173,7 +173,7 @@
                 return self._pop_output_bytes(size)
             return None
 
-        return self._read(timeout_seconds, retrieve_bytes_from_stdout_buffer)
+        return self._read(deadline, retrieve_bytes_from_stdout_buffer)
 
     def _check_for_crash(self):
         if self._proc.poll() != None:
@@ -211,7 +211,7 @@
             self._sample()
 
     def _split_string_after_index(self, string, index):
-        return string[0:index], string[index:]
+        return string[:index], string[index:]
 
     def _pop_output_bytes(self, bytes_count):
         output, self._output = self._split_string_after_index(self._output, bytes_count)
@@ -221,11 +221,11 @@
         output, self._error = self._split_string_after_index(self._error, bytes_count)
         return output
 
-    def _wait_for_data_and_update_buffers(self, ms_to_wait):
+    def _wait_for_data_and_update_buffers(self, deadline):
         out_fd = self._proc.stdout.fileno()
         err_fd = self._proc.stderr.fileno()
         select_fds = (out_fd, err_fd)
-        read_fds, _, _ = select.select(select_fds, [], select_fds, ms_to_wait)
+        read_fds, _, _ = select.select(select_fds, [], select_fds, deadline - time.time())
         try:
             if out_fd in read_fds:
                 self._output += self._proc.stdout.read()
@@ -246,8 +246,7 @@
     # This read function is a bit oddly-designed, as it polls both stdout and stderr, yet
     # only reads/returns from one of them (buffering both in local self._output/self._error).
     # It might be cleaner to pass in the file descriptor to poll instead.
-    def _read(self, timeout, fetch_bytes_from_buffers_callback):
-        deadline = time.time() + timeout
+    def _read(self, deadline, fetch_bytes_from_buffers_callback):
         while True:
             if self._check_for_abort(deadline):
                 return None
@@ -256,7 +255,7 @@
             if bytes is not None:
                 return bytes
 
-            self._wait_for_data_and_update_buffers(deadline - time.time())
+            self._wait_for_data_and_update_buffers(deadline)
 
     def stop(self):
         if not self._proc:

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/port/webkit.py (98033 => 98034)


--- trunk/Tools/Scripts/webkitpy/layout_tests/port/webkit.py	2011-10-20 22:55:49 UTC (rev 98033)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/port/webkit.py	2011-10-20 23:17:42 UTC (rev 98034)
@@ -177,35 +177,39 @@
         return process
 
     def _read_image_diff(self, sp):
-        timeout = 2.0
-        deadline = time.time() + timeout
-        output = sp.read_stdout_line(timeout)
+        deadline = time.time() + 2.0
+        output = None
         output_image = ""
-        diff_percent = 0
-        while not sp.timed_out and not sp.crashed and output:
+
+        while True:
+            output = sp.read_stdout_line(deadline)
+            if sp.timed_out or sp.crashed or not output:
+                break
+
+            if output.startswith('diff'):  # This is the last line ImageDiff prints.
+                break
+
             if output.startswith('Content-Length'):
                 m = re.match('Content-Length: (\d+)', output)
                 content_length = int(m.group(1))
-                timeout = deadline - time.time()
-                output_image = sp.read_stdout(timeout, content_length)
-                output = sp.read_stdout_line(timeout)
+                output_image = sp.read_stdout(deadline, content_length)
+                output = sp.read_stdout_line(deadline)
                 break
-            elif output.startswith('diff'):
-                break
-            else:
-                timeout = deadline - time.time()
-                output = sp.read_stdout_line(deadline)
 
         if sp.timed_out:
             _log.error("ImageDiff timed out")
         if sp.crashed:
             _log.error("ImageDiff crashed")
+        # FIXME: There is no need to shut down the ImageDiff server after every diff.
         sp.stop()
-        if output.startswith('diff'):
+
+        diff_percent = 0
+        if output and output.startswith('diff'):
             m = re.match('diff: (.+)% (passed|failed)', output)
             if m.group(2) == 'passed':
                 return [None, 0]
             diff_percent = float(m.group(1))
+
         return (output_image, diff_percent)
 
     def default_results_directory(self):
@@ -550,6 +554,7 @@
             or self._read_header(block, line, 'Content-Length: ', '_content_length', int)
             or self._read_header(block, line, 'ActualHash: ', 'content_hash')):
             return
+        # Note, we're not reading ExpectedHash: here, but we could.
         # If the line wasn't a header, we just append it to the content.
         block.content += line
 
@@ -566,20 +571,18 @@
             if out_seen_eof and (self.err_seen_eof or not wait_for_stderr_eof):
                 break
 
-            if self._server_process.timed_out or self._detected_crash():
-                break
-
-            timeout = deadline - time.time()
-
             if self.err_seen_eof:
-                out_line = self._server_process.read_stdout_line(timeout)
+                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(timeout)
+                err_line = self._server_process.read_stderr_line(deadline)
             else:
-                out_line, err_line = self._server_process.read_either_stdout_or_stderr_line(timeout)
+                out_line, err_line = self._server_process.read_either_stdout_or_stderr_line(deadline)
 
+            if self._server_process.timed_out or self._detected_crash():
+                break
+
             if out_line:
                 assert not out_seen_eof
                 out_line, out_seen_eof = self._strip_eof(out_line)
@@ -588,11 +591,13 @@
                 err_line, self.err_seen_eof = self._strip_eof(err_line)
 
             if out_line:
+                assert out_line[-1] == "\n", "Missing newline"
+                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 block._content_length is not None and not block.content:
-                    block.content = self._server_process.read_stdout(deadline - time.time(), block._content_length)
+                if content_length_before_header_check != block._content_length:
+                    block.content = self._server_process.read_stdout(deadline, block._content_length)
 
             if err_line:
                 if self._check_for_driver_crash(err_line):

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py (98033 => 98034)


--- trunk/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py	2011-10-20 22:55:49 UTC (rev 98033)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py	2011-10-20 23:17:42 UTC (rev 98034)
@@ -205,12 +205,18 @@
         self.crashed = False
         self.lines = lines or []
 
-    def read_stdout_line(self, timeout):
+    def read_stdout_line(self, deadline):
         return self.lines.pop(0) + "\n"
 
-    def read_either_stdout_or_stderr_line(self, timeout):
+    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
+
+    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(timeout), None
+        return self.read_stdout_line(deadline), None
 
 
 class WebKitDriverTest(unittest.TestCase):
@@ -227,3 +233,20 @@
         self.assertEquals(content_block.content_type, 'my_type')
         self.assertEquals(content_block.encoding, 'none')
         self.assertEquals(content_block.content_hash, 'foobar')
+
+    def test_read_binary_block(self):
+        port = TestWebKitPort()
+        driver = WebKitDriver(port, 0)
+        driver._server_process = MockServerProcess([
+            'ActualHash: actual',
+            'ExpectedHash: expected',
+            'Content-Type: image/png',
+            'Content-Length: 8',
+            "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')
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to