| 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 |