Commit a08d814b authored by Xianzhu Wang's avatar Xianzhu Wang Committed by Commit Bot

[rebaseline-cl] Remove extra baselines when rebaselining

This is a follow-up of crrev.com/c/1286894 which let a test fail if
the test has some extra baselines. This CL let rebaseline-cl work for
the failures by removing the extra baselines.

Bug: 703899
Change-Id: Ib811cb1179af399406214ed78cb22520967915cb
Reviewed-on: https://chromium-review.googlesource.com/c/1298124
Commit-Queue: Xianzhu Wang <wangxianzhu@chromium.org>
Reviewed-by: default avatarRobert Ma <robertma@chromium.org>
Cr-Commit-Position: refs/heads/master@{#604601}
parent 1b9b03f7
...@@ -78,15 +78,15 @@ class BaselineOptimizer(object): ...@@ -78,15 +78,15 @@ class BaselineOptimizer(object):
# The baseline belongs to a virtual suite. # The baseline belongs to a virtual suite.
_log.debug('Optimizing virtual fallback path.') _log.debug('Optimizing virtual fallback path.')
self._patch_virtual_subtree(test_name, extension, baseline_name) self._patch_virtual_subtree(test_name, extension, baseline_name)
succeeded &= self._optimize_subtree(baseline_name) succeeded &= self._optimize_subtree(test_name, baseline_name)
self._optimize_virtual_root(test_name, extension, baseline_name) self._optimize_virtual_root(test_name, extension, baseline_name)
else: else:
# The given baseline is already non-virtual. # The given baseline is already non-virtual.
non_virtual_baseline_name = baseline_name non_virtual_baseline_name = baseline_name
_log.debug('Optimizing non-virtual fallback path.') _log.debug('Optimizing non-virtual fallback path.')
succeeded &= self._optimize_subtree(non_virtual_baseline_name) succeeded &= self._optimize_subtree(test_name, non_virtual_baseline_name)
self._remove_all_pass_testharness_result_at_root(non_virtual_baseline_name) self._remove_extra_result_at_root(test_name, non_virtual_baseline_name)
if not succeeded: if not succeeded:
_log.error('Heuristics failed to optimize %s', baseline_name) _log.error('Heuristics failed to optimize %s', baseline_name)
...@@ -97,7 +97,7 @@ class BaselineOptimizer(object): ...@@ -97,7 +97,7 @@ class BaselineOptimizer(object):
for path in sorted(results_by_directory): for path in sorted(results_by_directory):
writer('%s%s: %s' % (indent, self._platform(path), results_by_directory[path])) writer('%s%s: %s' % (indent, self._platform(path), results_by_directory[path]))
def read_results_by_directory(self, baseline_name): def read_results_by_directory(self, test_name, baseline_name):
"""Reads the baselines with the given file name in all directories. """Reads the baselines with the given file name in all directories.
Returns: Returns:
...@@ -111,12 +111,15 @@ class BaselineOptimizer(object): ...@@ -111,12 +111,15 @@ class BaselineOptimizer(object):
for directory in directories: for directory in directories:
path = self._join_directory(directory, baseline_name) path = self._join_directory(directory, baseline_name)
if self._filesystem.exists(path): if self._filesystem.exists(path):
results_by_directory[directory] = ResultDigest(self._filesystem, path) results_by_directory[directory] = ResultDigest(self._filesystem, path, self._is_reftest(test_name))
return results_by_directory return results_by_directory
def _optimize_subtree(self, baseline_name): def _is_reftest(self, test_name):
return bool(self._default_port.reference_files(test_name))
def _optimize_subtree(self, test_name, baseline_name):
basename = self._filesystem.basename(baseline_name) basename = self._filesystem.basename(baseline_name)
results_by_directory, new_results_by_directory = self._find_optimal_result_placement(baseline_name) results_by_directory, new_results_by_directory = self._find_optimal_result_placement(test_name, baseline_name)
if new_results_by_directory == results_by_directory: if new_results_by_directory == results_by_directory:
if new_results_by_directory: if new_results_by_directory:
...@@ -252,19 +255,19 @@ class BaselineOptimizer(object): ...@@ -252,19 +255,19 @@ class BaselineOptimizer(object):
virtual_root_baseline_path = self._filesystem.join(self._layout_tests_dir, baseline_name) virtual_root_baseline_path = self._filesystem.join(self._layout_tests_dir, baseline_name)
if self._filesystem.exists(virtual_root_baseline_path): if self._filesystem.exists(virtual_root_baseline_path):
_log.debug('Virtual root baseline found. Checking if we can remove it.') _log.debug('Virtual root baseline found. Checking if we can remove it.')
self._try_to_remove_virtual_root(baseline_name, virtual_root_baseline_path) self._try_to_remove_virtual_root(test_name, baseline_name, virtual_root_baseline_path)
else: else:
_log.debug('Virtual root baseline not found. Searching for virtual baselines redundant with non-virtual ones.') _log.debug('Virtual root baseline not found. Searching for virtual baselines redundant with non-virtual ones.')
self._unpatch_virtual_subtree(test_name, extension, baseline_name) self._unpatch_virtual_subtree(test_name, extension, baseline_name)
def _try_to_remove_virtual_root(self, baseline_name, virtual_root_baseline_path): def _try_to_remove_virtual_root(self, test_name, baseline_name, virtual_root_baseline_path):
# See if all the successors of the virtual root (i.e. all non-virtual # See if all the successors of the virtual root (i.e. all non-virtual
# platforms) have the same baseline as the virtual root. If so, the # platforms) have the same baseline as the virtual root. If so, the
# virtual root is redundant and can be safely removed. # virtual root is redundant and can be safely removed.
virtual_root_digest = ResultDigest(self._filesystem, virtual_root_baseline_path) virtual_root_digest = ResultDigest(self._filesystem, virtual_root_baseline_path, self._is_reftest(test_name))
# Read the base (non-virtual) results. # Read the base (non-virtual) results.
results_by_directory = self.read_results_by_directory(self._virtual_base(baseline_name)) results_by_directory = self.read_results_by_directory(test_name, self._virtual_base(baseline_name))
results_by_port_name = self._results_by_port_name(results_by_directory) results_by_port_name = self._results_by_port_name(results_by_directory)
for port_name in self._ports.keys(): for port_name in self._ports.keys():
...@@ -280,10 +283,11 @@ class BaselineOptimizer(object): ...@@ -280,10 +283,11 @@ class BaselineOptimizer(object):
# Check all immediate predecessors of the virtual root and delete those # Check all immediate predecessors of the virtual root and delete those
# duplicate with their non-virtual fallback, essentially undoing some # duplicate with their non-virtual fallback, essentially undoing some
# of the work done in _patch_virtual_subtree. # of the work done in _patch_virtual_subtree.
is_reftest = self._is_reftest(test_name)
def unpatcher(virtual_baseline, non_virtual_fallback): def unpatcher(virtual_baseline, non_virtual_fallback):
if self._filesystem.exists(virtual_baseline) and \ if self._filesystem.exists(virtual_baseline) and \
(ResultDigest(self._filesystem, virtual_baseline) == (ResultDigest(self._filesystem, virtual_baseline, is_reftest) ==
ResultDigest(self._filesystem, non_virtual_fallback)): ResultDigest(self._filesystem, non_virtual_fallback, is_reftest)):
_log.debug(' Deleting (file system): %s (redundant with %s).', virtual_baseline, non_virtual_fallback) _log.debug(' Deleting (file system): %s (redundant with %s).', virtual_baseline, non_virtual_fallback)
self._filesystem.remove(virtual_baseline) self._filesystem.remove(virtual_baseline)
self._walk_immediate_predecessors_of_virtual_root(test_name, extension, baseline_name, unpatcher) self._walk_immediate_predecessors_of_virtual_root(test_name, extension, baseline_name, unpatcher)
...@@ -341,7 +345,7 @@ class BaselineOptimizer(object): ...@@ -341,7 +345,7 @@ class BaselineOptimizer(object):
results_by_port_name[port_name] = results_by_directory[directory] results_by_port_name[port_name] = results_by_directory[directory]
break break
if port_name not in results_by_port_name: if port_name not in results_by_port_name:
# Implicit all-PASS testharness.js result. # Implicit extra result.
results_by_port_name[port_name] = ResultDigest(None, None) results_by_port_name[port_name] = ResultDigest(None, None)
return results_by_port_name return results_by_port_name
...@@ -393,8 +397,8 @@ class BaselineOptimizer(object): ...@@ -393,8 +397,8 @@ class BaselineOptimizer(object):
for directory in immediately_preceding_root: for directory in immediately_preceding_root:
del new_results_by_directory[directory] del new_results_by_directory[directory]
def _find_optimal_result_placement(self, baseline_name): def _find_optimal_result_placement(self, test_name, baseline_name):
results_by_directory = self.read_results_by_directory(baseline_name) results_by_directory = self.read_results_by_directory(test_name, baseline_name)
results_by_port_name = self._results_by_port_name(results_by_directory) results_by_port_name = self._results_by_port_name(results_by_directory)
new_results_by_directory = self._remove_redundant_results( new_results_by_directory = self._remove_redundant_results(
...@@ -436,13 +440,12 @@ class BaselineOptimizer(object): ...@@ -436,13 +440,12 @@ class BaselineOptimizer(object):
found_different_result = True found_different_result = True
break break
# If we did not find a different fallback and current_result is an # If we did not find a different fallback and current_result is
# all-PASS testharness.js result, we can safely remove it, as it is # an extra result, we can safely remove it.
# the implicit result. # Note that we do not remove the generic extra result here.
# Note that we do not remove the generic all-PASS result here.
# Roots (virtual and non-virtual) are treated specially later. # Roots (virtual and non-virtual) are treated specially later.
if (not found_different_result if (not found_different_result
and current_result.is_all_pass_testharness_result and current_result.is_extra_result
and current_directory != self._baseline_root() and current_directory != self._baseline_root()
and current_directory in new_results_by_directory): and current_directory in new_results_by_directory):
del new_results_by_directory[current_directory] del new_results_by_directory[current_directory]
...@@ -454,18 +457,18 @@ class BaselineOptimizer(object): ...@@ -454,18 +457,18 @@ class BaselineOptimizer(object):
for index, directory in enumerate(search_path): for index, directory in enumerate(search_path):
if directory in results_by_directory and (results_by_directory[directory] == current_result): if directory in results_by_directory and (results_by_directory[directory] == current_result):
return index, directory return index, directory
assert current_result.is_all_pass_testharness_result, ( assert current_result.is_extra_result, (
'result %s not found in search path %s, %s' % (current_result, search_path, results_by_directory)) 'result %s not found in search path %s, %s' % (current_result, search_path, results_by_directory))
# Implicit all-PASS at the root. # Implicit extra result at the root.
return len(search_path) - 1, search_path[-1] return len(search_path) - 1, search_path[-1]
def _remove_all_pass_testharness_result_at_root(self, baseline_name): def _remove_extra_result_at_root(self, test_name, baseline_name):
"""Removes the all-PASS testharness.js result at the non-virtual root.""" """Removes extra result at the non-virtual root."""
assert not self._virtual_base(baseline_name), 'A virtual baseline is passed in.' assert not self._virtual_base(baseline_name), 'A virtual baseline is passed in.'
path = self._join_directory(self._baseline_root(), baseline_name) path = self._join_directory(self._baseline_root(), baseline_name)
if (self._filesystem.exists(path) if (self._filesystem.exists(path)
and ResultDigest.test_all_pass_testharness_result(self._filesystem, path)): and ResultDigest(self._filesystem, path, self._is_reftest(test_name)).is_extra_result):
_log.debug('Deleting redundant all-PASS root baseline.') _log.debug('Deleting extra baseline (empty, -expected.png for reftest, or all-PASS testharness JS result)')
_log.debug(' Deleting (file system): ' + path) _log.debug(' Deleting (file system): ' + path)
self._filesystem.remove(path) self._filesystem.remove(path)
...@@ -477,33 +480,58 @@ class ResultDigest(object): ...@@ -477,33 +480,58 @@ class ResultDigest(object):
including text and image. SHA1 is used internally to digest the file. including text and image. SHA1 is used internally to digest the file.
""" """
_ALL_PASS = '<PASS>' # A baseline is extra in the following cases:
# 1. if the result is an all-PASS testharness result,
def __init__(self, fs, path): # 2. if the result is empty,
# 3. if the test is a reftest and the baseline is an -expected-png file.
#
# An extra baseline should be deleted if doesn't override the fallback
# baseline. To check that, we compare the ResultDigests of the baseline and
# the fallback baseline. If the baseline is not the root baseline and the
# fallback baseline doesn't exist, we assume that the fallback baseline is
# an *implicit extra result* which equals to any extra baselines, so that
# the extra baseline will be treated as not overriding the fallback baseline
# thus will be removed.
_IMPLICIT_EXTRA_RESULT = '<EXTRA>'
def __init__(self, fs, path, is_reftest=False):
"""Constructs the digest for a result. """Constructs the digest for a result.
Args: Args:
fs: An instance of common.system.FileSystem. fs: An instance of common.system.FileSystem.
path: The path to a result file. If None is provided, the result is path: The path to a result file. If None is provided, the result is
an *implicit* all-PASS testharness.js result. an *implicit* extra result.
is_reftest: Whether the test is a reftest.
""" """
self.path = path self.path = path
if path is None: if path is None:
# Implicit all-PASS result. self.sha = self._IMPLICIT_EXTRA_RESULT
self.sha = self._ALL_PASS self.is_extra_result = True
self.is_all_pass_testharness_result = True return
else:
# Unfortunately, we may read the file twice, once in text mode and assert fs.exists(path)
# once in binary mode. if path.endswith('.txt'):
content = fs.read_text_file(path)
self.is_extra_result = not content or is_all_pass_testharness_result(content)
# Unfortunately, we may read the file twice, once in text mode
# and once in binary mode.
self.sha = fs.sha1(path) self.sha = fs.sha1(path)
self.is_all_pass_testharness_result = self.test_all_pass_testharness_result(fs, path) return
if path.endswith('.png') and is_reftest:
self.is_extra_result = True
self.sha = ''
return
self.is_extra_result = not fs.read_binary_file(path)
self.sha = fs.sha1(path)
def __eq__(self, other): def __eq__(self, other):
if other is None: if other is None:
return False return False
# Implicit all-PASS is equal to any all-PASS results. # Implicit extra result is equal to any extra results.
if self.sha == self._ALL_PASS or other.sha == self._ALL_PASS: if self.sha == self._IMPLICIT_EXTRA_RESULT or other.sha == self._IMPLICIT_EXTRA_RESULT:
return self.is_all_pass_testharness_result and other.is_all_pass_testharness_result return self.is_extra_result and other.is_extra_result
return self.sha == other.sha return self.sha == other.sha
# Python 2 does not automatically delegate __ne__ to not __eq__. # Python 2 does not automatically delegate __ne__ to not __eq__.
...@@ -511,16 +539,8 @@ class ResultDigest(object): ...@@ -511,16 +539,8 @@ class ResultDigest(object):
return not self.__eq__(other) return not self.__eq__(other)
def __str__(self): def __str__(self):
return self.sha[0:6] return self.sha[0:7]
def __repr__(self): def __repr__(self):
all_pass = ' ALL_PASS' if self.is_all_pass_testharness_result else '' is_extra_result = ' EXTRA' if self.is_extra_result else ''
return '<ResultDigest %s%s %s>' % (self.sha, all_pass, self.path) return '<ResultDigest %s%s %s>' % (self.sha, is_extra_result, self.path)
@staticmethod
def test_all_pass_testharness_result(fs, path):
# TODO(robertma): Find an appropriate constant for this (or make one).
if not path.endswith('-expected.txt'):
return False
content = fs.read_text_file(path)
return is_all_pass_testharness_result(content)
...@@ -91,10 +91,10 @@ class BaselineOptimizerTest(unittest.TestCase): ...@@ -91,10 +91,10 @@ class BaselineOptimizerTest(unittest.TestCase):
self.assertEqual(sorted(self.host.port_factory.all_port_names()), self.assertEqual(sorted(self.host.port_factory.all_port_names()),
['linux-trusty', 'mac-mac10.10', 'mac-mac10.11', 'mac-mac10.12', 'mac-mac10.13', 'win-win10']) ['linux-trusty', 'mac-mac10.10', 'mac-mac10.11', 'mac-mac10.12', 'mac-mac10.13', 'win-win10'])
def _assert_optimization(self, results_by_directory, directory_to_new_results, baseline_dirname=''): def _assert_optimization(self, results_by_directory, directory_to_new_results, baseline_dirname='', suffix='txt'):
layout_tests_dir = PathFinder(self.fs).layout_tests_dir() layout_tests_dir = PathFinder(self.fs).layout_tests_dir()
test_name = 'mock-test.html' test_name = 'mock-test.html'
baseline_name = 'mock-test-expected.txt' baseline_name = 'mock-test-expected.' + suffix
self.fs.write_text_file( self.fs.write_text_file(
self.fs.join(layout_tests_dir, 'VirtualTestSuites'), self.fs.join(layout_tests_dir, 'VirtualTestSuites'),
'[{"prefix": "gpu", "base": "fast/canvas", "args": ["--foo"]}]') '[{"prefix": "gpu", "base": "fast/canvas", "args": ["--foo"]}]')
...@@ -104,7 +104,7 @@ class BaselineOptimizerTest(unittest.TestCase): ...@@ -104,7 +104,7 @@ class BaselineOptimizerTest(unittest.TestCase):
baseline_optimizer = BaselineOptimizer(self.host, self.host.port_factory.get(), self.host.port_factory.all_port_names()) baseline_optimizer = BaselineOptimizer(self.host, self.host.port_factory.get(), self.host.port_factory.all_port_names())
self.assertTrue(baseline_optimizer.optimize( self.assertTrue(baseline_optimizer.optimize(
self.fs.join(baseline_dirname, test_name), 'txt')) self.fs.join(baseline_dirname, test_name), suffix))
for dirname, contents in directory_to_new_results.items(): for dirname, contents in directory_to_new_results.items():
path = self.fs.join(layout_tests_dir, dirname, baseline_name) path = self.fs.join(layout_tests_dir, dirname, baseline_name)
...@@ -119,6 +119,11 @@ class BaselineOptimizerTest(unittest.TestCase): ...@@ -119,6 +119,11 @@ class BaselineOptimizerTest(unittest.TestCase):
if dirname not in directory_to_new_results or directory_to_new_results[dirname] is None: if dirname not in directory_to_new_results or directory_to_new_results[dirname] is None:
self.assertFalse(self.fs.exists(path), '%s should not exist after optimization' % path) self.assertFalse(self.fs.exists(path), '%s should not exist after optimization' % path)
def _assert_reftest_optimization(self, results_by_directory, directory_to_new_results, test_path='', baseline_dirname=''):
layout_tests_dir = PathFinder(self.fs).layout_tests_dir()
self.fs.write_text_file(self.fs.join(layout_tests_dir, test_path, 'mock-test-expected.html'), 'ref')
self._assert_optimization(results_by_directory, directory_to_new_results, baseline_dirname, suffix='png')
def test_linux_redundant_with_win(self): def test_linux_redundant_with_win(self):
self._assert_optimization( self._assert_optimization(
{ {
...@@ -379,6 +384,126 @@ class BaselineOptimizerTest(unittest.TestCase): ...@@ -379,6 +384,126 @@ class BaselineOptimizerTest(unittest.TestCase):
}, },
baseline_dirname='virtual/gpu/fast/canvas') baseline_dirname='virtual/gpu/fast/canvas')
def test_empty_at_root(self):
self._assert_optimization(
{'': ''},
{'': None})
def test_empty_at_linux(self):
self._assert_optimization(
{'platform/linux': ''},
{'platform/linux': None})
def test_empty_at_linux_and_win(self):
# https://crbug.com/805008
self._assert_optimization(
{
'platform/linux': '',
'platform/win': '',
},
{
'platform/linux': None,
'platform/win': None,
})
def test_empty_at_virtual_root(self):
self._assert_optimization(
{'virtual/gpu/fast/canvas': ''},
{'virtual/gpu/fast/canvas': None},
baseline_dirname='virtual/gpu/fast/canvas')
def test_empty_at_virtual_linux(self):
self._assert_optimization(
{'platform/linux/virtual/gpu/fast/canvas': ''},
{'platform/linux/virtual/gpu/fast/canvas': None},
baseline_dirname='virtual/gpu/fast/canvas')
def test_empty_falls_back_to_non_empty(self):
# The empty baseline needs to be preserved in this case.
self._assert_optimization(
{
'platform/linux': '',
'': '1',
},
{
'platform/linux': '',
'': '1',
})
def test_virtual_empty_falls_back_to_non_empty(self):
# The empty baseline needs to be preserved in this case.
self._assert_optimization(
{
'virtual/gpu/fast/canvas': '',
'platform/linux/fast/canvas': '1',
},
{
'virtual/gpu/fast/canvas': '',
'platform/linux/fast/canvas': '1',
},
baseline_dirname='virtual/gpu/fast/canvas')
def test_extra_png_for_reftest_at_root(self):
self._assert_reftest_optimization(
{'': 'extra'},
{'': None})
def test_extra_png_for_reftest_at_linux(self):
self._assert_reftest_optimization(
{'platform/linux': 'extra'},
{'platform/linux': None})
def test_extra_png_for_reftest_at_linux_and_win(self):
# https://crbug.com/805008
self._assert_reftest_optimization(
{
'platform/linux': 'extra1',
'platform/win': 'extra2',
},
{
'platform/linux': None,
'platform/win': None,
})
def test_extra_png_for_reftest_at_virtual_root(self):
self._assert_reftest_optimization(
{'virtual/gpu/fast/canvas': 'extra'},
{'virtual/gpu/fast/canvas': None},
test_path='fast/canvas', baseline_dirname='virtual/gpu/fast/canvas')
def test_extra_png_for_reftest_at_virtual_linux(self):
self._assert_reftest_optimization(
{'platform/linux/virtual/gpu/fast/canvas': 'extra'},
{'platform/linux/virtual/gpu/fast/canvas': None},
test_path='fast/canvas', baseline_dirname='virtual/gpu/fast/canvas')
def test_extra_png_for_reftest_falls_back_to_base(self):
# The extra png for reftest should be removed even if it's different
# from the fallback.
self._assert_reftest_optimization(
{
'platform/linux': 'extra1',
'': 'extra2',
},
{
'platform/linux': None,
'': None,
})
def test_virtual_extra_png_for_reftest_falls_back_to_base(self):
# The extra png for reftest should be removed even if it's different
# from the fallback.
self._assert_reftest_optimization(
{
'virtual/gpu/fast/canvas': 'extra',
'platform/linux/fast/canvas': 'extra2',
},
{
'virtual/gpu/fast/canvas': None,
'platform/linux/fast/canvas': None,
},
test_path='fast/canvas', baseline_dirname='virtual/gpu/fast/canvas')
# Tests for protected methods - pylint: disable=protected-access # Tests for protected methods - pylint: disable=protected-access
def test_move_baselines(self): def test_move_baselines(self):
...@@ -440,26 +565,45 @@ class ResultDigestTest(unittest.TestCase): ...@@ -440,26 +565,45 @@ class ResultDigestTest(unittest.TestCase):
self.fs.write_text_file('/all-pass/foo-expected.txt', ALL_PASS_TESTHARNESS_RESULT) self.fs.write_text_file('/all-pass/foo-expected.txt', ALL_PASS_TESTHARNESS_RESULT)
self.fs.write_text_file('/all-pass/bar-expected.txt', ALL_PASS_TESTHARNESS_RESULT2) self.fs.write_text_file('/all-pass/bar-expected.txt', ALL_PASS_TESTHARNESS_RESULT2)
self.fs.write_text_file('/failures/baz-expected.txt', 'failure') self.fs.write_text_file('/failures/baz-expected.txt', 'failure')
self.fs.write_binary_file('/others/reftest-expected.png', 'extra')
def test_test_all_pass_testharness_result(self): self.fs.write_binary_file('/others/reftest2-expected.png', 'extra2')
self.assertTrue(ResultDigest.test_all_pass_testharness_result( self.fs.write_text_file('/others/empty-expected.txt', '')
self.fs, '/all-pass/foo-expected.txt')) self.fs.write_binary_file('/others/something-expected.png', 'Something')
self.assertTrue(ResultDigest.test_all_pass_testharness_result( self.fs.write_binary_file('/others/empty-expected.png', '')
self.fs, '/all-pass/bar-expected.txt'))
self.assertFalse(ResultDigest.test_all_pass_testharness_result( def test_all_pass_testharness_result(self):
self.fs, '/failures/baz-expected.txt')) self.assertTrue(ResultDigest(self.fs, '/all-pass/foo-expected.txt').is_extra_result)
self.assertFalse(ResultDigest.test_all_pass_testharness_result( self.assertTrue(ResultDigest(self.fs, '/all-pass/bar-expected.txt').is_extra_result)
self.fs, '/others/something-expected.png')) self.assertFalse(ResultDigest(self.fs, '/failures/baz-expected.txt').is_extra_result)
def test_implicit_all_pass(self): def test_empty_result(self):
# Implicit all-PASS should equal to any all-PASS but not failures. self.assertFalse(ResultDigest(self.fs, '/others/something-expected.png').is_extra_result)
self.assertTrue(ResultDigest(self.fs, '/others/empty-expected.txt').is_extra_result)
self.assertTrue(ResultDigest(self.fs, '/others/empty-expected.png').is_extra_result)
def test_extra_png_for_reftest_result(self):
self.assertFalse(ResultDigest(self.fs, '/others/something-expected.png').is_extra_result)
self.assertTrue(ResultDigest(self.fs, '/others/reftest-expected.png', is_reftest=True).is_extra_result)
def test_non_extra_result(self):
self.assertFalse(ResultDigest(self.fs, '/others/something-expected.png').is_extra_result)
def test_implicit_extra_result(self):
# Implicit empty equal to any extra result but not failures.
implicit = ResultDigest(None, None) implicit = ResultDigest(None, None)
self.assertTrue(implicit == ResultDigest(self.fs, '/all-pass/foo-expected.txt')) self.assertTrue(implicit == ResultDigest(self.fs, '/all-pass/foo-expected.txt'))
self.assertTrue(implicit == ResultDigest(self.fs, '/all-pass/bar-expected.txt')) self.assertTrue(implicit == ResultDigest(self.fs, '/all-pass/bar-expected.txt'))
self.assertFalse(implicit == ResultDigest(self.fs, '/failures/baz-expected.txt')) self.assertFalse(implicit == ResultDigest(self.fs, '/failures/baz-expected.txt'))
self.assertTrue(implicit == ResultDigest(self.fs, '/others/reftest-expected.png', is_reftest=True))
def test_different_all_pass_results(self): def test_different_all_pass_results(self):
x = ResultDigest(self.fs, '/all-pass/foo-expected.txt') x = ResultDigest(self.fs, '/all-pass/foo-expected.txt')
y = ResultDigest(self.fs, '/all-pass/bar-expected.txt') y = ResultDigest(self.fs, '/all-pass/bar-expected.txt')
self.assertTrue(x != y) self.assertTrue(x != y)
self.assertFalse(x == y) self.assertFalse(x == y)
def test_same_extra_png_for_reftest(self):
x = ResultDigest(self.fs, '/others/reftest-expected.png', is_reftest=True)
y = ResultDigest(self.fs, '/others/reftest2-expected.png', is_reftest=True)
self.assertTrue(x == y)
self.assertFalse(x != y)
...@@ -40,7 +40,9 @@ class MockWeb(object): ...@@ -40,7 +40,9 @@ class MockWeb(object):
self.urls_fetched.append(url) self.urls_fetched.append(url)
if url in self.urls: if url in self.urls:
return self.urls[url] return self.urls[url]
return 'MOCK Web result, convert 404 to None=%s' % return_none_on_404 if return_none_on_404:
return None
return 'MOCK Web result, 404 Not found'
def request(self, method, url, data, headers=None): # pylint: disable=unused-argument def request(self, method, url, data, headers=None): # pylint: disable=unused-argument
return MockResponse(self.responses.pop(0)) return MockResponse(self.responses.pop(0))
......
...@@ -33,15 +33,6 @@ class RebaselineTest(AbstractRebaseliningCommand): ...@@ -33,15 +33,6 @@ class RebaselineTest(AbstractRebaseliningCommand):
def _rebaseline_test_and_update_expectations(self, options): def _rebaseline_test_and_update_expectations(self, options):
self._baseline_suffix_list = options.suffixes.split(',') self._baseline_suffix_list = options.suffixes.split(',')
port_name = options.port_name or self._tool.builders.port_name_for_builder_name(options.builder)
port = self._tool.port_factory.get(port_name)
if port.reference_files(options.test):
if 'png' in self._baseline_suffix_list:
_log.warning('Cannot rebaseline image result for reftest: %s', options.test)
return
assert self._baseline_suffix_list == ['txt']
if options.results_directory: if options.results_directory:
results_url = 'file://' + options.results_directory results_url = 'file://' + options.results_directory
else: else:
...@@ -49,40 +40,58 @@ class RebaselineTest(AbstractRebaseliningCommand): ...@@ -49,40 +40,58 @@ class RebaselineTest(AbstractRebaseliningCommand):
options.builder, build_number=options.build_number, options.builder, build_number=options.build_number,
step_name=options.step_name) step_name=options.step_name)
succeeded = True
port_name = options.port_name or self._tool.builders.port_name_for_builder_name(options.builder)
test_name = options.test
for suffix in self._baseline_suffix_list: for suffix in self._baseline_suffix_list:
self._rebaseline_test(port_name, options.test, suffix, results_url) if not self._rebaseline_test(port_name, test_name, suffix, results_url):
self.expectation_line_changes.remove_line( succeeded = False
test=options.test,
port_name=port_name)
def _save_baseline(self, data, target_baseline): if succeeded:
if not data: self.expectation_line_changes.remove_line(test=test_name, port_name=port_name)
_log.debug('No baseline data to save.')
return
filesystem = self._tool.filesystem
self._tool.filesystem.maybe_make_directory(filesystem.dirname(target_baseline))
self._tool.filesystem.write_binary_file(target_baseline, data)
def _rebaseline_test(self, port_name, test_name, suffix, results_url): def _rebaseline_test(self, port_name, test_name, suffix, results_url):
"""Downloads a baseline file and saves it to the filesystem. """Downloads a baseline file and saves it to the filesystem.
Args: Args:
port_name: The port that the baseline is for. This determines port: The port that the baseline is for. This determines
the directory that the baseline is saved to. the directory that the baseline is saved to.
test_name: The name of the test being rebaselined. test_name: The name of the test being rebaselined.
suffix: The baseline file extension (e.g. png); together with the suffix: The baseline file extension (e.g. png); together with the
test name and results_url this determines what file to download. test name and results_url this determines what file to download.
results_url: Base URL to download the actual result from. results_url: Base URL to download the actual result from.
Returns:
True if the rebaseline is successful.
""" """
baseline_directory = self._tool.port_factory.get(port_name).baseline_version_dir() port = self._tool.port_factory.get(port_name)
baseline_directory = port.baseline_version_dir()
source_baseline = '%s/%s' % (results_url, self._file_name_for_actual_result(test_name, suffix)) source_baseline = '%s/%s' % (results_url, self._file_name_for_actual_result(test_name, suffix))
target_baseline = self._tool.filesystem.join(baseline_directory, self._file_name_for_expected_result(test_name, suffix)) target_baseline = self._tool.filesystem.join(baseline_directory, self._file_name_for_expected_result(test_name, suffix))
_log.debug('Retrieving source %s for target %s.', source_baseline, target_baseline) succeeded = True
self._save_baseline( if suffix == 'png' and port.reference_files(test_name):
self._tool.web.get_binary(source_baseline, return_none_on_404=True), _log.warning('Cannot rebaseline image result for reftest: %s', test_name)
target_baseline) succeeded = False
data = ''
# Still continue in case we can remove extra -expected.png.
else:
_log.debug('Retrieving source %s for target %s.', source_baseline, target_baseline)
data = self._tool.web.get_binary(source_baseline, return_none_on_404=True)
if not data:
# We don't just remove the file because the test may create empty
# result on this platform but non-empty on other platforms.
# Create an empty file, and let optimization deal with it.
_log.debug('Writing empty result %s which may be removed during optimization.', target_baseline)
data = ''
filesystem = self._tool.filesystem
filesystem.maybe_make_directory(filesystem.dirname(target_baseline))
filesystem.write_binary_file(target_baseline, data)
return succeeded
def _print_expectation_line_changes(self): def _print_expectation_line_changes(self):
print json.dumps(self.expectation_line_changes.to_dict()) print json.dumps(self.expectation_line_changes.to_dict())
...@@ -29,11 +29,12 @@ class TestRebaselineTest(BaseTestCase): ...@@ -29,11 +29,12 @@ class TestRebaselineTest(BaseTestCase):
self.tool.executive = MockExecutive() self.tool.executive = MockExecutive()
port = self.tool.port_factory.get('test-win-win7') port = self.tool.port_factory.get('test-win-win7')
self._write( baseline_relative_path = 'platform/test-win-win10/failures/expected/image-expected.txt'
port.host.filesystem.join( baseline_local_absolute_path = port.host.filesystem.join(port.layout_tests_dir(), baseline_relative_path)
port.layout_tests_dir(), self._write(baseline_local_absolute_path, 'original win10 result')
'platform/test-win-win10/failures/expected/image-expected.txt'), actual_result_url = ('https://test-results.appspot.com/data/layout_results/MOCK_Win10/' +
'original win10 result') 'results/layout-test-results/failures/expected/image-actual.txt')
self.tool.web.urls[actual_result_url] = 'new win10 result'
oc = OutputCapture() oc = OutputCapture()
try: try:
...@@ -53,11 +54,9 @@ class TestRebaselineTest(BaseTestCase): ...@@ -53,11 +54,9 @@ class TestRebaselineTest(BaseTestCase):
finally: finally:
out, _, _ = oc.restore_output() out, _, _ = oc.restore_output()
self.assertMultiLineEqual( self.assertItemsEqual(self.tool.web.urls_fetched, [actual_result_url])
self._read(self.tool.filesystem.join( self.assertMultiLineEqual(self._read(baseline_local_absolute_path),
port.layout_tests_dir(), 'new win10 result')
'platform/test-win-win10/failures/expected/image-expected.txt')),
'MOCK Web result, convert 404 to None=True')
self.assertFalse(self.tool.filesystem.exists(self.tool.filesystem.join( self.assertFalse(self.tool.filesystem.exists(self.tool.filesystem.join(
port.layout_tests_dir(), 'platform/test-win-win7/failures/expected/image-expected.txt'))) port.layout_tests_dir(), 'platform/test-win-win7/failures/expected/image-expected.txt')))
self.assertMultiLineEqual( self.assertMultiLineEqual(
...@@ -102,9 +101,35 @@ class TestRebaselineTest(BaseTestCase): ...@@ -102,9 +101,35 @@ class TestRebaselineTest(BaseTestCase):
def test_rebaseline_test(self): def test_rebaseline_test(self):
# pylint: disable=protected-access # pylint: disable=protected-access
actual_result_url = self.WEB_PREFIX + '/userscripts/another-test-actual.txt'
self.tool.web.urls[actual_result_url] = 'new result'
self.command._rebaseline_test('test-linux-trusty', 'userscripts/another-test.html', 'txt', self.WEB_PREFIX)
self.assertItemsEqual(self.tool.web.urls_fetched, [actual_result_url])
port = self.tool.port_factory.get('test-linux-trusty')
self.assertMultiLineEqual(
self._read(port.host.filesystem.join(port.baseline_version_dir(), 'userscripts/another-test-expected.txt')),
'new result')
def test_rebaseline_test_empty_result(self):
# pylint: disable=protected-access
actual_result_url = self.WEB_PREFIX + '/userscripts/another-test-actual.txt'
self.tool.web.urls[actual_result_url] = ''
self.command._rebaseline_test('test-linux-trusty', 'userscripts/another-test.html', 'txt', self.WEB_PREFIX) self.command._rebaseline_test('test-linux-trusty', 'userscripts/another-test.html', 'txt', self.WEB_PREFIX)
self.assertItemsEqual( self.assertItemsEqual(self.tool.web.urls_fetched, [actual_result_url])
self.tool.web.urls_fetched, [self.WEB_PREFIX + '/userscripts/another-test-actual.txt']) port = self.tool.port_factory.get('test-linux-trusty')
self.assertMultiLineEqual(
self._read(port.host.filesystem.join(port.baseline_version_dir(), 'userscripts/another-test-expected.txt')),
'')
def test_rebaseline_test_non_existence_result(self):
# pylint: disable=protected-access
actual_result_url = self.WEB_PREFIX + '/userscripts/another-test-actual.txt'
self.command._rebaseline_test('test-linux-trusty', 'userscripts/another-test.html', 'txt', self.WEB_PREFIX)
self.assertItemsEqual(self.tool.web.urls_fetched, [actual_result_url])
port = self.tool.port_factory.get('test-linux-trusty')
self.assertMultiLineEqual(
self._read(port.host.filesystem.join(port.baseline_version_dir(), 'userscripts/another-test-expected.txt')),
'')
def test_rebaseline_test_with_results_directory(self): def test_rebaseline_test_with_results_directory(self):
# pylint: disable=protected-access # pylint: disable=protected-access
...@@ -124,3 +149,15 @@ class TestRebaselineTest(BaseTestCase): ...@@ -124,3 +149,15 @@ class TestRebaselineTest(BaseTestCase):
self, self.command._rebaseline_test_and_update_expectations, args=[self.options(suffixes='png')], self, self.command._rebaseline_test_and_update_expectations, args=[self.options(suffixes='png')],
expected_logs='Cannot rebaseline image result for reftest: userscripts/another-test.html\n') expected_logs='Cannot rebaseline image result for reftest: userscripts/another-test.html\n')
self.assertDictEqual(self.command.expectation_line_changes.to_dict(), {'remove-lines': []}) self.assertDictEqual(self.command.expectation_line_changes.to_dict(), {'remove-lines': []})
def test_rebaseline_reftest_with_text(self):
# pylint: disable=protected-access
self._write('userscripts/another-test.html', 'test data')
self._write('userscripts/another-test-expected.html', 'generic result')
self._write('userscripts/another-test-expected.txt', 'text')
OutputCapture().assert_outputs(
self, self.command._rebaseline_test_and_update_expectations, args=[self.options(suffixes='png,txt')],
expected_logs='Cannot rebaseline image result for reftest: userscripts/another-test.html\n')
self.assertItemsEqual(self.tool.web.urls_fetched,
[self.WEB_PREFIX + '/userscripts/another-test-actual.txt'])
self.assertDictEqual(self.command.expectation_line_changes.to_dict(), {'remove-lines': []})
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