15c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Copyright (C) 2011, Google Inc. All rights reserved.
25c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#
35c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# Redistribution and use in source and binary forms, with or without
45c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# modification, are permitted provided that the following conditions are
55c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# met:
65c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#
75c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#     * Redistributions of source code must retain the above copyright
85c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# notice, this list of conditions and the following disclaimer.
95c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#     * Redistributions in binary form must reproduce the above
105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# copyright notice, this list of conditions and the following disclaimer
115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# in the documentation and/or other materials provided with the
125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# distribution.
135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#     * Neither the name of Google Inc. nor the names of its
145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# contributors may be used to endorse or promote products derived from
155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# this software without specific prior written permission.
165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#
175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
265c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)import copy
305c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)import logging
315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
3253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)from webkitpy.common.memoized import memoized
335c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
345c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)_log = logging.getLogger(__name__)
355c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
365c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
375c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)# FIXME: Should this function be somewhere more general?
385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)def _invert_dictionary(dictionary):
395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    inverted_dictionary = {}
405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    for key, value in dictionary.items():
415c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        if inverted_dictionary.get(value):
425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            inverted_dictionary[value].append(key)
435c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        else:
445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            inverted_dictionary[value] = [key]
455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    return inverted_dictionary
465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)class BaselineOptimizer(object):
4953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)    ROOT_LAYOUT_TESTS_DIRECTORY = 'LayoutTests'
5053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
517242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def __init__(self, host, port, port_names, skip_scm_commands):
5253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        self._filesystem = host.filesystem
53f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)        self._skip_scm_commands = skip_scm_commands
54f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)        self._files_to_delete = []
55f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)        self._files_to_add = []
5653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        self._scm = host.scm()
577242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        self._default_port = port
587242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        self._ports = {}
597242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        for port_name in port_names:
607242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci            self._ports[port_name] = host.port_factory.get(port_name)
617242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci
627242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        self._webkit_base = port.webkit_base()
637242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        self._layout_tests_dir = port.layout_tests_dir()
647242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci
65e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        # Only used by unittests.
66e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        self.new_results_by_directory = []
67e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch
687242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def _baseline_root(self, baseline_name):
697242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        virtual_suite = self._virtual_suite(baseline_name)
70e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        if virtual_suite:
71e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch            return self._filesystem.join(self.ROOT_LAYOUT_TESTS_DIRECTORY, virtual_suite.name)
72e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        return self.ROOT_LAYOUT_TESTS_DIRECTORY
73e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch
74e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch    def _baseline_search_path(self, port, baseline_name):
757242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        virtual_suite = self._virtual_suite(baseline_name)
76e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        if virtual_suite:
77e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch            return port.virtual_baseline_search_path(baseline_name)
78e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        return port.baseline_search_path()
7953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
807242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def _virtual_suite(self, baseline_name):
817242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        return self._default_port.lookup_virtual_suite(baseline_name)
827242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci
837242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def _virtual_base(self, baseline_name):
847242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        return self._default_port.lookup_virtual_test_base(baseline_name)
857242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci
867242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def _relative_baseline_search_paths(self, port, baseline_name):
877242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        baseline_search_path = self._baseline_search_path(port, baseline_name)
887242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        baseline_root = self._baseline_root(baseline_name)
897242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        relative_paths = [self._filesystem.relpath(path, self._webkit_base) for path in baseline_search_path]
907242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        return relative_paths + [baseline_root]
915c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
92f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)    def _join_directory(self, directory, baseline_name):
93e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        # This code is complicated because both the directory name and the baseline_name have the virtual
94e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        # test suite in the name and the virtual baseline name is not a strict superset of the non-virtual name.
959e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles)        # For example, virtual/gpu/fast/canvas/foo-expected.png corresponds to fast/canvas/foo-expected.png and
969e12abdf8c3a23d52091ea54ebb6a04d327f9300Torne (Richard Coles)        # the baseline directories are like platform/mac/virtual/gpu/fast/canvas. So, to get the path
97e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        # to the baseline in the platform directory, we need to append jsut foo-expected.png to the directory.
987242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        virtual_suite = self._virtual_suite(baseline_name)
99e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        if virtual_suite:
100e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch            baseline_name_without_virtual = baseline_name[len(virtual_suite.name) + 1:]
101e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        else:
102e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch            baseline_name_without_virtual = baseline_name
103f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)        return self._filesystem.join(self._scm.checkout_root, directory, baseline_name_without_virtual)
104f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)
105f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)    def read_results_by_directory(self, baseline_name):
106f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)        results_by_directory = {}
1077242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        directories = reduce(set.union, map(set, [self._relative_baseline_search_paths(port, baseline_name) for port in self._ports.values()]))
108e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch
10953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        for directory in directories:
110f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)            path = self._join_directory(directory, baseline_name)
1115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            if self._filesystem.exists(path):
1125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                results_by_directory[directory] = self._filesystem.sha1(path)
1135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return results_by_directory
1145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
115e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch    def _results_by_port_name(self, results_by_directory, baseline_name):
1165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        results_by_port_name = {}
1177242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        for port_name, port in self._ports.items():
1187242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci            for directory in self._relative_baseline_search_paths(port, baseline_name):
1195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                if directory in results_by_directory:
1205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                    results_by_port_name[port_name] = results_by_directory[directory]
1215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                    break
1225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return results_by_port_name
1235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
12453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)    @memoized
125e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch    def _directories_immediately_preceding_root(self, baseline_name):
12653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        directories = set()
1277242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        for port in self._ports.values():
1287242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci            directory = self._filesystem.relpath(self._baseline_search_path(port, baseline_name)[-1], self._webkit_base)
12953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            directories.add(directory)
13053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        return directories
13153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
132e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch    def _optimize_result_for_root(self, new_results_by_directory, baseline_name):
13353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        # The root directory (i.e. LayoutTests) is the only one that doesn't correspond
13453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        # to a specific platform. As such, it's the only one where the baseline in fallback directories
135e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        # immediately before it can be promoted up, i.e. if win and mac
13653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        # have the same baseline, then it can be promoted up to be the LayoutTests baseline.
13753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        # All other baselines can only be removed if they're redundant with a baseline earlier
13853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        # in the fallback order. They can never promoted up.
139e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        directories_immediately_preceding_root = self._directories_immediately_preceding_root(baseline_name)
14053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
14153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        shared_result = None
14253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        root_baseline_unused = False
14353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        for directory in directories_immediately_preceding_root:
14453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            this_result = new_results_by_directory.get(directory)
14553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
14653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            # If any of these directories don't have a baseline, there's no optimization we can do.
14753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            if not this_result:
14853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                return
14953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
15053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            if not shared_result:
15153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                shared_result = this_result
15253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            elif shared_result != this_result:
15353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                root_baseline_unused = True
15453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
1557242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        baseline_root = self._baseline_root(baseline_name)
156e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch
15753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        # The root baseline is unused if all the directories immediately preceding the root
15853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        # have a baseline, but have different baselines, so the baselines can't be promoted up.
15953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        if root_baseline_unused:
160e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch            if baseline_root in new_results_by_directory:
161e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch                del new_results_by_directory[baseline_root]
16253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            return
16353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
164e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        new_results_by_directory[baseline_root] = shared_result
16553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        for directory in directories_immediately_preceding_root:
16653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            del new_results_by_directory[directory]
1675c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1685c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    def _find_optimal_result_placement(self, baseline_name):
1695c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        results_by_directory = self.read_results_by_directory(baseline_name)
170e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        results_by_port_name = self._results_by_port_name(results_by_directory, baseline_name)
1715c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        port_names_by_result = _invert_dictionary(results_by_port_name)
1725c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
173e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        new_results_by_directory = self._remove_redundant_results(results_by_directory, results_by_port_name, port_names_by_result, baseline_name)
174e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        self._optimize_result_for_root(new_results_by_directory, baseline_name)
1755c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
1765c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return results_by_directory, new_results_by_directory
1775c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
178e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch    def _remove_redundant_results(self, results_by_directory, results_by_port_name, port_names_by_result, baseline_name):
17953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        new_results_by_directory = copy.copy(results_by_directory)
1807242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        for port_name, port in self._ports.items():
18153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            current_result = results_by_port_name.get(port_name)
18253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
18353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            # This happens if we're missing baselines for a port.
18453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            if not current_result:
18553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                continue;
18653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
1877242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci            fallback_path = self._relative_baseline_search_paths(port, baseline_name)
18853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            current_index, current_directory = self._find_in_fallbackpath(fallback_path, current_result, new_results_by_directory)
18953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            for index in range(current_index + 1, len(fallback_path)):
19053e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                new_directory = fallback_path[index]
19153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                if not new_directory in new_results_by_directory:
19253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                    # No result for this baseline in this directory.
19353e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                    continue
19453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                elif new_results_by_directory[new_directory] == current_result:
19553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                    # Result for new_directory are redundant with the result earlier in the fallback order.
19653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                    if current_directory in new_results_by_directory:
19753e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                        del new_results_by_directory[current_directory]
19853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                else:
19953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)                    # The new_directory contains a different result, so stop trying to push results up.
2005c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                    break
2015c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
20253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        return new_results_by_directory
2035c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2045c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    def _find_in_fallbackpath(self, fallback_path, current_result, results_by_directory):
2055c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        for index, directory in enumerate(fallback_path):
2065c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            if directory in results_by_directory and (results_by_directory[directory] == current_result):
2075c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                return index, directory
2085c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        assert False, "result %s not found in fallback_path %s, %s" % (current_result, fallback_path, results_by_directory)
2095c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    def _platform(self, filename):
21153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)        platform_dir = self.ROOT_LAYOUT_TESTS_DIRECTORY + self._filesystem.sep + 'platform' + self._filesystem.sep
2125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        if filename.startswith(platform_dir):
2135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            return filename.replace(platform_dir, '').split(self._filesystem.sep)[0]
2145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        platform_dir = self._filesystem.join(self._scm.checkout_root, platform_dir)
2155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        if filename.startswith(platform_dir):
2165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            return filename.replace(platform_dir, '').split(self._filesystem.sep)[0]
2175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return '(generic)'
2185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    def _move_baselines(self, baseline_name, results_by_directory, new_results_by_directory):
2205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        data_for_result = {}
2215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        for directory, result in results_by_directory.items():
2225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            if not result in data_for_result:
223f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)                source = self._join_directory(directory, baseline_name)
2245c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                data_for_result[result] = self._filesystem.read_binary_file(source)
2255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
22609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)        scm_files = []
22709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)        fs_files = []
2285c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        for directory, result in results_by_directory.items():
2295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            if new_results_by_directory.get(directory) != result:
23009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                file_name = self._join_directory(directory, baseline_name)
23109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                if self._scm.exists(file_name):
23209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                    scm_files.append(file_name)
23309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                else:
23409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                    fs_files.append(file_name)
23509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)
23609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)        if scm_files or fs_files:
23709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)            if scm_files:
23809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                _log.debug("    Deleting (SCM):")
23909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                for platform_dir in sorted(self._platform(filename) for filename in scm_files):
24009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                    _log.debug("      " + platform_dir)
241f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)                if self._skip_scm_commands:
242f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)                    self._files_to_delete.extend(scm_files)
243f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)                else:
244f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)                    self._scm.delete_list(scm_files)
24509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)            if fs_files:
24609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                _log.debug("    Deleting (file system):")
24709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                for platform_dir in sorted(self._platform(filename) for filename in fs_files):
24809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                    _log.debug("      " + platform_dir)
24909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                for filename in fs_files:
25009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)                    self._filesystem.remove(filename)
2515c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        else:
2525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            _log.debug("    (Nothing to delete)")
2535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2545c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        file_names = []
2555c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        for directory, result in new_results_by_directory.items():
2565c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            if results_by_directory.get(directory) != result:
257f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)                destination = self._join_directory(directory, baseline_name)
2585c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                self._filesystem.maybe_make_directory(self._filesystem.split(destination)[0])
2595c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                self._filesystem.write_binary_file(destination, data_for_result[result])
2605c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                file_names.append(destination)
261f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)
2625c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        if file_names:
2635c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            _log.debug("    Adding:")
2645c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            for platform_dir in sorted(self._platform(filename) for filename in file_names):
2655c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                _log.debug("      " + platform_dir)
266f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)            if self._skip_scm_commands:
267f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)                # Have adds win over deletes.
268f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)                self._files_to_delete = list(set(self._files_to_delete) - set(file_names))
269f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)                self._files_to_add.extend(file_names)
270f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)            else:
271f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)                self._scm.add_list(file_names)
2725c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        else:
2735c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            _log.debug("    (Nothing to add)")
2745c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
2755c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)    def write_by_directory(self, results_by_directory, writer, indent):
2765c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        for path in sorted(results_by_directory):
2775c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            writer("%s%s: %s" % (indent, self._platform(path), results_by_directory[path][0:6]))
2785c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
279e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch    def _optimize_subtree(self, baseline_name):
2805c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        basename = self._filesystem.basename(baseline_name)
2815c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        results_by_directory, new_results_by_directory = self._find_optimal_result_placement(baseline_name)
28253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
2835c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        if new_results_by_directory == results_by_directory:
2845c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            if new_results_by_directory:
2855c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                _log.debug("  %s: (already optimal)" % basename)
2865c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                self.write_by_directory(results_by_directory, _log.debug, "    ")
2875c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            else:
2885c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)                _log.debug("  %s: (no baselines found)" % basename)
28953e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            # This is just used for unittests. Intentionally set it to the old data if we don't modify anything.
290e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch            self.new_results_by_directory.append(results_by_directory)
2915c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            return True
29253e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)
293e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        if self._results_by_port_name(results_by_directory, baseline_name) != self._results_by_port_name(new_results_by_directory, baseline_name):
29453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            # This really should never happen. Just a sanity check to make sure the script fails in the case of bugs
29553e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            # instead of committing incorrect baselines.
29653e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)            _log.error("  %s: optimization failed" % basename)
2975c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            self.write_by_directory(results_by_directory, _log.warning, "      ")
2985c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)            return False
2995c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
3005c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        _log.debug("  %s:" % basename)
3015c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        _log.debug("    Before: ")
3025c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        self.write_by_directory(results_by_directory, _log.debug, "      ")
3035c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        _log.debug("    After: ")
3045c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        self.write_by_directory(new_results_by_directory, _log.debug, "      ")
3055c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
3065c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        self._move_baselines(baseline_name, results_by_directory, new_results_by_directory)
3075c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)        return True
308e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch
309e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch    def _optimize_virtual_root(self, baseline_name, non_virtual_baseline_name):
3107242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        virtual_root_expected_baseline_path = self._filesystem.join(self._layout_tests_dir, baseline_name)
311e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        if not self._filesystem.exists(virtual_root_expected_baseline_path):
312e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch            return
313e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        root_sha1 = self._filesystem.sha1(virtual_root_expected_baseline_path)
314e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch
315e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        results_by_directory = self.read_results_by_directory(non_virtual_baseline_name)
316e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        # See if all the immediate predecessors of the virtual root have the same expected result.
3177242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        for port in self._ports.values():
3187242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci            directories = self._relative_baseline_search_paths(port, non_virtual_baseline_name)
319e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch            for directory in directories:
320e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch                if directory not in results_by_directory:
321e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch                    continue
322e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch                if results_by_directory[directory] != root_sha1:
323e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch                    return
324e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch                break
325e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch
326f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)        _log.debug("Deleting redundant virtual root expected result.")
327c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)        if self._skip_scm_commands and virtual_root_expected_baseline_path in self._files_to_add:
328c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)            self._files_to_add.remove(virtual_root_expected_baseline_path)
329c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)        if self._scm.exists(virtual_root_expected_baseline_path):
330c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)            _log.debug("    Deleting (SCM): " + virtual_root_expected_baseline_path)
331c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)            if self._skip_scm_commands:
332c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)                self._files_to_delete.append(virtual_root_expected_baseline_path)
333c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)            else:
334c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)                self._scm.delete(virtual_root_expected_baseline_path)
335f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)        else:
336c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)            _log.debug("    Deleting (file system): " + virtual_root_expected_baseline_path)
337c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)            self._filesystem.remove(virtual_root_expected_baseline_path)
338e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch
339e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch    def optimize(self, baseline_name):
340e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        # The virtual fallback path is the same as the non-virtual one tacked on to the bottom of the non-virtual path.
341e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        # See https://docs.google.com/a/chromium.org/drawings/d/1eGdsIKzJ2dxDDBbUaIABrN4aMLD1bqJTfyxNGZsTdmg/edit for
342e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        # a visual representation of this.
343e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        #
344e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        # So, we can optimize the virtual path, then the virtual root and then the regular path.
345e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch
3467242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        self._files_to_delete = []
3477242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        self._files_to_add = []
348e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        _log.debug("Optimizing regular fallback path.")
349e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        result = self._optimize_subtree(baseline_name)
3507242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        non_virtual_baseline_name = self._virtual_base(baseline_name)
351e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        if not non_virtual_baseline_name:
352f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)            return result, self._files_to_delete, self._files_to_add
353e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch
354e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        self._optimize_virtual_root(baseline_name, non_virtual_baseline_name)
355e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch
356e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        _log.debug("Optimizing non-virtual fallback path.")
357e69819bd8e388ea4ad1636a19aa6b2eed4952191Ben Murdoch        result |= self._optimize_subtree(non_virtual_baseline_name)
358f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)        return result, self._files_to_delete, self._files_to_add
359