| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Fetches a copy of the latest state of a W3C test repository and commits. | 5 """Fetches a copy of the latest state of a W3C test repository and commits. |
| 6 | 6 |
| 7 If this script is given the argument --auto-update, it will also: | 7 If this script is given the argument --auto-update, it will also: |
| 8 1. Upload a CL. | 8 1. Upload a CL. |
| 9 2. Trigger try jobs and wait for them to complete. | 9 2. Trigger try jobs and wait for them to complete. |
| 10 3. Make any changes that are required for new failing tests. | 10 3. Make any changes that are required for new failing tests. |
| 11 4. Commit the CL. | 11 4. Commit the CL. |
| 12 | 12 |
| 13 If this script is given the argument --auto-update, it will also attempt to | 13 If this script is given the argument --auto-update, it will also attempt to |
| 14 upload a CL, trigger try jobs, and make any changes that are required for | 14 upload a CL, trigger try jobs, and make any changes that are required for |
| 15 new failing tests before committing. | 15 new failing tests before committing. |
| 16 """ | 16 """ |
| 17 | 17 |
| 18 import argparse | 18 import argparse |
| 19 import json | 19 import json |
| 20 import logging | 20 import logging |
| 21 import re | 21 import re |
| 22 | 22 |
| 23 from webkitpy.common.net.git_cl import GitCL | 23 from webkitpy.common.net.git_cl import GitCL |
| 24 from webkitpy.common.webkit_finder import WebKitFinder | 24 from webkitpy.common.webkit_finder import WebKitFinder |
| 25 from webkitpy.layout_tests.models.test_expectations import TestExpectations, Tes
tExpectationParser | 25 from webkitpy.layout_tests.models.test_expectations import TestExpectations, Tes
tExpectationParser |
| 26 from webkitpy.w3c.update_w3c_test_expectations import W3CExpectationsLineAdder | 26 from webkitpy.w3c.common import WPT_REPO_URL, CSS_REPO_URL, WPT_DEST_NAME, CSS_D
EST_NAME |
| 27 from webkitpy.w3c.directory_owners_extractor import DirectoryOwnersExtractor |
| 27 from webkitpy.w3c.test_copier import TestCopier | 28 from webkitpy.w3c.test_copier import TestCopier |
| 28 from webkitpy.w3c.common import WPT_REPO_URL, CSS_REPO_URL, WPT_DEST_NAME, CSS_D
EST_NAME | 29 from webkitpy.w3c.wpt_expectations_updater import WPTExpectationsUpdater |
| 29 | 30 |
| 30 # Settings for how often to check try job results and how long to wait. | 31 # Settings for how often to check try job results and how long to wait. |
| 31 POLL_DELAY_SECONDS = 2 * 60 | 32 POLL_DELAY_SECONDS = 2 * 60 |
| 32 TIMEOUT_SECONDS = 180 * 60 | 33 TIMEOUT_SECONDS = 180 * 60 |
| 33 | 34 |
| 34 _log = logging.getLogger(__file__) | 35 _log = logging.getLogger(__file__) |
| 35 | 36 |
| 36 | 37 |
| 37 class TestImporter(object): | 38 class TestImporter(object): |
| 38 | 39 |
| (...skipping 278 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 317 if self.git_cl.has_failing_try_results(try_results): | 318 if self.git_cl.has_failing_try_results(try_results): |
| 318 _log.info('CQ failed; aborting.') | 319 _log.info('CQ failed; aborting.') |
| 319 self.git_cl.run(['set-close']) | 320 self.git_cl.run(['set-close']) |
| 320 return False | 321 return False |
| 321 | 322 |
| 322 _log.info('Update completed.') | 323 _log.info('Update completed.') |
| 323 return True | 324 return True |
| 324 | 325 |
| 325 def _upload_cl(self): | 326 def _upload_cl(self): |
| 326 _log.info('Uploading change list.') | 327 _log.info('Uploading change list.') |
| 327 cc_list = self.get_directory_owners_to_cc() | 328 cc_list = self.get_directory_owners() |
| 328 description = self._cl_description() | 329 description = self._cl_description() |
| 329 self.git_cl.run([ | 330 self.git_cl.run([ |
| 330 'upload', | 331 'upload', |
| 331 '-f', | 332 '-f', |
| 332 '--rietveld', | 333 '--rietveld', |
| 333 '-m', | 334 '-m', |
| 334 description, | 335 description, |
| 335 ] + ['--cc=' + email for email in cc_list]) | 336 ] + ['--cc=' + email for email in cc_list]) |
| 336 | 337 |
| 338 def get_directory_owners(self): |
| 339 """Returns a list of email addresses of owners of changed tests.""" |
| 340 _log.info('Gathering directory owners emails to CC.') |
| 341 changed_files = self.host.cwd().changed_files() |
| 342 extractor = DirectoryOwnersExtractor(self.fs) |
| 343 extractor.read_owner_map() |
| 344 return extractor.list_owners(changed_files) |
| 345 |
| 337 def _cl_description(self): | 346 def _cl_description(self): |
| 338 description = self.check_run(['git', 'log', '-1', '--format=%B']) | 347 description = self.check_run(['git', 'log', '-1', '--format=%B']) |
| 339 build_link = self._build_link() | 348 build_link = self._build_link() |
| 340 if build_link: | 349 if build_link: |
| 341 description += 'Build: %s\n\n' % build_link | 350 description += 'Build: %s\n\n' % build_link |
| 342 description += 'TBR=qyearsley@chromium.org\n' | 351 description += 'TBR=qyearsley@chromium.org\n' |
| 343 # Move any NOEXPORT tag to the end of the description. | 352 # Move any NOEXPORT tag to the end of the description. |
| 344 description = description.replace('NOEXPORT=true', '') | 353 description = description.replace('NOEXPORT=true', '') |
| 345 description = description.replace('\n\n\n\n', '\n\n') | 354 description = description.replace('\n\n\n\n', '\n\n') |
| 346 description += 'NOEXPORT=true' | 355 description += 'NOEXPORT=true' |
| 347 return description | 356 return description |
| 348 | 357 |
| 349 def _build_link(self): | 358 def _build_link(self): |
| 350 """Returns a link to a job, if running on buildbot.""" | 359 """Returns a link to a job, if running on buildbot.""" |
| 351 master_name = self.host.environ.get('BUILDBOT_MASTERNAME') | 360 master_name = self.host.environ.get('BUILDBOT_MASTERNAME') |
| 352 builder_name = self.host.environ.get('BUILDBOT_BUILDERNAME') | 361 builder_name = self.host.environ.get('BUILDBOT_BUILDERNAME') |
| 353 build_number = self.host.environ.get('BUILDBOT_BUILDNUMBER') | 362 build_number = self.host.environ.get('BUILDBOT_BUILDNUMBER') |
| 354 if not (master_name and builder_name and build_number): | 363 if not (master_name and builder_name and build_number): |
| 355 return None | 364 return None |
| 356 return 'https://build.chromium.org/p/%s/builders/%s/builds/%s' % (master
_name, builder_name, build_number) | 365 return 'https://build.chromium.org/p/%s/builders/%s/builds/%s' % (master
_name, builder_name, build_number) |
| 357 | 366 |
| 358 def get_directory_owners_to_cc(self): | |
| 359 """Returns a list of email addresses to CC for the current import.""" | |
| 360 _log.info('Gathering directory owners emails to CC.') | |
| 361 directory_owners_file_path = self.finder.path_from_webkit_base( | |
| 362 'Tools', 'Scripts', 'webkitpy', 'w3c', 'directory_owners.json') | |
| 363 with open(directory_owners_file_path) as data_file: | |
| 364 directory_to_owner = self.parse_directory_owners(json.load(data_file
)) | |
| 365 out = self.check_run(['git', 'diff', 'origin/master', '--name-only']) | |
| 366 changed_files = out.splitlines() | |
| 367 return self.generate_email_list(changed_files, directory_to_owner) | |
| 368 | |
| 369 @staticmethod | |
| 370 def parse_directory_owners(decoded_data_file): | |
| 371 directory_dict = {} | |
| 372 for dict_set in decoded_data_file: | |
| 373 if dict_set['notification-email']: | |
| 374 directory_dict[dict_set['directory']] = dict_set['notification-e
mail'] | |
| 375 return directory_dict | |
| 376 | |
| 377 def generate_email_list(self, changed_files, directory_to_owner): | |
| 378 """Returns a list of email addresses based on the given file list and | |
| 379 directory-to-owner mapping. | |
| 380 | |
| 381 Args: | |
| 382 changed_files: A list of file paths relative to the repository root. | |
| 383 directory_to_owner: A dict mapping layout test directories to emails
. | |
| 384 | |
| 385 Returns: | |
| 386 A list of the email addresses to be notified for the current import. | |
| 387 """ | |
| 388 email_addresses = set() | |
| 389 for file_path in changed_files: | |
| 390 test_path = self.finder.layout_test_name(file_path) | |
| 391 if test_path is None: | |
| 392 continue | |
| 393 test_dir = self.fs.dirname(test_path) | |
| 394 if test_dir in directory_to_owner: | |
| 395 address = directory_to_owner[test_dir] | |
| 396 if not re.match(r'\S+@\S+', address): | |
| 397 _log.warning('%s appears not be an email address, skipping.'
, address) | |
| 398 continue | |
| 399 email_addresses.add(address) | |
| 400 return sorted(email_addresses) | |
| 401 | |
| 402 def fetch_new_expectations_and_baselines(self): | 367 def fetch_new_expectations_and_baselines(self): |
| 403 """Adds new expectations and downloads baselines based on try job result
s, then commits and uploads the change.""" | 368 """Adds new expectations and downloads baselines based on try job result
s, then commits and uploads the change.""" |
| 404 _log.info('Adding test expectations lines to LayoutTests/TestExpectation
s.') | 369 _log.info('Adding test expectations lines to LayoutTests/TestExpectation
s.') |
| 405 line_adder = W3CExpectationsLineAdder(self.host) | 370 line_adder = WPTExpectationsUpdater(self.host) |
| 406 line_adder.run() | 371 line_adder.run() |
| 407 message = 'Update test expectations and baselines.' | 372 message = 'Update test expectations and baselines.' |
| 408 self.check_run(['git', 'commit', '-a', '-m', message]) | 373 self.check_run(['git', 'commit', '-a', '-m', message]) |
| 409 self.git_cl.run(['upload', '-m', message, '--rietveld']) | 374 self.git_cl.run(['upload', '-m', message, '--rietveld']) |
| 410 | 375 |
| 411 def update_all_test_expectations_files(self, deleted_tests, renamed_tests): | 376 def update_all_test_expectations_files(self, deleted_tests, renamed_tests): |
| 412 """Updates all test expectations files for tests that have been deleted
or renamed.""" | 377 """Updates all test expectations files for tests that have been deleted
or renamed.""" |
| 413 port = self.host.port_factory.get() | 378 port = self.host.port_factory.get() |
| 414 for path, file_contents in port.all_expectations_dict().iteritems(): | 379 for path, file_contents in port.all_expectations_dict().iteritems(): |
| 415 parser = TestExpectationParser(port, all_tests=None, is_lint_mode=Fa
lse) | 380 parser = TestExpectationParser(port, all_tests=None, is_lint_mode=Fa
lse) |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 451 """Returns a dict mapping source to dest name for layout tests that have
been renamed.""" | 416 """Returns a dict mapping source to dest name for layout tests that have
been renamed.""" |
| 452 out = self.check_run(['git', 'diff', 'origin/master', '-M100%', '--diff-
filter=R', '--name-status']) | 417 out = self.check_run(['git', 'diff', 'origin/master', '-M100%', '--diff-
filter=R', '--name-status']) |
| 453 renamed_tests = {} | 418 renamed_tests = {} |
| 454 for line in out.splitlines(): | 419 for line in out.splitlines(): |
| 455 _, source_path, dest_path = line.split() | 420 _, source_path, dest_path = line.split() |
| 456 source_test = self.finder.layout_test_name(source_path) | 421 source_test = self.finder.layout_test_name(source_path) |
| 457 dest_test = self.finder.layout_test_name(dest_path) | 422 dest_test = self.finder.layout_test_name(dest_path) |
| 458 if source_test and dest_test: | 423 if source_test and dest_test: |
| 459 renamed_tests[source_test] = dest_test | 424 renamed_tests[source_test] = dest_test |
| 460 return renamed_tests | 425 return renamed_tests |
| OLD | NEW |