Overlay expected and actual repaint rects for LayoutTests.

Based on dsinclair@'s original patch https://codereview.chromium.org/174073002/

For the new world of text based repaint tests it can be hard
to visualize how the repaint rects appear on the screen. This
is a first pass at visualizing the difference between the old
and new repaint rects when one of the text based repaint tests
fail.

Review URL: https://codereview.chromium.org/303223008

git-svn-id: svn://svn.chromium.org/blink/trunk@175182 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent c4dc0c6a
...@@ -799,6 +799,13 @@ function runTests() ...@@ -799,6 +799,13 @@ function runTests()
assertTrue(document.querySelector('#flaky-tests-table td:nth-child(3) a').getAttribute('href') == 'retries/foo/bar-image-diffs.html'); assertTrue(document.querySelector('#flaky-tests-table td:nth-child(3) a').getAttribute('href') == 'retries/foo/bar-image-diffs.html');
}); });
results = mockResults();
results.tests['foo'] = mockExpectation('PASS', 'TEXT');
results.tests['foo'].has_repaint_overlay = true;
runTest(results, function() {
assertTrue(document.querySelector('tbody td:nth-child(2)').textContent.indexOf('overlay') != -1);
})
document.body.innerHTML = '<pre>' + g_log.join('\n') + '</pre>'; document.body.innerHTML = '<pre>' + g_log.join('\n') + '</pre>';
} }
......
...@@ -250,3 +250,4 @@ TEST-46: PASS ...@@ -250,3 +250,4 @@ TEST-46: PASS
TEST-47: PASS TEST-47: PASS
TEST-48: PASS TEST-48: PASS
TEST-48: PASS TEST-48: PASS
TEST-49: PASS
...@@ -425,7 +425,7 @@ function shouldUseTracLinks() ...@@ -425,7 +425,7 @@ function shouldUseTracLinks()
return !globalState().results.layout_tests_dir || !location.toString().indexOf('file://') == 0; return !globalState().results.layout_tests_dir || !location.toString().indexOf('file://') == 0;
} }
function testLink(test) function testLinkTarget(test)
{ {
var target; var target;
if (shouldUseTracLinks()) { if (shouldUseTracLinks()) {
...@@ -436,6 +436,12 @@ function testLink(test) ...@@ -436,6 +436,12 @@ function testLink(test)
target += '#l1'; target += '#l1';
} else } else
target = globalState().results.layout_tests_dir + '/' + test; target = globalState().results.layout_tests_dir + '/' + test;
return target;
}
function testLink(test)
{
var target = testLinkTarget(test);
return '<a class=test-link href="' + target + '">' + test + '</a><span class=flag onclick="unflag(this)"> \u2691</span>'; return '<a class=test-link href="' + target + '">' + test + '</a><span class=flag onclick="unflag(this)"> \u2691</span>';
} }
...@@ -522,7 +528,7 @@ function toggleImages() ...@@ -522,7 +528,7 @@ function toggleImages()
} }
} }
function textResultLinks(prefix) function textResultLinks(test, prefix, hasRepaintOverlay)
{ {
var html = resultLink(prefix, '-expected.txt', 'expected') + var html = resultLink(prefix, '-expected.txt', 'expected') +
resultLink(prefix, '-actual.txt', 'actual') + resultLink(prefix, '-actual.txt', 'actual') +
...@@ -534,6 +540,9 @@ function textResultLinks(prefix) ...@@ -534,6 +540,9 @@ function textResultLinks(prefix)
if (globalState().results.has_wdiff) if (globalState().results.has_wdiff)
html += resultLink(prefix, '-wdiff.html', 'wdiff'); html += resultLink(prefix, '-wdiff.html', 'wdiff');
if (hasRepaintOverlay)
html += resultLink(prefix, '-overlay.html?' + encodeURIComponent(testLinkTarget(test)), 'overlay');
return html; return html;
} }
...@@ -585,7 +594,7 @@ function tableRow(testObject) ...@@ -585,7 +594,7 @@ function tableRow(testObject)
if (testObject.is_testharness_test) { if (testObject.is_testharness_test) {
row += resultLink(testPrefix, '-actual.txt', 'actual'); row += resultLink(testPrefix, '-actual.txt', 'actual');
} else { } else {
row += textResultLinks(testPrefix); row += textResultLinks(testObject.name, testPrefix, testObject.has_repaint_overlay);
} }
} }
...@@ -700,7 +709,7 @@ function testList(tests, header, tableId) ...@@ -700,7 +709,7 @@ function testList(tests, header, tableId)
html += resultLink(stripExtension(test), '-leak-log.txt', 'leak log'); html += resultLink(stripExtension(test), '-leak-log.txt', 'leak log');
else if (tableId == 'timeout-tests-table') { else if (tableId == 'timeout-tests-table') {
// FIXME: only include timeout actual/diff results here if we actually spit out results for timeout tests. // FIXME: only include timeout actual/diff results here if we actually spit out results for timeout tests.
html += textResultLinks(stripExtension(test)); html += textResultLinks(test, stripExtension(test), testObject.has_repaint_overlay);
} }
html += '</td></tr></tbody>'; html += '</td></tr></tbody>';
......
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import re
def result_contains_repaint_rects(text):
return isinstance(text, str) and re.search('^\s*\(repaint rects$', text, re.MULTILINE) != None
def generate_repaint_overlay_html(test_name, actual_text, expected_text):
if not result_contains_repaint_rects(expected_text):
return ''
def make_js_rect(input_str):
rect_pattern = '\(rect\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\)'
rects = []
for m in re.finditer(rect_pattern, input_str):
rects.append('[' + ','.join(m.groups()) + ']')
return '[' + ','.join(rects) + ']'
# FIXME: Need to consider layer offset and transforms.
expected_rects = make_js_rect(expected_text)
actual_rects = make_js_rect(actual_text)
return """<!DOCTYPE HTML>
<html>
<head>
<title>%(title)s</title>
<style>
body {
margin: 0;
padding: 0;
}
iframe {
position: absolute;
top: 60px;
left: 0;
border: 0;
z-index: -1;
}
canvas {
position: absolute;
top: 60px;
left: 0;
z-index: 1;
}
#actual { display: none; }
#dump {
position: absolute;
top: 60px;
left: 0;
z-index: 2;
display: none;
}
</style>
</head>
<body>
Note: This version doesn't support transformation and layer offset yet.<br>
<label><input type="checkbox" checked onchange="toggle_test(this.checked)">Show test</label>
<label><input type="checkbox" checked onchange="toggle_diff_only(this.checked)">Show differences only</label>
<label><input type="checkbox" checked onchange="toggle_hide_duplicate_rects(this.checked)">Hide duplicate rects</label>
<label><input type="checkbox" onchange="toggle_print_rects(this.checked)">Dump rects</label>
<br>
<span id='type'>Expected Invalidations</span>
<div id=overlay>
<canvas id='expected' width='2000' height='2000'></canvas>
<canvas id='actual' width='2000' height='2000'></canvas>
<pre id='dump'></pre>
</div>
<script>
var show_diff_only = true;
var hide_duplicate_rects = true;
function toggle_test(show_test) {
iframe.style.display = show_test ? 'block' : 'none';
}
function toggle_diff_only(new_show_diff_only) {
show_diff_only = new_show_diff_only;
draw_repaint_rects();
}
function toggle_hide_duplicate_rects(new_hide_duplicate_rects) {
hide_duplicate_rects = new_hide_duplicate_rects;
draw_repaint_rects();
}
function toggle_print_rects(dump_rects) {
document.getElementById('dump').style.display = dump_rects ? 'block' : 'none';
}
var original_expected_rects = %(expected_rects)s;
var original_actual_rects = %(actual_rects)s;
function rectsEqual(rect1, rect2) {
return rect1[0] == rect2[0] && rect1[1] == rect2[1] && rect1[2] == rect2[2] && rect1[3] == rect2[3];
}
function findDifference(rects1, rects2) {
for (var i = rects1.length - 1; i >= 0; i--) {
for (var k = rects2.length - 1; k >= 0; k--) {
if (rectsEqual(rects1[i], rects2[k])) {
rects1.splice(i, 1);
rects2.splice(k, 1);
break;
}
}
}
}
function removeDuplicateRects(rects) {
for (var i = rects.length - 1; i > 0; i--) {
for (var k = i - 1; k >= 0; k--) {
if (rectsEqual(rects[i], rects[k])) {
rects.splice(i, 1);
break;
}
}
}
}
function draw_rects(context, rects) {
context.clearRect(0, 0, 2000, 2000);
for (var i = 0; i < rects.length; i++) {
var rect = rects[i];
context.fillRect(rect[0], rect[1], rect[2], rect[3]);
context.strokeRect(rect[0], rect[1], rect[2], rect[3]);
}
}
var expected_canvas = document.getElementById('expected');
var actual_canvas = document.getElementById('actual');
function dump_rects(rects) {
var result = '';
for (var i = 0; i < rects.length; i++)
result += '(' + rects[i].toString() + ')\\n';
return result;
}
function draw_repaint_rects() {
var expected_rects = original_expected_rects.slice(0);
var actual_rects = original_actual_rects.slice(0);
if (hide_duplicate_rects) {
removeDuplicateRects(expected_rects);
removeDuplicateRects(actual_rects);
}
if (show_diff_only)
findDifference(expected_rects, actual_rects);
document.getElementById('dump').textContent =
'Expected:\\n' + dump_rects(expected_rects)
+ '\\nActual:\\n' + dump_rects(actual_rects);
var expected_ctx = expected_canvas.getContext("2d");
expected_ctx.lineWidth = 0.5;
expected_ctx.strokeStyle = 'rgba(255, 0, 0, 1)';
expected_ctx.fillStyle = 'rgba(255, 0, 0, 0.25)';
draw_rects(expected_ctx, expected_rects);
var actual_ctx = actual_canvas.getContext("2d");
actual_ctx.lineWidth = 1;
actual_ctx.strokeStyle = 'rgba(0, 255, 0, 1)';
actual_ctx.fillStyle = 'rgba(0, 255, 0, 0.25)';
draw_rects(actual_ctx, actual_rects);
}
draw_repaint_rects();
var path = decodeURIComponent(location.search).substr(1);
var iframe = document.createElement('iframe');
iframe.id = 'test-frame';
iframe.width = 800;
iframe.height = 600;
iframe.src = path;
var overlay = document.getElementById('overlay');
overlay.appendChild(iframe);
var type = document.getElementById('type');
var expected_showing = true;
function flip() {
if (expected_showing) {
type.textContent = 'Actual Invalidations';
expected_canvas.style.display = 'none';
actual_canvas.style.display = 'block';
} else {
type.textContent = 'Expected Invalidations';
actual_canvas.style.display = 'none';
expected_canvas.style.display = 'block';
}
expected_showing = !expected_showing
}
setInterval(flip, 3000);
</script>
</body>
</html>
""" % {
'title': test_name,
'expected_rects': expected_rects,
'actual_rects': actual_rects,
}
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import optparse
import webkitpy.thirdparty.unittest2 as unittest
from webkitpy.layout_tests.controllers import repaint_overlay
EXPECTED_TEXT = """
(GraphicsLayer
(bounds 800.00 600.00)
(children 1
(GraphicsLayer
(bounds 800.00 600.00)
(contentsOpaque 1)
(drawsContent 1)
(repaint rects
(rect 8.00 108.00 100.00 100.00)
(rect 0.00 216.00 800.00 100.00)
)
)
)
)
"""
ACTUAL_TEXT = """
(GraphicsLayer
(bounds 800.00 600.00)
(children 1
(GraphicsLayer
(bounds 800.00 600.00)
(contentsOpaque 1)
(drawsContent 1)
(repaint rects
(rect 0.00 216.00 800.00 100.00)
)
)
)
)
"""
class TestRepaintOverlay(unittest.TestCase):
def test_result_contains_repaint_rects(self):
self.assertTrue(repaint_overlay.result_contains_repaint_rects(EXPECTED_TEXT))
self.assertTrue(repaint_overlay.result_contains_repaint_rects(ACTUAL_TEXT))
self.assertFalse(repaint_overlay.result_contains_repaint_rects('ABCD'))
def test_generate_repaint_overlay_html(self):
html = repaint_overlay.generate_repaint_overlay_html('test', ACTUAL_TEXT, EXPECTED_TEXT)
self.assertNotEqual(-1, html.find('expected_rects = [[8.00,108.00,100.00,100.00],[0.00,216.00,800.00,100.00]];'))
self.assertNotEqual(-1, html.find('actual_rects = [[0.00,216.00,800.00,100.00]];'))
...@@ -31,6 +31,7 @@ import logging ...@@ -31,6 +31,7 @@ import logging
import re import re
import time import time
from webkitpy.layout_tests.controllers import repaint_overlay
from webkitpy.layout_tests.controllers import test_result_writer from webkitpy.layout_tests.controllers import test_result_writer
from webkitpy.layout_tests.port.driver import DeviceFailure, DriverInput, DriverOutput from webkitpy.layout_tests.port.driver import DeviceFailure, DriverInput, DriverOutput
from webkitpy.layout_tests.models import test_expectations from webkitpy.layout_tests.models import test_expectations
...@@ -249,7 +250,8 @@ class SingleTestRunner(object): ...@@ -249,7 +250,8 @@ class SingleTestRunner(object):
if self._should_run_pixel_test: if self._should_run_pixel_test:
failures.extend(self._compare_image(expected_driver_output, driver_output)) failures.extend(self._compare_image(expected_driver_output, driver_output))
return TestResult(self._test_name, failures, driver_output.test_time, driver_output.has_stderr(), return TestResult(self._test_name, failures, driver_output.test_time, driver_output.has_stderr(),
pid=driver_output.pid) pid=driver_output.pid,
has_repaint_overlay=repaint_overlay.result_contains_repaint_rects(expected_driver_output.text))
def _compare_testharness_test(self, driver_output, expected_driver_output): def _compare_testharness_test(self, driver_output, expected_driver_output):
if expected_driver_output.image or expected_driver_output.audio or expected_driver_output.text: if expected_driver_output.image or expected_driver_output.audio or expected_driver_output.text:
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
import logging import logging
from webkitpy.layout_tests.controllers import repaint_overlay
from webkitpy.layout_tests.models import test_failures from webkitpy.layout_tests.models import test_failures
...@@ -52,6 +53,7 @@ def write_test_result(filesystem, port, results_directory, test_name, driver_out ...@@ -52,6 +53,7 @@ def write_test_result(filesystem, port, results_directory, test_name, driver_out
test_failures.FailureTestHarnessAssertion)): test_failures.FailureTestHarnessAssertion)):
writer.write_text_files(driver_output.text, expected_driver_output.text) writer.write_text_files(driver_output.text, expected_driver_output.text)
writer.create_text_diff_and_write_result(driver_output.text, expected_driver_output.text) writer.create_text_diff_and_write_result(driver_output.text, expected_driver_output.text)
writer.create_repaint_overlay_result(driver_output.text, expected_driver_output.text)
elif isinstance(failure, test_failures.FailureMissingImage): elif isinstance(failure, test_failures.FailureMissingImage):
writer.write_image_files(driver_output.image, expected_image=None) writer.write_image_files(driver_output.image, expected_image=None)
elif isinstance(failure, test_failures.FailureMissingImageHash): elif isinstance(failure, test_failures.FailureMissingImageHash):
...@@ -107,6 +109,7 @@ class TestResultWriter(object): ...@@ -107,6 +109,7 @@ class TestResultWriter(object):
FILENAME_SUFFIX_PRETTY_PATCH = "-pretty-diff.html" FILENAME_SUFFIX_PRETTY_PATCH = "-pretty-diff.html"
FILENAME_SUFFIX_IMAGE_DIFF = "-diff.png" FILENAME_SUFFIX_IMAGE_DIFF = "-diff.png"
FILENAME_SUFFIX_IMAGE_DIFFS_HTML = "-diffs.html" FILENAME_SUFFIX_IMAGE_DIFFS_HTML = "-diffs.html"
FILENAME_SUFFIX_OVERLAY = "-overlay.html"
def __init__(self, filesystem, port, root_output_dir, test_name): def __init__(self, filesystem, port, root_output_dir, test_name):
self._filesystem = filesystem self._filesystem = filesystem
...@@ -210,6 +213,12 @@ class TestResultWriter(object): ...@@ -210,6 +213,12 @@ class TestResultWriter(object):
pretty_patch_filename = self.output_filename(self.FILENAME_SUFFIX_PRETTY_PATCH) pretty_patch_filename = self.output_filename(self.FILENAME_SUFFIX_PRETTY_PATCH)
self._write_file(pretty_patch_filename, pretty_patch) self._write_file(pretty_patch_filename, pretty_patch)
def create_repaint_overlay_result(self, actual_text, expected_text):
html = repaint_overlay.generate_repaint_overlay_html(self._test_name, actual_text, expected_text)
if html:
overlay_filename = self.output_filename(self.FILENAME_SUFFIX_OVERLAY)
self._write_file(overlay_filename, html)
def write_audio_files(self, actual_audio, expected_audio): def write_audio_files(self, actual_audio, expected_audio):
self.write_output_files('.wav', actual_audio, expected_audio) self.write_output_files('.wav', actual_audio, expected_audio)
......
...@@ -38,7 +38,7 @@ class TestResult(object): ...@@ -38,7 +38,7 @@ class TestResult(object):
def loads(string): def loads(string):
return cPickle.loads(string) return cPickle.loads(string)
def __init__(self, test_name, failures=None, test_run_time=None, has_stderr=False, reftest_type=None, pid=None, references=None, device_failed=False): def __init__(self, test_name, failures=None, test_run_time=None, has_stderr=False, reftest_type=None, pid=None, references=None, device_failed=False, has_repaint_overlay=False):
self.test_name = test_name self.test_name = test_name
self.failures = failures or [] self.failures = failures or []
self.test_run_time = test_run_time or 0 # The time taken to execute the test itself. self.test_run_time = test_run_time or 0 # The time taken to execute the test itself.
...@@ -47,6 +47,7 @@ class TestResult(object): ...@@ -47,6 +47,7 @@ class TestResult(object):
self.pid = pid self.pid = pid
self.references = references or [] self.references = references or []
self.device_failed = device_failed self.device_failed = device_failed
self.has_repaint_overlay = has_repaint_overlay
# FIXME: Setting this in the constructor makes this class hard to mutate. # FIXME: Setting this in the constructor makes this class hard to mutate.
self.type = test_failures.determine_result_type(failures) self.type = test_failures.determine_result_type(failures)
......
...@@ -267,6 +267,9 @@ def summarize_results(port_obj, expectations, initial_results, retry_results, en ...@@ -267,6 +267,9 @@ def summarize_results(port_obj, expectations, initial_results, retry_results, en
if retry_result: if retry_result:
test_dict.update(_interpret_test_failures(retry_result.failures)) test_dict.update(_interpret_test_failures(retry_result.failures))
if (result.has_repaint_overlay):
test_dict['has_repaint_overlay'] = True
# Store test hierarchically by directory. e.g. # Store test hierarchically by directory. e.g.
# foo/bar/baz.html: test_dict # foo/bar/baz.html: test_dict
# foo/bar/baz1.html: test_dict # foo/bar/baz1.html: test_dict
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment