| OLD | NEW |
| 1 # Copyright (C) 2011, Google Inc. All rights reserved. | 1 # Copyright (C) 2011, Google Inc. All rights reserved. |
| 2 # | 2 # |
| 3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
| 4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
| 5 # met: | 5 # met: |
| 6 # | 6 # |
| 7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
| 8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
| 9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
| 10 # copyright notice, this list of conditions and the following disclaimer | 10 # copyright notice, this list of conditions and the following disclaimer |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 28 | 28 |
| 29 import copy | 29 import copy |
| 30 import logging | 30 import logging |
| 31 | 31 |
| 32 from webkitpy.common.memoized import memoized | 32 from webkitpy.common.memoized import memoized |
| 33 from functools import reduce |
| 33 | 34 |
| 34 _log = logging.getLogger(__name__) | 35 _log = logging.getLogger(__name__) |
| 35 | 36 |
| 36 | 37 |
| 37 # FIXME: Should this function be somewhere more general? | 38 # FIXME: Should this function be somewhere more general? |
| 38 def _invert_dictionary(dictionary): | 39 def _invert_dictionary(dictionary): |
| 39 inverted_dictionary = {} | 40 inverted_dictionary = {} |
| 40 for key, value in dictionary.items(): | 41 for key, value in dictionary.items(): |
| 41 if inverted_dictionary.get(value): | 42 if inverted_dictionary.get(value): |
| 42 inverted_dictionary[value].append(key) | 43 inverted_dictionary[value].append(key) |
| 43 else: | 44 else: |
| 44 inverted_dictionary[value] = [key] | 45 inverted_dictionary[value] = [key] |
| 45 return inverted_dictionary | 46 return inverted_dictionary |
| 46 | 47 |
| 47 | 48 |
| 48 class BaselineOptimizer(object): | 49 class BaselineOptimizer(object): |
| 49 ROOT_LAYOUT_TESTS_DIRECTORY = 'LayoutTests' | 50 ROOT_LAYOUT_TESTS_DIRECTORY = 'LayoutTests' |
| 50 | 51 |
| 51 def __init__(self, host, port, port_names, skip_scm_commands): | 52 def __init__(self, host, port, port_names, skip_scm_commands): |
| 52 self._filesystem = host.filesystem | 53 self._filesystem = host.filesystem |
| 53 self._skip_scm_commands = skip_scm_commands | 54 self._skip_scm_commands = skip_scm_commands |
| 54 self.files_to_delete = [] | 55 self._files_to_delete = [] |
| 55 self.files_to_add = [] | 56 self._files_to_add = [] |
| 56 self._scm = host.scm() | 57 self._scm = host.scm() |
| 57 self._default_port = port | 58 self._default_port = port |
| 58 self._ports = {} | 59 self._ports = {} |
| 59 for port_name in port_names: | 60 for port_name in port_names: |
| 60 self._ports[port_name] = host.port_factory.get(port_name) | 61 self._ports[port_name] = host.port_factory.get(port_name) |
| 61 | 62 |
| 62 self._webkit_base = port.webkit_base() | 63 self._webkit_base = port.webkit_base() |
| 63 self._layout_tests_dir = port.layout_tests_dir() | 64 self._layout_tests_dir = port.layout_tests_dir() |
| 64 | 65 |
| 65 # Only used by unittests. | 66 # Only used by unittests. |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 130 directories.add(directory) | 131 directories.add(directory) |
| 131 return directories | 132 return directories |
| 132 | 133 |
| 133 def _optimize_result_for_root(self, new_results_by_directory, baseline_name)
: | 134 def _optimize_result_for_root(self, new_results_by_directory, baseline_name)
: |
| 134 # The root directory (i.e. LayoutTests) is the only one that doesn't cor
respond | 135 # The root directory (i.e. LayoutTests) is the only one that doesn't cor
respond |
| 135 # to a specific platform. As such, it's the only one where the baseline
in fallback directories | 136 # to a specific platform. As such, it's the only one where the baseline
in fallback directories |
| 136 # immediately before it can be promoted up, i.e. if win and mac | 137 # immediately before it can be promoted up, i.e. if win and mac |
| 137 # have the same baseline, then it can be promoted up to be the LayoutTes
ts baseline. | 138 # have the same baseline, then it can be promoted up to be the LayoutTes
ts baseline. |
| 138 # All other baselines can only be removed if they're redundant with a ba
seline earlier | 139 # All other baselines can only be removed if they're redundant with a ba
seline earlier |
| 139 # in the fallback order. They can never promoted up. | 140 # in the fallback order. They can never promoted up. |
| 140 directories_preceding_root = self._directories_immediately_preceding_roo
t(baseline_name) | 141 directories_immediately_preceding_root = self._directories_immediately_p
receding_root(baseline_name) |
| 141 | 142 |
| 142 shared_result = None | 143 shared_result = None |
| 143 root_baseline_unused = False | 144 root_baseline_unused = False |
| 144 for directory in directories_preceding_root: | 145 for directory in directories_immediately_preceding_root: |
| 145 this_result = new_results_by_directory.get(directory) | 146 this_result = new_results_by_directory.get(directory) |
| 146 | 147 |
| 147 # If any of these directories don't have a baseline, there's no opti
mization we can do. | 148 # If any of these directories don't have a baseline, there's no opti
mization we can do. |
| 148 if not this_result: | 149 if not this_result: |
| 149 return | 150 return |
| 150 | 151 |
| 151 if not shared_result: | 152 if not shared_result: |
| 152 shared_result = this_result | 153 shared_result = this_result |
| 153 elif shared_result != this_result: | 154 elif shared_result != this_result: |
| 154 root_baseline_unused = True | 155 root_baseline_unused = True |
| 155 | 156 |
| 156 baseline_root = self._baseline_root(baseline_name) | 157 baseline_root = self._baseline_root(baseline_name) |
| 157 | 158 |
| 158 # The root baseline is unused if all the directories immediately precedi
ng the root | 159 # The root baseline is unused if all the directories immediately precedi
ng the root |
| 159 # have a baseline, but have different baselines, so the baselines can't
be promoted up. | 160 # have a baseline, but have different baselines, so the baselines can't
be promoted up. |
| 160 if root_baseline_unused: | 161 if root_baseline_unused: |
| 161 if baseline_root in new_results_by_directory: | 162 if baseline_root in new_results_by_directory: |
| 162 del new_results_by_directory[baseline_root] | 163 del new_results_by_directory[baseline_root] |
| 163 return | 164 return |
| 164 | 165 |
| 165 new_results_by_directory[baseline_root] = shared_result | 166 new_results_by_directory[baseline_root] = shared_result |
| 166 for directory in directories_preceding_root: | 167 for directory in directories_immediately_preceding_root: |
| 167 del new_results_by_directory[directory] | 168 del new_results_by_directory[directory] |
| 168 | 169 |
| 169 def _find_optimal_result_placement(self, baseline_name): | 170 def _find_optimal_result_placement(self, baseline_name): |
| 170 results_by_directory = self.read_results_by_directory(baseline_name) | 171 results_by_directory = self.read_results_by_directory(baseline_name) |
| 171 results_by_port_name = self._results_by_port_name(results_by_directory,
baseline_name) | 172 results_by_port_name = self._results_by_port_name(results_by_directory,
baseline_name) |
| 173 port_names_by_result = _invert_dictionary(results_by_port_name) |
| 172 | 174 |
| 173 new_results_by_directory = self._remove_redundant_results( | 175 new_results_by_directory = self._remove_redundant_results( |
| 174 results_by_directory, results_by_port_name, baseline_name) | 176 results_by_directory, results_by_port_name, port_names_by_result, ba
seline_name) |
| 175 self._optimize_result_for_root(new_results_by_directory, baseline_name) | 177 self._optimize_result_for_root(new_results_by_directory, baseline_name) |
| 176 | 178 |
| 177 return results_by_directory, new_results_by_directory | 179 return results_by_directory, new_results_by_directory |
| 178 | 180 |
| 179 def _remove_redundant_results(self, results_by_directory, results_by_port_na
me, baseline_name): | 181 def _remove_redundant_results(self, results_by_directory, results_by_port_na
me, port_names_by_result, baseline_name): |
| 180 new_results_by_directory = copy.copy(results_by_directory) | 182 new_results_by_directory = copy.copy(results_by_directory) |
| 181 for port_name, port in self._ports.items(): | 183 for port_name, port in self._ports.items(): |
| 182 current_result = results_by_port_name.get(port_name) | 184 current_result = results_by_port_name.get(port_name) |
| 183 | 185 |
| 184 # This happens if we're missing baselines for a port. | 186 # This happens if we're missing baselines for a port. |
| 185 if not current_result: | 187 if not current_result: |
| 186 continue | 188 continue |
| 187 | 189 |
| 188 fallback_path = self._relative_baseline_search_paths(port, baseline_
name) | 190 fallback_path = self._relative_baseline_search_paths(port, baseline_
name) |
| 189 current_index, current_directory = self._find_in_fallbackpath(fallba
ck_path, current_result, new_results_by_directory) | 191 current_index, current_directory = self._find_in_fallbackpath(fallba
ck_path, current_result, new_results_by_directory) |
| (...skipping 20 matching lines...) Expand all Loading... |
| 210 | 212 |
| 211 def _platform(self, filename): | 213 def _platform(self, filename): |
| 212 platform_dir = self.ROOT_LAYOUT_TESTS_DIRECTORY + self._filesystem.sep +
'platform' + self._filesystem.sep | 214 platform_dir = self.ROOT_LAYOUT_TESTS_DIRECTORY + self._filesystem.sep +
'platform' + self._filesystem.sep |
| 213 if filename.startswith(platform_dir): | 215 if filename.startswith(platform_dir): |
| 214 return filename.replace(platform_dir, '').split(self._filesystem.sep
)[0] | 216 return filename.replace(platform_dir, '').split(self._filesystem.sep
)[0] |
| 215 platform_dir = self._filesystem.join(self._webkit_base, platform_dir) | 217 platform_dir = self._filesystem.join(self._webkit_base, platform_dir) |
| 216 if filename.startswith(platform_dir): | 218 if filename.startswith(platform_dir): |
| 217 return filename.replace(platform_dir, '').split(self._filesystem.sep
)[0] | 219 return filename.replace(platform_dir, '').split(self._filesystem.sep
)[0] |
| 218 return '(generic)' | 220 return '(generic)' |
| 219 | 221 |
| 220 def move_baselines(self, baseline_name, results_by_directory, new_results_by
_directory): | 222 def _move_baselines(self, baseline_name, results_by_directory, new_results_b
y_directory): |
| 221 data_for_result = {} | 223 data_for_result = {} |
| 222 for directory, result in results_by_directory.items(): | 224 for directory, result in results_by_directory.items(): |
| 223 if not result in data_for_result: | 225 if not result in data_for_result: |
| 224 source = self._join_directory(directory, baseline_name) | 226 source = self._join_directory(directory, baseline_name) |
| 225 data_for_result[result] = self._filesystem.read_binary_file(sour
ce) | 227 data_for_result[result] = self._filesystem.read_binary_file(sour
ce) |
| 226 | 228 |
| 227 scm_files = [] | 229 scm_files = [] |
| 228 fs_files = [] | 230 fs_files = [] |
| 229 for directory, result in results_by_directory.items(): | 231 for directory, result in results_by_directory.items(): |
| 230 if new_results_by_directory.get(directory) != result: | 232 if new_results_by_directory.get(directory) != result: |
| 231 file_name = self._join_directory(directory, baseline_name) | 233 file_name = self._join_directory(directory, baseline_name) |
| 232 if self._scm.exists(file_name): | 234 if self._scm.exists(file_name): |
| 233 scm_files.append(file_name) | 235 scm_files.append(file_name) |
| 234 elif self._filesystem.exists(file_name): | 236 elif self._filesystem.exists(file_name): |
| 235 fs_files.append(file_name) | 237 fs_files.append(file_name) |
| 236 | 238 |
| 237 if scm_files or fs_files: | 239 if scm_files or fs_files: |
| 238 if scm_files: | 240 if scm_files: |
| 239 _log.debug(" Deleting (SCM):") | 241 _log.debug(" Deleting (SCM):") |
| 240 for platform_dir in sorted(self._platform(filename) for filename
in scm_files): | 242 for platform_dir in sorted(self._platform(filename) for filename
in scm_files): |
| 241 _log.debug(" " + platform_dir) | 243 _log.debug(" " + platform_dir) |
| 242 if self._skip_scm_commands: | 244 if self._skip_scm_commands: |
| 243 self.files_to_delete.extend(scm_files) | 245 self._files_to_delete.extend(scm_files) |
| 244 else: | 246 else: |
| 245 self._scm.delete_list(scm_files) | 247 self._scm.delete_list(scm_files) |
| 246 if fs_files: | 248 if fs_files: |
| 247 _log.debug(" Deleting (file system):") | 249 _log.debug(" Deleting (file system):") |
| 248 for platform_dir in sorted(self._platform(filename) for filename
in fs_files): | 250 for platform_dir in sorted(self._platform(filename) for filename
in fs_files): |
| 249 _log.debug(" " + platform_dir) | 251 _log.debug(" " + platform_dir) |
| 250 for filename in fs_files: | 252 for filename in fs_files: |
| 251 self._filesystem.remove(filename) | 253 self._filesystem.remove(filename) |
| 252 else: | 254 else: |
| 253 _log.debug(" (Nothing to delete)") | 255 _log.debug(" (Nothing to delete)") |
| 254 | 256 |
| 255 file_names = [] | 257 file_names = [] |
| 256 for directory, result in new_results_by_directory.items(): | 258 for directory, result in new_results_by_directory.items(): |
| 257 if results_by_directory.get(directory) != result: | 259 if results_by_directory.get(directory) != result: |
| 258 destination = self._join_directory(directory, baseline_name) | 260 destination = self._join_directory(directory, baseline_name) |
| 259 self._filesystem.maybe_make_directory(self._filesystem.split(des
tination)[0]) | 261 self._filesystem.maybe_make_directory(self._filesystem.split(des
tination)[0]) |
| 260 self._filesystem.write_binary_file(destination, data_for_result[
result]) | 262 self._filesystem.write_binary_file(destination, data_for_result[
result]) |
| 261 file_names.append(destination) | 263 file_names.append(destination) |
| 262 | 264 |
| 263 if file_names: | 265 if file_names: |
| 264 _log.debug(" Adding:") | 266 _log.debug(" Adding:") |
| 265 for platform_dir in sorted(self._platform(filename) for filename in
file_names): | 267 for platform_dir in sorted(self._platform(filename) for filename in
file_names): |
| 266 _log.debug(" " + platform_dir) | 268 _log.debug(" " + platform_dir) |
| 267 if self._skip_scm_commands: | 269 if self._skip_scm_commands: |
| 268 # Have adds win over deletes. | 270 # Have adds win over deletes. |
| 269 self.files_to_delete = list(set(self.files_to_delete) - set(file
_names)) | 271 self._files_to_delete = list(set(self._files_to_delete) - set(fi
le_names)) |
| 270 self.files_to_add.extend(file_names) | 272 self._files_to_add.extend(file_names) |
| 271 else: | 273 else: |
| 272 self._scm.add_list(file_names) | 274 self._scm.add_list(file_names) |
| 273 else: | 275 else: |
| 274 _log.debug(" (Nothing to add)") | 276 _log.debug(" (Nothing to add)") |
| 275 | 277 |
| 276 def write_by_directory(self, results_by_directory, writer, indent): | 278 def write_by_directory(self, results_by_directory, writer, indent): |
| 277 for path in sorted(results_by_directory): | 279 for path in sorted(results_by_directory): |
| 278 writer("%s%s: %s" % (indent, self._platform(path), results_by_direct
ory[path][0:6])) | 280 writer("%s%s: %s" % (indent, self._platform(path), results_by_direct
ory[path][0:6])) |
| 279 | 281 |
| 280 def _optimize_subtree(self, baseline_name): | 282 def _optimize_subtree(self, baseline_name): |
| (...skipping 17 matching lines...) Expand all Loading... |
| 298 _log.error(" %s: optimization failed", basename) | 300 _log.error(" %s: optimization failed", basename) |
| 299 self.write_by_directory(results_by_directory, _log.warning, " "
) | 301 self.write_by_directory(results_by_directory, _log.warning, " "
) |
| 300 return False | 302 return False |
| 301 | 303 |
| 302 _log.debug(" %s:", basename) | 304 _log.debug(" %s:", basename) |
| 303 _log.debug(" Before: ") | 305 _log.debug(" Before: ") |
| 304 self.write_by_directory(results_by_directory, _log.debug, " ") | 306 self.write_by_directory(results_by_directory, _log.debug, " ") |
| 305 _log.debug(" After: ") | 307 _log.debug(" After: ") |
| 306 self.write_by_directory(new_results_by_directory, _log.debug, " ") | 308 self.write_by_directory(new_results_by_directory, _log.debug, " ") |
| 307 | 309 |
| 308 self.move_baselines(baseline_name, results_by_directory, new_results_by_
directory) | 310 self._move_baselines(baseline_name, results_by_directory, new_results_by
_directory) |
| 309 return True | 311 return True |
| 310 | 312 |
| 311 def _optimize_virtual_root(self, baseline_name, non_virtual_baseline_name): | 313 def _optimize_virtual_root(self, baseline_name, non_virtual_baseline_name): |
| 312 virtual_root_baseline_path = self._filesystem.join(self._layout_tests_di
r, baseline_name) | 314 virtual_root_expected_baseline_path = self._filesystem.join(self._layout
_tests_dir, baseline_name) |
| 313 if not self._filesystem.exists(virtual_root_baseline_path): | 315 if not self._filesystem.exists(virtual_root_expected_baseline_path): |
| 314 return | 316 return |
| 315 root_sha1 = self._filesystem.sha1(virtual_root_baseline_path) | 317 root_sha1 = self._filesystem.sha1(virtual_root_expected_baseline_path) |
| 316 | 318 |
| 317 results_by_directory = self.read_results_by_directory(non_virtual_baseli
ne_name) | 319 results_by_directory = self.read_results_by_directory(non_virtual_baseli
ne_name) |
| 318 # See if all the immediate predecessors of the virtual root have the sam
e expected result. | 320 # See if all the immediate predecessors of the virtual root have the sam
e expected result. |
| 319 for port in self._ports.values(): | 321 for port in self._ports.values(): |
| 320 directories = self._relative_baseline_search_paths(port, non_virtual
_baseline_name) | 322 directories = self._relative_baseline_search_paths(port, non_virtual
_baseline_name) |
| 321 for directory in directories: | 323 for directory in directories: |
| 322 if directory not in results_by_directory: | 324 if directory not in results_by_directory: |
| 323 continue | 325 continue |
| 324 if results_by_directory[directory] != root_sha1: | 326 if results_by_directory[directory] != root_sha1: |
| 325 return | 327 return |
| 326 break | 328 break |
| 327 | 329 |
| 328 _log.debug("Deleting redundant virtual root expected result.") | 330 _log.debug("Deleting redundant virtual root expected result.") |
| 329 if self._skip_scm_commands and virtual_root_baseline_path in self.files_
to_add: | 331 if self._skip_scm_commands and virtual_root_expected_baseline_path in se
lf._files_to_add: |
| 330 self.files_to_add.remove(virtual_root_baseline_path) | 332 self._files_to_add.remove(virtual_root_expected_baseline_path) |
| 331 if self._scm.exists(virtual_root_baseline_path): | 333 if self._scm.exists(virtual_root_expected_baseline_path): |
| 332 _log.debug(" Deleting (SCM): " + virtual_root_baseline_path) | 334 _log.debug(" Deleting (SCM): " + virtual_root_expected_baseline_p
ath) |
| 333 if self._skip_scm_commands: | 335 if self._skip_scm_commands: |
| 334 self.files_to_delete.append(virtual_root_baseline_path) | 336 self._files_to_delete.append(virtual_root_expected_baseline_path
) |
| 335 else: | 337 else: |
| 336 self._scm.delete(virtual_root_baseline_path) | 338 self._scm.delete(virtual_root_expected_baseline_path) |
| 337 else: | 339 else: |
| 338 _log.debug(" Deleting (file system): " + virtual_root_baseline_pa
th) | 340 _log.debug(" Deleting (file system): " + virtual_root_expected_ba
seline_path) |
| 339 self._filesystem.remove(virtual_root_baseline_path) | 341 self._filesystem.remove(virtual_root_expected_baseline_path) |
| 340 | 342 |
| 341 def optimize(self, baseline_name): | 343 def optimize(self, baseline_name): |
| 342 # The virtual fallback path is the same as the non-virtual one tacked on
to the bottom of the non-virtual path. | 344 # The virtual fallback path is the same as the non-virtual one tacked on
to the bottom of the non-virtual path. |
| 343 # See https://docs.google.com/a/chromium.org/drawings/d/1eGdsIKzJ2dxDDBb
UaIABrN4aMLD1bqJTfyxNGZsTdmg/edit for | 345 # See https://docs.google.com/a/chromium.org/drawings/d/1eGdsIKzJ2dxDDBb
UaIABrN4aMLD1bqJTfyxNGZsTdmg/edit for |
| 344 # a visual representation of this. | 346 # a visual representation of this. |
| 345 # | 347 # |
| 346 # So, we can optimize the virtual path, then the virtual root and then t
he regular path. | 348 # So, we can optimize the virtual path, then the virtual root and then t
he regular path. |
| 347 | 349 |
| 348 self.files_to_delete = [] | 350 self._files_to_delete = [] |
| 349 self.files_to_add = [] | 351 self._files_to_add = [] |
| 350 _log.debug("Optimizing regular fallback path.") | 352 _log.debug("Optimizing regular fallback path.") |
| 351 result = self._optimize_subtree(baseline_name) | 353 result = self._optimize_subtree(baseline_name) |
| 352 non_virtual_baseline_name = self._virtual_base(baseline_name) | 354 non_virtual_baseline_name = self._virtual_base(baseline_name) |
| 353 if not non_virtual_baseline_name: | 355 if not non_virtual_baseline_name: |
| 354 return result, self.files_to_delete, self.files_to_add | 356 return result, self._files_to_delete, self._files_to_add |
| 355 | 357 |
| 356 self._optimize_virtual_root(baseline_name, non_virtual_baseline_name) | 358 self._optimize_virtual_root(baseline_name, non_virtual_baseline_name) |
| 357 | 359 |
| 358 _log.debug("Optimizing non-virtual fallback path.") | 360 _log.debug("Optimizing non-virtual fallback path.") |
| 359 result |= self._optimize_subtree(non_virtual_baseline_name) | 361 result |= self._optimize_subtree(non_virtual_baseline_name) |
| 360 return result, self.files_to_delete, self.files_to_add | 362 return result, self._files_to_delete, self._files_to_add |
| OLD | NEW |