Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2010 Google Inc. All rights reserved. | 1 # Copyright (c) 2010 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 |
| 11 # in the documentation and/or other materials provided with the | 11 # in the documentation and/or other materials provided with the |
| 12 # distribution. | 12 # distribution. |
| 13 # * Neither the name of Google Inc. nor the names of its | 13 # * Neither the name of Google Inc. nor the names of its |
| 14 # contributors may be used to endorse or promote products derived from | 14 # contributors may be used to endorse or promote products derived from |
| 15 # this software without specific prior written permission. | 15 # this software without specific prior written permission. |
| 16 # | 16 # |
| 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 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 from __future__ import print_function | 29 from __future__ import print_function |
| 30 import collections | |
| 30 import json | 31 import json |
| 31 import logging | 32 import logging |
| 32 import optparse | 33 import optparse |
| 33 import re | 34 import re |
| 34 import sys | 35 import sys |
| 35 import traceback | 36 import traceback |
| 36 | 37 |
| 37 from webkitpy.common.memoized import memoized | 38 from webkitpy.common.memoized import memoized |
| 38 from webkitpy.common.net.buildbot import Build | 39 from webkitpy.common.net.buildbot import Build |
| 39 from webkitpy.common.system.executive import ScriptError | 40 from webkitpy.common.system.executive import ScriptError |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 127 | 128 |
| 128 def update(self, other): | 129 def update(self, other): |
| 129 assert isinstance(other, ChangeSet) | 130 assert isinstance(other, ChangeSet) |
| 130 assert isinstance(other.lines_to_remove, dict) | 131 assert isinstance(other.lines_to_remove, dict) |
| 131 for test in other.lines_to_remove: | 132 for test in other.lines_to_remove: |
| 132 if test not in self.lines_to_remove: | 133 if test not in self.lines_to_remove: |
| 133 self.lines_to_remove[test] = [] | 134 self.lines_to_remove[test] = [] |
| 134 self.lines_to_remove[test].extend(other.lines_to_remove[test]) | 135 self.lines_to_remove[test].extend(other.lines_to_remove[test]) |
| 135 | 136 |
| 136 | 137 |
| 138 class TestBaselineSet(object): | |
| 139 """Represents a collection of tests and platforms that can be rebaselined. | |
| 140 | |
| 141 A TestBaselineSet specifies tests to rebaseline along with information | |
| 142 about where to fetch the baselines from. | |
| 143 """ | |
| 144 | |
| 145 def __init__(self, host): | |
| 146 self._host = host | |
| 147 self._test_prefix_map = collections.defaultdict(list) | |
| 148 | |
| 149 def __iter__(self): | |
| 150 return iter(self._iter_combinations()) | |
| 151 | |
| 152 def __bool__(self): | |
| 153 return bool(self._test_prefix_map) | |
| 154 | |
| 155 def _iter_combinations(self): | |
| 156 """Iterates through (test, build) combinations.""" | |
| 157 port = self._host.port_factory.get() | |
| 158 for test_prefix, builds in self._test_prefix_map.iteritems(): | |
| 159 for build in builds: | |
| 160 for test in port.tests([test_prefix]): | |
| 161 yield (test, build) | |
| 162 | |
| 163 def __str__(self): | |
| 164 if not self._test_prefix_map: | |
| 165 return '<Empty TestBaselineSet>' | |
| 166 return '<TestBaselineSet with:\n ' + '\n '.join('%s: %s' % pair for pa ir in self._iter_combinations()) + '>' | |
| 167 | |
| 168 def add(self, test_prefix, build): | |
| 169 """Adds an entry for baselines to download for some set of tests. | |
| 170 | |
| 171 Args: | |
| 172 test_prefix: This can be a full test path, or directory of tests, or a path with globs. | |
| 173 build: A Build object. This specifies where to fetch baselines from. | |
| 174 """ | |
| 175 self._test_prefix_map[test_prefix].append(build) | |
| 176 | |
| 177 def all_builders(self): | |
| 178 """Returns all builder names in in this collection.""" | |
| 179 return sorted({b.builder_name for _, b in self._iter_combinations()}) | |
| 180 | |
| 181 | |
| 137 class CopyExistingBaselinesInternal(AbstractRebaseliningCommand): | 182 class CopyExistingBaselinesInternal(AbstractRebaseliningCommand): |
| 138 name = 'copy-existing-baselines-internal' | 183 name = 'copy-existing-baselines-internal' |
| 139 help_text = ('Copy existing baselines down one level in the baseline order t o ensure ' | 184 help_text = ('Copy existing baselines down one level in the baseline order t o ensure ' |
| 140 "new baselines don't break existing passing platforms.") | 185 "new baselines don't break existing passing platforms.") |
| 141 | 186 |
| 142 def __init__(self): | 187 def __init__(self): |
| 143 super(CopyExistingBaselinesInternal, self).__init__(options=[ | 188 super(CopyExistingBaselinesInternal, self).__init__(options=[ |
| 144 self.results_directory_option, | 189 self.results_directory_option, |
| 145 self.suffixes_option, | 190 self.suffixes_option, |
| 146 self.builder_option, | 191 self.builder_option, |
| (...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 306 release_builders = [] | 351 release_builders = [] |
| 307 for builder_name in self._tool.builders.all_continuous_builder_names(): | 352 for builder_name in self._tool.builders.all_continuous_builder_names(): |
| 308 port = self._tool.port_factory.get_from_builder_name(builder_name) | 353 port = self._tool.port_factory.get_from_builder_name(builder_name) |
| 309 if port.test_configuration().build_type == 'release': | 354 if port.test_configuration().build_type == 'release': |
| 310 release_builders.append(builder_name) | 355 release_builders.append(builder_name) |
| 311 return release_builders | 356 return release_builders |
| 312 | 357 |
| 313 def _run_webkit_patch(self, args, verbose): | 358 def _run_webkit_patch(self, args, verbose): |
| 314 try: | 359 try: |
| 315 verbose_args = ['--verbose'] if verbose else [] | 360 verbose_args = ['--verbose'] if verbose else [] |
| 316 stderr = self._tool.executive.run_command([self._tool.path()] + verb ose_args + | 361 stderr = self._tool.executive.run_command( |
| 317 args, cwd=self._tool.git() .checkout_root, return_stderr=True) | 362 [self._tool.path()] + verbose_args + args, |
| 363 cwd=self._tool.git().checkout_root, | |
| 364 return_stderr=True) | |
| 318 for line in stderr.splitlines(): | 365 for line in stderr.splitlines(): |
| 319 _log.warning(line) | 366 _log.warning(line) |
| 320 except ScriptError: | 367 except ScriptError: |
| 321 traceback.print_exc(file=sys.stderr) | 368 traceback.print_exc(file=sys.stderr) |
| 322 | 369 |
| 323 def _builders_to_fetch_from(self, builders_to_check): | 370 def _builders_to_fetch_from(self, builders_to_check): |
| 324 """Returns the subset of builders that will cover all of the baseline | 371 """Returns the subset of builders that will cover all of the baseline |
| 325 search paths used in the input list. | 372 search paths used in the input list. |
| 326 | 373 |
| 327 In particular, if the input list contains both Release and Debug | 374 In particular, if the input list contains both Release and Debug |
| 328 versions of a configuration, we *only* return the Release version | 375 versions of a configuration, we *only* return the Release version |
| 329 (since we don't save debug versions of baselines). | 376 (since we don't save debug versions of baselines). |
| 330 | 377 |
| 331 Args: | 378 Args: |
| 332 builders_to_check: List of builder names. | 379 builders_to_check: List of builder names. |
| 380 | |
| 381 Returns: | |
| 382 A list of builders that we may fetch from. | |
|
wkorman
2017/03/21 19:02:13
Document that it's returned sorted? Presuming that
qyearsley
2017/03/21 21:47:22
It's not really important that it was sorted; the
| |
| 333 """ | 383 """ |
| 334 release_builders = set() | 384 release_builders = set() |
| 335 debug_builders = set() | 385 debug_builders = set() |
| 336 builders_to_fallback_paths = {} | |
| 337 for builder in builders_to_check: | 386 for builder in builders_to_check: |
| 338 port = self._tool.port_factory.get_from_builder_name(builder) | 387 port = self._tool.port_factory.get_from_builder_name(builder) |
| 339 if port.test_configuration().build_type == 'release': | 388 if port.test_configuration().build_type == 'release': |
| 340 release_builders.add(builder) | 389 release_builders.add(builder) |
| 341 else: | 390 else: |
| 342 debug_builders.add(builder) | 391 debug_builders.add(builder) |
| 392 | |
| 393 builders_to_fallback_paths = {} | |
| 343 for builder in list(release_builders) + list(debug_builders): | 394 for builder in list(release_builders) + list(debug_builders): |
| 344 port = self._tool.port_factory.get_from_builder_name(builder) | 395 port = self._tool.port_factory.get_from_builder_name(builder) |
| 345 fallback_path = port.baseline_search_path() | 396 fallback_path = port.baseline_search_path() |
| 346 if fallback_path not in builders_to_fallback_paths.values(): | 397 if fallback_path not in builders_to_fallback_paths.values(): |
| 347 builders_to_fallback_paths[builder] = fallback_path | 398 builders_to_fallback_paths[builder] = fallback_path |
| 348 return builders_to_fallback_paths.keys() | |
| 349 | 399 |
| 350 @staticmethod | 400 return sorted(builders_to_fallback_paths) |
| 351 def _builder_names(builds): | |
| 352 # TODO(qyearsley): If test_prefix_list dicts are converted to instances | |
| 353 # of some class, then this could be replaced with a method on that clas s. | |
| 354 return [b.builder_name for b in builds] | |
| 355 | 401 |
| 356 def _rebaseline_commands(self, test_prefix_list, options): | 402 def _rebaseline_commands(self, test_baseline_set, options): |
| 357 path_to_webkit_patch = self._tool.path() | 403 path_to_webkit_patch = self._tool.path() |
| 358 cwd = self._tool.git().checkout_root | 404 cwd = self._tool.git().checkout_root |
| 359 copy_baseline_commands = [] | 405 copy_baseline_commands = [] |
| 360 rebaseline_commands = [] | 406 rebaseline_commands = [] |
| 361 lines_to_remove = {} | 407 lines_to_remove = {} |
| 362 port = self._tool.port_factory.get() | |
| 363 | 408 |
| 364 for test_prefix in test_prefix_list: | 409 for test, build in test_baseline_set: |
| 365 for test in port.tests([test_prefix]): | 410 if build.builder_name not in self._builders_to_fetch_from(test_basel ine_set.all_builders()): |
| 366 builders_to_fetch_from = self._builders_to_fetch_from(self._buil der_names(test_prefix_list[test_prefix])) | 411 continue |
| 367 for build in sorted(test_prefix_list[test_prefix]): | |
| 368 builder, build_number = build.builder_name, build.build_numb er | |
| 369 if builder not in builders_to_fetch_from: | |
| 370 break | |
| 371 | 412 |
| 372 suffixes = self._suffixes_for_actual_failures(test, build) | 413 suffixes = self._suffixes_for_actual_failures(test, build) |
| 373 if not suffixes: | 414 if not suffixes: |
| 374 # If we're not going to rebaseline the test because it's passing on this | 415 # If we're not going to rebaseline the test because it's passing on this |
| 375 # builder, we still want to remove the line from TestExp ectations. | 416 # builder, we still want to remove the line from TestExpectation s. |
| 376 if test not in lines_to_remove: | 417 if test not in lines_to_remove: |
| 377 lines_to_remove[test] = [] | 418 lines_to_remove[test] = [] |
| 378 lines_to_remove[test].append(builder) | 419 lines_to_remove[test].append(build.builder_name) |
| 379 continue | 420 continue |
| 380 | 421 |
| 381 args = ['--suffixes', ','.join(suffixes), '--builder', build er, '--test', test] | 422 args = ['--suffixes', ','.join(suffixes), '--builder', build.builder _name, '--test', test] |
| 423 if options.verbose: | |
| 424 args.append('--verbose') | |
| 382 | 425 |
| 383 if options.verbose: | 426 copy_command = [self._tool.executable, path_to_webkit_patch, 'copy-e xisting-baselines-internal'] + args |
| 384 args.append('--verbose') | 427 copy_baseline_commands.append(tuple([copy_command, cwd])) |
| 385 | 428 |
| 386 copy_baseline_commands.append( | 429 if build.build_number: |
| 387 tuple([[self._tool.executable, path_to_webkit_patch, 'co py-existing-baselines-internal'] + args, cwd])) | 430 args.extend(['--build-number', str(build.build_number)]) |
| 431 if options.results_directory: | |
| 432 args.extend(['--results-directory', options.results_directory]) | |
| 388 | 433 |
| 389 if build_number: | 434 rebaseline_command = [self._tool.executable, path_to_webkit_patch, ' rebaseline-test-internal'] + args |
| 390 args.extend(['--build-number', str(build_number)]) | 435 rebaseline_commands.append(tuple([rebaseline_command, cwd])) |
| 391 if options.results_directory: | |
| 392 args.extend(['--results-directory', options.results_dire ctory]) | |
| 393 | 436 |
| 394 rebaseline_commands.append( | |
| 395 tuple([[self._tool.executable, path_to_webkit_patch, 're baseline-test-internal'] + args, cwd])) | |
| 396 return copy_baseline_commands, rebaseline_commands, lines_to_remove | 437 return copy_baseline_commands, rebaseline_commands, lines_to_remove |
| 397 | 438 |
| 398 @staticmethod | 439 @staticmethod |
| 399 def _extract_expectation_line_changes(command_results): | 440 def _extract_expectation_line_changes(command_results): |
| 400 """Parses the JSON lines from sub-command output and returns the result as a ChangeSet.""" | 441 """Parses the JSON lines from sub-command output and returns the result as a ChangeSet.""" |
| 401 change_set = ChangeSet() | 442 change_set = ChangeSet() |
| 402 for _, stdout, _ in command_results: | 443 for _, stdout, _ in command_results: |
| 403 updated = False | 444 updated = False |
| 404 for line in filter(None, stdout.splitlines()): | 445 for line in filter(None, stdout.splitlines()): |
| 405 try: | 446 try: |
| 406 parsed_line = json.loads(line) | 447 parsed_line = json.loads(line) |
| 407 change_set.update(ChangeSet.from_dict(parsed_line)) | 448 change_set.update(ChangeSet.from_dict(parsed_line)) |
| 408 updated = True | 449 updated = True |
| 409 except ValueError: | 450 except ValueError: |
| 410 _log.debug('"%s" is not a JSON object, ignoring', line) | 451 _log.debug('"%s" is not a JSON object, ignoring', line) |
| 411 if not updated: | 452 if not updated: |
| 412 # TODO(qyearsley): This probably should be an error. See http:// crbug.com/649412. | 453 # TODO(qyearsley): This probably should be an error. See http:// crbug.com/649412. |
| 413 _log.debug('Could not add file based off output "%s"', stdout) | 454 _log.debug('Could not add file based off output "%s"', stdout) |
| 414 return change_set | 455 return change_set |
| 415 | 456 |
| 416 def _optimize_baselines(self, test_prefix_list, verbose=False): | 457 def _optimize_baselines(self, test_baseline_set, verbose=False): |
| 458 """Returns a list of commands to run in parallel to de-duplicate baselin es.""" | |
| 459 tests_to_suffixes = collections.defaultdict(set) | |
| 460 for test, build in test_baseline_set: | |
| 461 builders_to_fetch_from = self._builders_to_fetch_from(test_baseline_ set.all_builders()) | |
| 462 if build.builder_name not in builders_to_fetch_from: | |
| 463 continue | |
| 464 tests_to_suffixes[test].update(self._suffixes_for_actual_failures(te st, build)) | |
| 465 | |
| 417 optimize_commands = [] | 466 optimize_commands = [] |
| 418 for test in test_prefix_list: | 467 for test, suffixes in tests_to_suffixes.iteritems(): |
| 419 all_suffixes = set() | |
| 420 builders_to_fetch_from = self._builders_to_fetch_from(self._builder_ names(test_prefix_list[test])) | |
| 421 for build in sorted(test_prefix_list[test]): | |
| 422 if build.builder_name not in builders_to_fetch_from: | |
| 423 break | |
| 424 all_suffixes.update(self._suffixes_for_actual_failures(test, bui ld)) | |
| 425 | |
| 426 # No need to optimize baselines for a test with no failures. | 468 # No need to optimize baselines for a test with no failures. |
| 427 if not all_suffixes: | 469 if not suffixes: |
| 428 continue | 470 continue |
| 429 | |
| 430 # FIXME: We should propagate the platform options as well. | 471 # FIXME: We should propagate the platform options as well. |
| 431 cmd_line = ['--suffixes', ','.join(all_suffixes), test] | 472 args = ['--suffixes', ','.join(suffixes), test] |
| 432 if verbose: | 473 if verbose: |
| 433 cmd_line.append('--verbose') | 474 args.append('--verbose') |
| 434 | |
| 435 path_to_webkit_patch = self._tool.path() | 475 path_to_webkit_patch = self._tool.path() |
| 436 cwd = self._tool.git().checkout_root | 476 cwd = self._tool.git().checkout_root |
| 437 optimize_commands.append(tuple([[self._tool.executable, path_to_webk it_patch, 'optimize-baselines'] + cmd_line, cwd])) | 477 command = [self._tool.executable, path_to_webkit_patch, 'optimize-ba selines'] + args |
| 478 optimize_commands.append(tuple([command, cwd])) | |
| 479 | |
| 438 return optimize_commands | 480 return optimize_commands |
| 439 | 481 |
| 440 def _update_expectations_files(self, lines_to_remove): | 482 def _update_expectations_files(self, lines_to_remove): |
| 441 # FIXME: This routine is way too expensive. We're creating O(n ports) Te stExpectations objects. | 483 # FIXME: This routine is way too expensive. We're creating O(n ports) Te stExpectations objects. |
| 442 # This is slow and uses a lot of memory. | 484 # This is slow and uses a lot of memory. |
| 443 tests = lines_to_remove.keys() | 485 tests = lines_to_remove.keys() |
| 444 to_remove = [] | 486 to_remove = [] |
| 445 | 487 |
| 446 # This is so we remove lines for builders that skip this test, e.g. Andr oid skips most | 488 # This is so we remove lines for builders that skip this test, e.g. Andr oid skips most |
| 447 # tests and we don't want to leave stray [ Android ] lines in TestExpect ations.. | 489 # tests and we don't want to leave stray [ Android ] lines in TestExpect ations.. |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 477 | 519 |
| 478 command_results = self._tool.executive.run_in_parallel(commands) | 520 command_results = self._tool.executive.run_in_parallel(commands) |
| 479 for _, _, stderr in command_results: | 521 for _, _, stderr in command_results: |
| 480 if stderr: | 522 if stderr: |
| 481 _log.error(stderr) | 523 _log.error(stderr) |
| 482 | 524 |
| 483 change_set = self._extract_expectation_line_changes(command_results) | 525 change_set = self._extract_expectation_line_changes(command_results) |
| 484 | 526 |
| 485 return change_set.lines_to_remove | 527 return change_set.lines_to_remove |
| 486 | 528 |
| 487 def rebaseline(self, options, test_prefix_list): | 529 def rebaseline(self, options, test_baseline_set): |
| 488 """Fetches new baselines and removes related test expectation lines. | 530 """Fetches new baselines and removes related test expectation lines. |
| 489 | 531 |
| 490 Args: | 532 Args: |
| 491 options: An object with the command line options. | 533 options: An object with the command line options. |
| 492 test_prefix_list: A dict of test names to Build objects for new | 534 test_baseline_set: A TestRebaselineSet instance, which represents |
| 493 baselines. For example: | 535 a set of tests/platform combinations to rebaseline. |
|
wkorman
2017/03/21 19:02:13
TestRebaselineSet -> TestBaselineSet
qyearsley
2017/03/21 21:47:22
Fixed
| |
| 494 { | |
| 495 "some/test.html": [Build("builder-1", 412), | |
| 496 Build("builder-2", 100)]}, | |
| 497 "some/other.html": [Build("builder-1", 412)} | |
| 498 } | |
| 499 This would mean that new text baselines should be | |
| 500 downloaded for "some/test.html" on both builder-1 | |
| 501 (build 412) and builder-2 (build 100), and new text | |
| 502 baselines should be downloaded for "some/other.html" | |
| 503 but only from builder-1. | |
| 504 TODO(qyearsley): Replace test_prefix_list everywhere | |
| 505 with some sort of class that contains the same data. | |
| 506 """ | 536 """ |
| 507 if self._tool.git().has_working_directory_changes(pathspec=self._layout_ tests_dir()): | 537 if self._tool.git().has_working_directory_changes(pathspec=self._layout_ tests_dir()): |
| 508 _log.error('There are uncommitted changes in the layout tests direct ory; aborting.') | 538 _log.error('There are uncommitted changes in the layout tests direct ory; aborting.') |
| 509 return | 539 return |
| 510 | 540 |
| 511 for test, builds_to_check in sorted(test_prefix_list.items()): | 541 for test in sorted({t for t, _ in test_baseline_set}): |
| 512 _log.info('Rebaselining %s', test) | 542 _log.info('Rebaselining %s', test) |
| 513 for build in sorted(builds_to_check): | |
| 514 _log.debug(' %s', build) | |
| 515 | 543 |
| 516 copy_baseline_commands, rebaseline_commands, extra_lines_to_remove = sel f._rebaseline_commands( | 544 copy_baseline_commands, rebaseline_commands, extra_lines_to_remove = sel f._rebaseline_commands( |
| 517 test_prefix_list, options) | 545 test_baseline_set, options) |
| 518 lines_to_remove = {} | 546 lines_to_remove = {} |
| 519 | 547 |
| 520 self._run_in_parallel(copy_baseline_commands) | 548 self._run_in_parallel(copy_baseline_commands) |
| 521 lines_to_remove = self._run_in_parallel(rebaseline_commands) | 549 lines_to_remove = self._run_in_parallel(rebaseline_commands) |
| 522 | 550 |
| 523 for test in extra_lines_to_remove: | 551 for test in extra_lines_to_remove: |
| 524 if test in lines_to_remove: | 552 if test in lines_to_remove: |
| 525 lines_to_remove[test] = lines_to_remove[test] + extra_lines_to_r emove[test] | 553 lines_to_remove[test] = lines_to_remove[test] + extra_lines_to_r emove[test] |
| 526 else: | 554 else: |
| 527 lines_to_remove[test] = extra_lines_to_remove[test] | 555 lines_to_remove[test] = extra_lines_to_remove[test] |
| 528 | 556 |
| 529 if lines_to_remove: | 557 if lines_to_remove: |
| 530 self._update_expectations_files(lines_to_remove) | 558 self._update_expectations_files(lines_to_remove) |
| 531 | 559 |
| 532 if options.optimize: | 560 if options.optimize: |
| 533 # TODO(wkorman): Consider changing temporary branch to base off of H EAD rather than | 561 # TODO(wkorman): Consider changing temporary branch to base off of H EAD rather than |
| 534 # origin/master to ensure we run baseline optimization processes wit h the same code as | 562 # origin/master to ensure we run baseline optimization processes wit h the same code as |
| 535 # auto-rebaseline itself. | 563 # auto-rebaseline itself. |
| 536 self._run_in_parallel(self._optimize_baselines(test_prefix_list, opt ions.verbose)) | 564 self._run_in_parallel(self._optimize_baselines(test_baseline_set, op tions.verbose)) |
| 537 | 565 |
| 538 self._remove_all_pass_testharness_baselines(test_prefix_list) | 566 self._remove_all_pass_testharness_baselines(test_baseline_set) |
| 539 | 567 |
| 540 self._tool.git().add_list(self.unstaged_baselines()) | 568 self._tool.git().add_list(self.unstaged_baselines()) |
| 541 | 569 |
| 542 def unstaged_baselines(self): | 570 def unstaged_baselines(self): |
| 543 """Returns absolute paths for unstaged (including untracked) baselines." "" | 571 """Returns absolute paths for unstaged (including untracked) baselines." "" |
| 544 baseline_re = re.compile(r'.*[\\/]LayoutTests[\\/].*-expected\.(txt|png| wav)$') | 572 baseline_re = re.compile(r'.*[\\/]LayoutTests[\\/].*-expected\.(txt|png| wav)$') |
| 545 unstaged_changes = self._tool.git().unstaged_changes() | 573 unstaged_changes = self._tool.git().unstaged_changes() |
| 546 return sorted(self._tool.git().absolute_path(path) for path in unstaged_ changes if re.match(baseline_re, path)) | 574 return sorted(self._tool.git().absolute_path(path) for path in unstaged_ changes if re.match(baseline_re, path)) |
| 547 | 575 |
| 548 def _remove_all_pass_testharness_baselines(self, test_prefix_list): | 576 def _remove_all_pass_testharness_baselines(self, test_baseline_set): |
| 549 """Removes all of the all-PASS baselines for the given builders and test s. | 577 """Removes all of the all-PASS baselines for the given builders and test s. |
| 550 | 578 |
| 551 In general, for testharness.js tests, the absence of a baseline | 579 In general, for testharness.js tests, the absence of a baseline |
| 552 indicates that the test is expected to pass. When rebaselining, | 580 indicates that the test is expected to pass. When rebaselining, |
| 553 new all-PASS baselines may be downloaded, but they should not be kept. | 581 new all-PASS baselines may be downloaded, but they should not be kept. |
| 554 """ | 582 """ |
| 555 filesystem = self._tool.filesystem | 583 filesystem = self._tool.filesystem |
| 556 baseline_paths = self._all_baseline_paths(test_prefix_list) | 584 baseline_paths = self._possible_baseline_paths(test_baseline_set) |
| 557 for path in baseline_paths: | 585 for path in baseline_paths: |
| 558 if not (filesystem.exists(path) and | 586 if not (filesystem.exists(path) and filesystem.splitext(path)[1] == '.txt'): |
| 559 filesystem.splitext(path)[1] == '.txt'): | |
| 560 continue | 587 continue |
| 561 contents = filesystem.read_text_file(path) | 588 contents = filesystem.read_text_file(path) |
| 562 if is_all_pass_testharness_result(contents): | 589 if is_all_pass_testharness_result(contents): |
| 563 _log.info('Removing all-PASS testharness baseline: %s', path) | 590 _log.info('Removing all-PASS testharness baseline: %s', path) |
| 564 filesystem.remove(path) | 591 filesystem.remove(path) |
| 565 | 592 |
| 566 def _all_baseline_paths(self, test_prefix_list): | 593 def _possible_baseline_paths(self, test_baseline_set): |
| 567 """Returns file paths for all baselines for the given tests and builders . | 594 """Returns file paths for all baselines for the given tests and builders . |
| 568 | 595 |
| 569 Args: | 596 Args: |
| 570 test_prefix_list: A dict mapping test prefixes, which could be | 597 test_baseline_set: A TestBaselineSet instance. |
| 571 directories or full test paths, to Builds. | |
| 572 TODO(qyearsley): If a class is added to replace | |
| 573 test_prefix_list, then this can be made a method on | |
| 574 that class. | |
| 575 | 598 |
| 576 Returns: | 599 Returns: |
| 577 A list of absolute paths to possible baseline files, | 600 A list of absolute paths where baselines could possibly exist. |
| 578 which may or may not exist on the local filesystem. | |
| 579 """ | 601 """ |
| 580 filesystem = self._tool.filesystem | 602 filesystem = self._tool.filesystem |
| 581 baseline_paths = [] | 603 baseline_paths = set() |
| 582 port = self._tool.port_factory.get() | 604 for test, build in test_baseline_set: |
| 583 | 605 filenames = [self._file_name_for_expected_result(test, suffix) for s uffix in BASELINE_SUFFIX_LIST] |
| 584 for test_prefix in test_prefix_list: | 606 port_baseline_dir = self._baseline_directory(build.builder_name) |
| 585 tests = port.tests([test_prefix]) | 607 baseline_paths.update({filesystem.join(port_baseline_dir, filename) for filename in filenames}) |
| 586 | 608 baseline_paths.update({filesystem.join(self._layout_tests_dir(), fil ename) for filename in filenames}) |
| 587 for build in test_prefix_list[test_prefix]: | |
| 588 port_baseline_dir = self._baseline_directory(build.builder_name) | |
| 589 baseline_paths.extend([ | |
| 590 filesystem.join(port_baseline_dir, self._file_name_for_expec ted_result(test, suffix)) | |
| 591 for test in tests for suffix in BASELINE_SUFFIX_LIST | |
| 592 ]) | |
| 593 | |
| 594 baseline_paths.extend([ | |
| 595 filesystem.join(self._layout_tests_dir(), self._file_name_for_ex pected_result(test, suffix)) | |
| 596 for test in tests for suffix in BASELINE_SUFFIX_LIST | |
| 597 ]) | |
| 598 | |
| 599 return sorted(baseline_paths) | 609 return sorted(baseline_paths) |
| 600 | 610 |
| 601 def _layout_tests_dir(self): | 611 def _layout_tests_dir(self): |
| 602 return self._tool.port_factory.get().layout_tests_dir() | 612 return self._tool.port_factory.get().layout_tests_dir() |
| 603 | 613 |
| 604 def _suffixes_for_actual_failures(self, test, build): | 614 def _suffixes_for_actual_failures(self, test, build): |
| 605 """Gets the baseline suffixes for actual mismatch failures in some resul ts. | 615 """Gets the baseline suffixes for actual mismatch failures in some resul ts. |
| 606 | 616 |
| 607 Args: | 617 Args: |
| 608 test: A full test path string. | 618 test: A full test path string. |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 639 | 649 |
| 640 class RebaselineExpectations(AbstractParallelRebaselineCommand): | 650 class RebaselineExpectations(AbstractParallelRebaselineCommand): |
| 641 name = 'rebaseline-expectations' | 651 name = 'rebaseline-expectations' |
| 642 help_text = 'Rebaselines the tests indicated in TestExpectations.' | 652 help_text = 'Rebaselines the tests indicated in TestExpectations.' |
| 643 show_in_main_help = True | 653 show_in_main_help = True |
| 644 | 654 |
| 645 def __init__(self): | 655 def __init__(self): |
| 646 super(RebaselineExpectations, self).__init__(options=[ | 656 super(RebaselineExpectations, self).__init__(options=[ |
| 647 self.no_optimize_option, | 657 self.no_optimize_option, |
| 648 ] + self.platform_options) | 658 ] + self.platform_options) |
| 649 self._test_prefix_list = None | 659 self._test_baseline_set = None |
| 650 | 660 |
| 651 @staticmethod | 661 @staticmethod |
| 652 def _tests_to_rebaseline(port): | 662 def _tests_to_rebaseline(port): |
| 653 tests_to_rebaseline = [] | 663 tests_to_rebaseline = [] |
| 654 for path, value in port.expectations_dict().items(): | 664 for path, value in port.expectations_dict().items(): |
| 655 expectations = TestExpectations(port, include_overrides=False, expec tations_dict={path: value}) | 665 expectations = TestExpectations(port, include_overrides=False, expec tations_dict={path: value}) |
| 656 for test in expectations.get_rebaselining_failures(): | 666 for test in expectations.get_rebaselining_failures(): |
| 657 tests_to_rebaseline.append(test) | 667 tests_to_rebaseline.append(test) |
| 658 return tests_to_rebaseline | 668 return tests_to_rebaseline |
| 659 | 669 |
| 660 def _add_tests_to_rebaseline(self, port_name): | 670 def _add_tests_to_rebaseline(self, port_name): |
| 661 builder_name = self._tool.builders.builder_name_for_port_name(port_name) | 671 builder_name = self._tool.builders.builder_name_for_port_name(port_name) |
| 662 if not builder_name: | 672 if not builder_name: |
| 663 return | 673 return |
| 664 tests = self._tests_to_rebaseline(self._tool.port_factory.get(port_name) ) | 674 tests = self._tests_to_rebaseline(self._tool.port_factory.get(port_name) ) |
| 665 | 675 |
| 666 if tests: | 676 if tests: |
| 667 _log.info('Retrieving results for %s from %s.', port_name, builder_n ame) | 677 _log.info('Retrieving results for %s from %s.', port_name, builder_n ame) |
| 668 | 678 |
| 669 for test_name in tests: | 679 for test_name in tests: |
| 670 _log.info(' %s', test_name) | 680 _log.info(' %s', test_name) |
| 671 if test_name not in self._test_prefix_list: | 681 self._test_baseline_set.add(test_name, Build(builder_name)) |
| 672 self._test_prefix_list[test_name] = [] | |
| 673 self._test_prefix_list[test_name].append(Build(builder_name)) | |
| 674 | 682 |
| 675 def execute(self, options, args, tool): | 683 def execute(self, options, args, tool): |
| 676 self._tool = tool | 684 self._tool = tool |
| 685 self._test_baseline_set = TestBaselineSet(tool) | |
| 677 options.results_directory = None | 686 options.results_directory = None |
| 678 self._test_prefix_list = {} | |
| 679 port_names = tool.port_factory.all_port_names(options.platform) | 687 port_names = tool.port_factory.all_port_names(options.platform) |
| 680 for port_name in port_names: | 688 for port_name in port_names: |
| 681 self._add_tests_to_rebaseline(port_name) | 689 self._add_tests_to_rebaseline(port_name) |
| 682 if not self._test_prefix_list: | 690 if not self._test_baseline_set: |
| 683 _log.warning('Did not find any tests marked Rebaseline.') | 691 _log.warning('Did not find any tests marked Rebaseline.') |
| 684 return | 692 return |
| 685 | 693 |
| 686 self.rebaseline(options, self._test_prefix_list) | 694 self.rebaseline(options, self._test_baseline_set) |
| 687 | 695 |
| 688 | 696 |
| 689 class Rebaseline(AbstractParallelRebaselineCommand): | 697 class Rebaseline(AbstractParallelRebaselineCommand): |
| 690 name = 'rebaseline' | 698 name = 'rebaseline' |
| 691 help_text = 'Rebaseline tests with results from the build bots.' | 699 help_text = 'Rebaseline tests with results from the continuous builders.' |
| 692 show_in_main_help = True | 700 show_in_main_help = True |
| 693 argument_names = '[TEST_NAMES]' | 701 argument_names = '[TEST_NAMES]' |
| 694 | 702 |
| 695 def __init__(self): | 703 def __init__(self): |
| 696 super(Rebaseline, self).__init__(options=[ | 704 super(Rebaseline, self).__init__(options=[ |
| 697 self.no_optimize_option, | 705 self.no_optimize_option, |
| 698 # FIXME: should we support the platform options in addition to (or i nstead of) --builders? | 706 # FIXME: should we support the platform options in addition to (or i nstead of) --builders? |
| 699 self.results_directory_option, | 707 self.results_directory_option, |
| 700 optparse.make_option('--builders', default=None, action='append', | 708 optparse.make_option('--builders', default=None, action='append', |
| 701 help=('Comma-separated-list of builders to pull new baselines from ' | 709 help=('Comma-separated-list of builders to pull new baselines from ' |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 712 _log.error('Must list tests to rebaseline.') | 720 _log.error('Must list tests to rebaseline.') |
| 713 return | 721 return |
| 714 | 722 |
| 715 if options.builders: | 723 if options.builders: |
| 716 builders_to_check = [] | 724 builders_to_check = [] |
| 717 for builder_names in options.builders: | 725 for builder_names in options.builders: |
| 718 builders_to_check += builder_names.split(',') | 726 builders_to_check += builder_names.split(',') |
| 719 else: | 727 else: |
| 720 builders_to_check = self._builders_to_pull_from() | 728 builders_to_check = self._builders_to_pull_from() |
| 721 | 729 |
| 722 test_prefix_list = {} | 730 test_baseline_set = TestBaselineSet(tool) |
| 723 | 731 |
| 724 for builder in builders_to_check: | 732 for builder in builders_to_check: |
| 725 for test in args: | 733 for test_prefix in args: |
| 726 if test not in test_prefix_list: | 734 test_baseline_set.add(test_prefix, Build(builder)) |
| 727 test_prefix_list[test] = [] | |
| 728 test_prefix_list[test].append(Build(builder)) | |
| 729 | 735 |
| 730 if options.verbose: | 736 _log.debug('Rebaselining: %s', test_baseline_set) |
| 731 _log.debug('rebaseline-json: ' + str(test_prefix_list)) | |
| 732 | 737 |
| 733 self.rebaseline(options, test_prefix_list) | 738 self.rebaseline(options, test_baseline_set) |
| OLD | NEW |