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 |