OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Rebaselining tool that automatically produces baselines for all platforms. | 6 """Rebaselining tool that automatically produces baselines for all platforms. |
7 | 7 |
8 The script does the following for each platform specified: | 8 The script does the following for each platform specified: |
9 1. Compile a list of tests that need rebaselining. | 9 1. Compile a list of tests that need rebaselining. |
10 2. Download test result archive from buildbot for the platform. | 10 2. Download test result archive from buildbot for the platform. |
(...skipping 17 matching lines...) Expand all Loading... |
28 import time | 28 import time |
29 import urllib | 29 import urllib |
30 import webbrowser | 30 import webbrowser |
31 import zipfile | 31 import zipfile |
32 | 32 |
33 from layout_package import path_utils | 33 from layout_package import path_utils |
34 from layout_package import test_expectations | 34 from layout_package import test_expectations |
35 from test_types import image_diff | 35 from test_types import image_diff |
36 from test_types import text_diff | 36 from test_types import text_diff |
37 | 37 |
| 38 # Repository type constants. |
| 39 REPO_SVN, REPO_UNKNOWN = range(2) |
| 40 |
38 BASELINE_SUFFIXES = ['.txt', '.png', '.checksum'] | 41 BASELINE_SUFFIXES = ['.txt', '.png', '.checksum'] |
39 REBASELINE_PLATFORM_ORDER = ['mac', 'win', 'win-xp', 'linux'] | 42 REBASELINE_PLATFORM_ORDER = ['mac', 'win', 'win-xp', 'win-vista', 'linux'] |
40 ARCHIVE_DIR_NAME_DICT = {'win': 'webkit-rel', | 43 ARCHIVE_DIR_NAME_DICT = {'win': 'webkit-rel', |
41 'win-vista': 'webkit-dbg-vista', | 44 'win-vista': 'webkit-dbg-vista', |
42 'win-xp': 'webkit-rel', | 45 'win-xp': 'webkit-rel', |
43 'mac': 'webkit-rel-mac5', | 46 'mac': 'webkit-rel-mac5', |
44 'linux': 'webkit-rel-linux', | 47 'linux': 'webkit-rel-linux', |
45 'win-canary': 'webkit-rel-webkit-org', | 48 'win-canary': 'webkit-rel-webkit-org', |
46 'win-vista-canary': 'webkit-dbg-vista', | 49 'win-vista-canary': 'webkit-dbg-vista', |
47 'win-xp-canary': 'webkit-rel-webkit-org', | 50 'win-xp-canary': 'webkit-rel-webkit-org', |
48 'mac-canary': 'webkit-rel-mac-webkit-org', | 51 'mac-canary': 'webkit-rel-mac-webkit-org', |
49 'linux-canary': 'webkit-rel-linux-webkit-org'} | 52 'linux-canary': 'webkit-rel-linux-webkit-org'} |
50 | 53 |
51 def RunShell(command, print_output=False): | 54 def RunShellWithReturnCode(command, print_output=False): |
52 """Executes a command and returns the output. | 55 """Executes a command and returns the output and process return code. |
53 | 56 |
54 Args: | 57 Args: |
55 command: program and arguments. | 58 command: program and arguments. |
56 print_output: if true, print the command results to standard output. | 59 print_output: if true, print the command results to standard output. |
57 | 60 |
58 Returns: | 61 Returns: |
59 command output | 62 command output, return code |
60 """ | 63 """ |
61 | 64 |
62 # Use a shell for subcommands on Windows to get a PATH search. | 65 # Use a shell for subcommands on Windows to get a PATH search. |
63 use_shell = sys.platform.startswith('win') | 66 use_shell = sys.platform.startswith('win') |
64 p = subprocess.Popen(command, stdout=subprocess.PIPE, | 67 p = subprocess.Popen(command, stdout=subprocess.PIPE, |
65 stderr=subprocess.STDOUT, shell=use_shell) | 68 stderr=subprocess.STDOUT, shell=use_shell) |
66 if print_output: | 69 if print_output: |
67 output_array = [] | 70 output_array = [] |
68 while True: | 71 while True: |
69 line = p.stdout.readline() | 72 line = p.stdout.readline() |
70 if not line: | 73 if not line: |
71 break | 74 break |
72 if print_output: | 75 if print_output: |
73 print line.strip('\n') | 76 print line.strip('\n') |
74 output_array.append(line) | 77 output_array.append(line) |
75 output = ''.join(output_array) | 78 output = ''.join(output_array) |
76 else: | 79 else: |
77 output = p.stdout.read() | 80 output = p.stdout.read() |
78 p.wait() | 81 p.wait() |
79 p.stdout.close() | 82 p.stdout.close() |
| 83 |
| 84 return output, p.returncode |
| 85 |
| 86 def RunShell(command, print_output=False): |
| 87 """Executes a command and returns the output. |
| 88 |
| 89 Args: |
| 90 command: program and arguments. |
| 91 print_output: if true, print the command results to standard output. |
| 92 |
| 93 Returns: |
| 94 command output |
| 95 """ |
| 96 |
| 97 output, return_code = RunShellWithReturnCode(command, print_output) |
80 return output | 98 return output |
81 | 99 |
82 | |
83 def LogDashedString(text, platform, logging_level=logging.INFO): | 100 def LogDashedString(text, platform, logging_level=logging.INFO): |
84 """Log text message with dashes on both sides.""" | 101 """Log text message with dashes on both sides.""" |
85 | 102 |
86 msg = text | 103 msg = text |
87 if platform: | 104 if platform: |
88 msg += ': ' + platform | 105 msg += ': ' + platform |
89 if len(msg) < 78: | 106 if len(msg) < 78: |
90 dashes = '-' * ((78 - len(msg)) / 2) | 107 dashes = '-' * ((78 - len(msg)) / 2) |
91 msg = '%s %s %s' % (dashes, msg, dashes) | 108 msg = '%s %s %s' % (dashes, msg, dashes) |
92 | 109 |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
165 | 182 |
166 # Create tests and expectations helper which is used to: | 183 # Create tests and expectations helper which is used to: |
167 # -. compile list of tests that need rebaselining. | 184 # -. compile list of tests that need rebaselining. |
168 # -. update the tests in test_expectations file after rebaseline is done. | 185 # -. update the tests in test_expectations file after rebaseline is done. |
169 self._test_expectations = test_expectations.TestExpectations(None, | 186 self._test_expectations = test_expectations.TestExpectations(None, |
170 self._file_dir, | 187 self._file_dir, |
171 platform, | 188 platform, |
172 False, | 189 False, |
173 False) | 190 False) |
174 | 191 |
| 192 self._repo_type = self._GetRepoType() |
| 193 |
175 def Run(self, backup): | 194 def Run(self, backup): |
176 """Run rebaseline process.""" | 195 """Run rebaseline process.""" |
177 | 196 |
178 LogDashedString('Compiling rebaselining tests', self._platform) | 197 LogDashedString('Compiling rebaselining tests', self._platform) |
179 if not self._CompileRebaseliningTests(): | 198 if not self._CompileRebaseliningTests(): |
180 return True | 199 return True |
181 | 200 |
182 LogDashedString('Downloading archive', self._platform) | 201 LogDashedString('Downloading archive', self._platform) |
183 archive_file = self._DownloadBuildBotArchive() | 202 archive_file = self._DownloadBuildBotArchive() |
184 logging.info('') | 203 logging.info('') |
(...skipping 19 matching lines...) Expand all Loading... |
204 return False | 223 return False |
205 | 224 |
206 logging.warning('All tests needing rebaselining were successfully ' | 225 logging.warning('All tests needing rebaselining were successfully ' |
207 'rebaselined.') | 226 'rebaselined.') |
208 | 227 |
209 return True | 228 return True |
210 | 229 |
211 def GetRebaseliningTests(self): | 230 def GetRebaseliningTests(self): |
212 return self._rebaselining_tests | 231 return self._rebaselining_tests |
213 | 232 |
| 233 def _GetRepoType(self): |
| 234 """Get the repository type that client is using.""" |
| 235 |
| 236 output, return_code = RunShellWithReturnCode(['svn', 'info'], False) |
| 237 if return_code == 0: |
| 238 return REPO_SVN |
| 239 |
| 240 return REPO_UNKNOWN |
| 241 |
214 def _CompileRebaseliningTests(self): | 242 def _CompileRebaseliningTests(self): |
215 """Compile list of tests that need rebaselining for the platform. | 243 """Compile list of tests that need rebaselining for the platform. |
216 | 244 |
217 Returns: | 245 Returns: |
218 List of tests that need rebaselining or | 246 List of tests that need rebaselining or |
219 None if there is no such test. | 247 None if there is no such test. |
220 """ | 248 """ |
221 | 249 |
222 self._rebaselining_tests = self._test_expectations.GetRebaseliningFailures() | 250 self._rebaselining_tests = self._test_expectations.GetRebaseliningFailures() |
223 if not self._rebaselining_tests: | 251 if not self._rebaselining_tests: |
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
354 test_basename, | 382 test_basename, |
355 suffix) | 383 suffix) |
356 logging.debug(' Archive test file name: "%s"', archive_test_name) | 384 logging.debug(' Archive test file name: "%s"', archive_test_name) |
357 if not archive_test_name in zip_namelist: | 385 if not archive_test_name in zip_namelist: |
358 logging.info(' %s file not in archive.', suffix) | 386 logging.info(' %s file not in archive.', suffix) |
359 continue | 387 continue |
360 | 388 |
361 found = True | 389 found = True |
362 logging.info(' %s file found in archive.', suffix) | 390 logging.info(' %s file found in archive.', suffix) |
363 | 391 |
| 392 # Extract new baseline from archive and save it to a temp file. |
| 393 data = zip_file.read(archive_test_name) |
| 394 temp_fd, temp_name = tempfile.mkstemp(suffix) |
| 395 f = os.fdopen(temp_fd, 'wb') |
| 396 f.write(data) |
| 397 f.close() |
| 398 |
364 expected_filename = '%s-expected%s' % (test_basename, suffix) | 399 expected_filename = '%s-expected%s' % (test_basename, suffix) |
365 expected_fullpath = os.path.join( | 400 expected_fullpath = os.path.join( |
366 path_utils.ChromiumBaselinePath(platform), expected_filename) | 401 path_utils.ChromiumBaselinePath(platform), expected_filename) |
367 expected_fullpath = os.path.normpath(expected_fullpath) | 402 expected_fullpath = os.path.normpath(expected_fullpath) |
368 logging.debug(' Expected file full path: "%s"', expected_fullpath) | 403 logging.debug(' Expected file full path: "%s"', expected_fullpath) |
369 | 404 |
370 data = zip_file.read(archive_test_name) | 405 # TODO(victorw): for now, the rebaselining tool checks whether |
| 406 # or not THIS baseline is duplicate and should be skipped. |
| 407 # We could improve the tool to check all baselines in upper and lower |
| 408 # levels and remove all duplicated baselines. |
| 409 if self._IsDupBaseline(temp_name, |
| 410 expected_fullpath, |
| 411 test, |
| 412 suffix, |
| 413 self._platform): |
| 414 os.remove(temp_name) |
| 415 self._DeleteBaseline(expected_fullpath) |
| 416 continue |
371 | 417 |
372 # Create the new baseline directory if it doesn't already exist. | 418 # Create the new baseline directory if it doesn't already exist. |
373 path_utils.MaybeMakeDirectory(os.path.dirname(expected_fullpath)) | 419 path_utils.MaybeMakeDirectory(os.path.dirname(expected_fullpath)) |
374 | 420 |
375 f = open(expected_fullpath, 'wb') | 421 shutil.move(temp_name, expected_fullpath) |
376 f.write(data) | |
377 f.close() | |
378 | |
379 # TODO(victorw): for now, the rebaselining tool checks whether | |
380 # or not THIS baseline is duplicate and should be skipped. | |
381 # We could improve the tool to check all baselines in upper and lower | |
382 # levels and remove all duplicated baselines. | |
383 if self._IsDupBaseline(expected_fullpath, test, suffix, self._platform): | |
384 # Clean up the duplicate baseline. | |
385 self._DeleteBaseline(expected_fullpath) | |
386 continue | |
387 | 422 |
388 if not self._SvnAdd(expected_fullpath): | 423 if not self._SvnAdd(expected_fullpath): |
389 svn_error = True | 424 svn_error = True |
390 elif suffix != '.checksum': | 425 elif suffix != '.checksum': |
391 self._CreateHtmlBaselineFiles(expected_fullpath) | 426 self._CreateHtmlBaselineFiles(expected_fullpath) |
392 | 427 |
393 if not found: | 428 if not found: |
394 logging.warn(' No new baselines found in archive.') | 429 logging.warn(' No new baselines found in archive.') |
395 else: | 430 else: |
396 if svn_error: | 431 if svn_error: |
397 logging.warn(' Failed to add baselines to SVN.') | 432 logging.warn(' Failed to add baselines to SVN.') |
398 else: | 433 else: |
399 logging.info(' Rebaseline succeeded.') | 434 logging.info(' Rebaseline succeeded.') |
400 self._rebaselined_tests.append(test) | 435 self._rebaselined_tests.append(test) |
401 | 436 |
402 test_no += 1 | 437 test_no += 1 |
403 | 438 |
404 zip_file.close() | 439 zip_file.close() |
405 os.remove(archive_file) | 440 os.remove(archive_file) |
406 | 441 |
407 return self._rebaselined_tests | 442 return self._rebaselined_tests |
408 | 443 |
409 def _IsDupBaseline(self, baseline_path, test, suffix, platform): | 444 def _IsDupBaseline(self, new_baseline, baseline_path, test, suffix, platform): |
410 """Check whether a baseline is duplicate and can fallback to same | 445 """Check whether a baseline is duplicate and can fallback to same |
411 baseline for another platform. For example, if a test has same baseline | 446 baseline for another platform. For example, if a test has same baseline |
412 on linux and windows, then we only store windows baseline and linux | 447 on linux and windows, then we only store windows baseline and linux |
413 baseline will fallback to the windows version. | 448 baseline will fallback to the windows version. |
414 | 449 |
415 Args: | 450 Args: |
416 expected_filename: baseline expectation file name. | 451 expected_filename: baseline expectation file name. |
417 test: test name. | 452 test: test name. |
418 suffix: file suffix of the expected results, including dot; e.g. '.txt' | 453 suffix: file suffix of the expected results, including dot; e.g. '.txt' |
419 or '.png'. | 454 or '.png'. |
420 platform: baseline platform 'mac', 'win' or 'linux'. | 455 platform: baseline platform 'mac', 'win' or 'linux'. |
421 | 456 |
422 Returns: | 457 Returns: |
423 True if the baseline is unnecessary. | 458 True if the baseline is unnecessary. |
424 False otherwise. | 459 False otherwise. |
425 """ | 460 """ |
426 test_filepath = os.path.join(path_utils.LayoutTestsDir(), test) | 461 test_filepath = os.path.join(path_utils.LayoutTestsDir(), test) |
427 all_baselines = path_utils.ExpectedBaseline(test_filepath, | 462 all_baselines = path_utils.ExpectedBaseline(test_filepath, |
428 suffix, | 463 suffix, |
429 platform, | 464 platform, |
430 True) | 465 True) |
431 for (fallback_dir, fallback_file) in all_baselines: | 466 for (fallback_dir, fallback_file) in all_baselines: |
432 if fallback_dir and fallback_file: | 467 if fallback_dir and fallback_file: |
433 fallback_fullpath = os.path.normpath( | 468 fallback_fullpath = os.path.normpath( |
434 os.path.join(fallback_dir, fallback_file)) | 469 os.path.join(fallback_dir, fallback_file)) |
435 if fallback_fullpath.lower() != baseline_path.lower(): | 470 if fallback_fullpath.lower() != baseline_path.lower(): |
436 if not self._DiffBaselines(baseline_path, fallback_fullpath): | 471 if not self._DiffBaselines(new_baseline, fallback_fullpath): |
437 logging.info(' Found same baseline at %s', fallback_fullpath) | 472 logging.info(' Found same baseline at %s', fallback_fullpath) |
438 return True | 473 return True |
439 else: | 474 else: |
440 return False | 475 return False |
441 | 476 |
442 return False | 477 return False |
443 | 478 |
444 def _DiffBaselines(self, file1, file2): | 479 def _DiffBaselines(self, file1, file2): |
445 """Check whether two baselines are different. | 480 """Check whether two baselines are different. |
446 | 481 |
(...skipping 13 matching lines...) Expand all Loading... |
460 return True | 495 return True |
461 | 496 |
462 if ext1 == '.PNG': | 497 if ext1 == '.PNG': |
463 return image_diff.ImageDiff(self._platform, '').DiffFiles(file1, | 498 return image_diff.ImageDiff(self._platform, '').DiffFiles(file1, |
464 file2) | 499 file2) |
465 else: | 500 else: |
466 return text_diff.TestTextDiff(self._platform, '').DiffFiles(file1, | 501 return text_diff.TestTextDiff(self._platform, '').DiffFiles(file1, |
467 file2) | 502 file2) |
468 | 503 |
469 def _DeleteBaseline(self, filename): | 504 def _DeleteBaseline(self, filename): |
470 """Remove the file from SVN repository and delete it from disk. | 505 """Remove the file from repository and delete it from disk. |
471 | 506 |
472 Args: | 507 Args: |
473 filename: full path of the file to delete. | 508 filename: full path of the file to delete. |
474 """ | 509 """ |
475 | 510 |
476 if not filename: | 511 if not filename or not os.path.isfile(filename): |
477 return | 512 return |
478 | 513 |
479 parent_dir, basename = os.path.split(filename) | 514 if self._repo_type == REPO_SVN: |
480 original_dir = os.getcwd() | 515 parent_dir, basename = os.path.split(filename) |
481 os.chdir(parent_dir) | 516 original_dir = os.getcwd() |
482 status_output = RunShell(['svn', 'delete', '--force', basename], False) | 517 os.chdir(parent_dir) |
483 os.chdir(original_dir) | 518 RunShell(['svn', 'delete', '--force', basename], False) |
| 519 os.chdir(original_dir) |
| 520 else: |
| 521 os.remove(filename) |
484 | 522 |
485 def _UpdateRebaselinedTestsInFile(self, backup): | 523 def _UpdateRebaselinedTestsInFile(self, backup): |
486 """Update the rebaselined tests in test expectations file. | 524 """Update the rebaselined tests in test expectations file. |
487 | 525 |
488 Args: | 526 Args: |
489 backup: if True, backup the original test expectations file. | 527 backup: if True, backup the original test expectations file. |
490 | 528 |
491 Returns: | 529 Returns: |
492 no | 530 no |
493 """ | 531 """ |
(...skipping 13 matching lines...) Expand all Loading... |
507 | 545 |
508 Returns: | 546 Returns: |
509 True if the file already exists in SVN or is sucessfully added to SVN. | 547 True if the file already exists in SVN or is sucessfully added to SVN. |
510 False otherwise. | 548 False otherwise. |
511 """ | 549 """ |
512 | 550 |
513 if not filename: | 551 if not filename: |
514 return False | 552 return False |
515 | 553 |
516 parent_dir, basename = os.path.split(filename) | 554 parent_dir, basename = os.path.split(filename) |
517 if parent_dir == filename: | 555 if self._repo_type != REPO_SVN or parent_dir == filename: |
518 logging.info("No svn checkout found. Assuming it's a git repo and not " | 556 logging.info("No svn checkout found, skip svn add.") |
519 "adding") | |
520 return True | 557 return True |
521 | 558 |
522 original_dir = os.getcwd() | 559 original_dir = os.getcwd() |
523 os.chdir(parent_dir) | 560 os.chdir(parent_dir) |
524 status_output = RunShell(['svn', 'status', basename], False) | 561 status_output = RunShell(['svn', 'status', basename], False) |
525 os.chdir(original_dir) | 562 os.chdir(original_dir) |
526 output = status_output.upper() | 563 output = status_output.upper() |
527 if output.startswith('A') or output.startswith('M'): | 564 if output.startswith('A') or output.startswith('M'): |
528 logging.info(' File already added to SVN: "%s"', filename) | 565 logging.info(' File already added to SVN: "%s"', filename) |
529 return True | 566 return True |
(...skipping 328 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
858 def main(): | 895 def main(): |
859 """Main function to produce new baselines.""" | 896 """Main function to produce new baselines.""" |
860 | 897 |
861 option_parser = optparse.OptionParser() | 898 option_parser = optparse.OptionParser() |
862 option_parser.add_option('-v', '--verbose', | 899 option_parser.add_option('-v', '--verbose', |
863 action='store_true', | 900 action='store_true', |
864 default=False, | 901 default=False, |
865 help='include debug-level logging.') | 902 help='include debug-level logging.') |
866 | 903 |
867 option_parser.add_option('-p', '--platforms', | 904 option_parser.add_option('-p', '--platforms', |
868 default='mac,win,win-xp,linux', | 905 default='mac,win,win-xp,win-vista,linux', |
869 help=('Comma delimited list of platforms that need ' | 906 help=('Comma delimited list of platforms that need ' |
870 'rebaselining.')) | 907 'rebaselining.')) |
871 | 908 |
872 option_parser.add_option('-u', '--archive_url', | 909 option_parser.add_option('-u', '--archive_url', |
873 default=('http://build.chromium.org/buildbot/' | 910 default=('http://build.chromium.org/buildbot/' |
874 'layout_test_results'), | 911 'layout_test_results'), |
875 help=('Url to find the layout test result archive ' | 912 help=('Url to find the layout test result archive ' |
876 'file.')) | 913 'file.')) |
877 | 914 |
878 option_parser.add_option('-t', '--archive_name', | 915 option_parser.add_option('-t', '--archive_name', |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
972 rebaseline_platforms, | 1009 rebaseline_platforms, |
973 rebaselining_tests) | 1010 rebaselining_tests) |
974 html_generator.GenerateHtml() | 1011 html_generator.GenerateHtml() |
975 html_generator.ShowHtml() | 1012 html_generator.ShowHtml() |
976 LogDashedString('Rebaselining result comparison done', None) | 1013 LogDashedString('Rebaselining result comparison done', None) |
977 | 1014 |
978 sys.exit(0) | 1015 sys.exit(0) |
979 | 1016 |
980 if '__main__' == __name__: | 1017 if '__main__' == __name__: |
981 main() | 1018 main() |
OLD | NEW |