Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(11)

Side by Side Diff: third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py

Issue 2765863003: Refactoring: Replace test_prefix_list variables with TestBaselineSet objects. (Closed)
Patch Set: Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698