def test_recent_failing_chromium_exports_all_pages(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=1) self.wpt_github.host.web.responses = [ { 'status_code': 200, 'headers': { 'Link': '<https://api.github.com/resources?page=2>; rel="next"' }, 'body': json.dumps({ 'incomplete_results': False, 'items': [self.generate_pr_item(1)] }) }, { 'status_code': 200, 'headers': { 'Link': '' }, 'body': json.dumps({ 'incomplete_results': False, 'items': [self.generate_pr_item(2)] }) }, ] self.assertEqual( len(self.wpt_github.recent_failing_chromium_exports()), 2)
def test_branch_check_runs_all_pages(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=1) self.wpt_github.host.web.responses = [ { 'status_code': 200, 'headers': { 'Link': '<https://api.github.com/resources?page=2>; rel="next"' }, 'body': json.dumps({'check_runs': [{ 'conclusion': 'success' }]}) }, { 'status_code': 200, 'headers': { 'Link': '' }, 'body': json.dumps({'check_runs': [{ 'conclusion': 'failure' }]}) }, ] check_runs = self.wpt_github.get_branch_check_runs('1') self.assertEqual(check_runs[0]['conclusion'], 'success') self.assertEqual(check_runs[1]['conclusion'], 'failure')
def test_constructor_throws_on_pr_history_window_too_large(self): with self.assertRaises(ValueError): self.wpt_github = WPTGitHub( MockHost(), user='******', token='decafbad', pr_history_window=MAX_PR_HISTORY_WINDOW + 1)
def test_all_pull_requests_throws_github_error_when_incomplete(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=1) self.wpt_github.host.web.responses = [ {'status_code': 200, 'body': json.dumps({'incomplete_results': True, 'items': [self.generate_pr_item(1)]})}, ] with self.assertRaises(GitHubError): self.wpt_github.all_pull_requests()
def test_all_pull_requests_single_page(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=1) self.wpt_github.host.web.responses = [ {'status_code': 200, 'headers': {'Link': ''}, 'body': json.dumps({'incomplete_results': False, 'items': [self.generate_pr_item(1)]})}, ] self.assertEqual(len(self.wpt_github.all_pull_requests()), 1)
def main(self, argv=None): """Creates PRs for in-flight CLs and merges changes that land on master. Returns: A boolean: True if success, False if there were any patch failures. """ options = self.parse_args(argv) self.dry_run = options.dry_run log_level = logging.DEBUG if options.verbose else logging.INFO configure_logging(logging_level=log_level, include_time=True) if options.verbose: # Print out the full output when executive.run_command fails. self.host.executive.error_output_limit = None credentials = read_credentials(self.host, options.credentials_json) if not (credentials.get('GH_USER') and credentials.get('GH_TOKEN')): _log.error('You must provide your GitHub credentials for this ' 'script to work.') _log.error('See https://chromium.googlesource.com/chromium/src' '/+/master/docs/testing/web_platform_tests.md' '#GitHub-credentials for instructions on how to set ' 'your credentials up.') return False self.wpt_github = self.wpt_github or WPTGitHub( self.host, credentials['GH_USER'], credentials['GH_TOKEN']) self.gerrit = self.gerrit or GerritAPI( self.host, credentials['GERRIT_USER'], credentials['GERRIT_TOKEN']) self.local_wpt = self.local_wpt or LocalWPT(self.host, credentials['GH_TOKEN']) self.local_wpt.fetch() _log.info('Searching for exportable in-flight CLs.') # The Gerrit search API is slow and easy to fail, so we wrap it in a try # statement to continue exporting landed commits when it fails. try: open_gerrit_cls = self.gerrit.query_exportable_open_cls() except GerritError as e: _log.info( 'In-flight CLs cannot be exported due to the following error:') _log.error(str(e)) gerrit_error = True else: self.process_gerrit_cls(open_gerrit_cls) gerrit_error = False _log.info('Searching for exportable Chromium commits.') exportable_commits, git_errors = self.get_exportable_commits() self.process_chromium_commits(exportable_commits) if git_errors: _log.info( 'Attention: The following errors have prevented some commits from being ' 'exported:') for error in git_errors: _log.error(error) return not (gerrit_error or git_errors)
def main(self, argv=None): """Closes all PRs that are abandoned in Gerrit.""" options = self.parse_args(argv) log_level = logging.DEBUG if options.verbose else logging.INFO configure_logging(logging_level=log_level, include_time=True) credentials = read_credentials(self.host, options.credentials_json) gh_user = credentials.get('GH_USER') gh_token = credentials.get('GH_TOKEN') if not gh_user or not gh_token: _log.error('You have not set your GitHub credentials. This ' 'script may fail with a network error when making ' 'an API request to GitHub.') _log.error('See https://chromium.googlesource.com/chromium/src' '/+/master/docs/testing/web_platform_tests.md' '#GitHub-credentials for instructions on how to set ' 'your credentials up.') return False gr_user = credentials['GERRIT_USER'] gr_token = credentials['GERRIT_TOKEN'] if not gr_user or not gr_token: _log.warning('You have not set your Gerrit credentials. This ' 'script may fail with a network error when making ' 'an API request to Gerrit.') self.wpt_github = self.wpt_github or WPTGitHub(self.host, gh_user, gh_token) self.gerrit = self.gerrit or GerritAPI(self.host, gr_user, gr_token) pull_requests = self.retrieve_all_prs() for pull_request in pull_requests: if pull_request.state != 'open': continue change_id = self.wpt_github.extract_metadata( 'Change-Id: ', pull_request.body) if not change_id: continue try: cl = self.gerrit.query_cl(change_id) except GerritError as e: _log.error('Could not query change_id %s: %s', change_id, str(e)) continue cl_status = cl.status if cl_status == 'ABANDONED': comment = 'Close this PR because the Chromium CL has been abandoned.' self.log_affected_pr_details(pull_request, comment) self.close_pr_and_delete_branch(pull_request.number, comment) elif cl_status == 'MERGED' and (not cl.is_exportable()): comment = 'Close this PR because the Chromium CL does not have exportable changes.' self.log_affected_pr_details(pull_request, comment) self.close_pr_and_delete_branch(pull_request.number, comment) return True
def test_branch_check_runs_single_page(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=1) self.wpt_github.host.web.responses = [ { 'status_code': 200, 'headers': { 'Link': '' }, 'body': json.dumps({'check_runs': [{ 'conclusion': 'success' }]}) }, ] self.assertEqual( self.wpt_github.get_branch_check_runs('1')[0]['conclusion'], 'success')
def main(self, argv=None): """Closes all PRs that are abandoned in Gerrit.""" options = self.parse_args(argv) log_level = logging.DEBUG if options.verbose else logging.INFO configure_logging(logging_level=log_level, include_time=True) credentials = read_credentials(self.host, options.credentials_json) gh_user = credentials.get('GH_USER') gh_token = credentials.get('GH_TOKEN') if not gh_user or not gh_token: _log.error('You have not set your GitHub credentials. This ' 'script may fail with a network error when making ' 'an API request to GitHub.') _log.error('See https://chromium.googlesource.com/chromium/src' '/+/master/docs/testing/web_platform_tests.md' '#GitHub-credentials for instructions on how to set ' 'your credentials up.') return False gr_user = credentials['GERRIT_USER'] gr_token = credentials['GERRIT_TOKEN'] if not gr_user or not gr_token: _log.warning('You have not set your Gerrit credentials. This ' 'script may fail with a network error when making ' 'an API request to Gerrit.') self.wpt_github = self.wpt_github or WPTGitHub(self.host, gh_user, gh_token) self.gerrit = self.gerrit or GerritAPI(self.host, gr_user, gr_token) pull_requests = self.retrieve_all_prs() for pull_request in pull_requests: if pull_request.state != 'open': continue change_id = self.wpt_github.extract_metadata('Change-Id: ', pull_request.body) if not change_id: continue cl_status = self.gerrit.query_cl(change_id).status if cl_status == 'ABANDONED': _log.info('https://github.com/web-platform-tests/wpt/pull/%s', pull_request.number) _log.info(self.wpt_github.extract_metadata('Reviewed-on: ', pull_request.body)) self.close_abandoned_pr(pull_request) return True
def extract_metadata(self, tag, commit_body, all_matches=False): return WPTGitHub.extract_metadata(tag, commit_body, all_matches)
def setUp(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad')
class WPTGitHubTest(unittest.TestCase): def generate_pr_item(self, pr_number, state='closed'): return { 'title': 'Foobar', 'number': pr_number, 'body': 'description', 'state': state, 'labels': [{ 'name': EXPORT_PR_LABEL }] } def setUp(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad') def test_init(self): self.assertEqual(self.wpt_github.user, 'rutabaga') self.assertEqual(self.wpt_github.token, 'decafbad') def test_constructor_throws_on_pr_history_window_too_large(self): with self.assertRaises(ValueError): self.wpt_github = WPTGitHub( MockHost(), user='******', token='decafbad', pr_history_window=MAX_PR_HISTORY_WINDOW + 1) def test_auth_token(self): self.assertEqual(self.wpt_github.auth_token(), base64.encodestring('rutabaga:decafbad').strip()) def test_extract_link_next(self): link_header = ( '<https://api.github.com/user/repos?page=1&per_page=100>; rel="first", ' '<https://api.github.com/user/repos?page=2&per_page=100>; rel="prev", ' '<https://api.github.com/user/repos?page=4&per_page=100>; rel="next", ' '<https://api.github.com/user/repos?page=50&per_page=100>; rel="last"' ) self.assertEqual(self.wpt_github.extract_link_next(link_header), '/user/repos?page=4&per_page=100') def test_extract_link_next_not_found(self): self.assertIsNone(self.wpt_github.extract_link_next('')) def test_recent_failing_chromium_exports_single_page(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=1) self.wpt_github.host.web.responses = [ { 'status_code': 200, 'headers': { 'Link': '' }, 'body': json.dumps({ 'incomplete_results': False, 'items': [self.generate_pr_item(1)] }) }, ] self.assertEqual( len(self.wpt_github.recent_failing_chromium_exports()), 1) def test_recent_failing_chromium_exports_all_pages(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=1) self.wpt_github.host.web.responses = [ { 'status_code': 200, 'headers': { 'Link': '<https://api.github.com/resources?page=2>; rel="next"' }, 'body': json.dumps({ 'incomplete_results': False, 'items': [self.generate_pr_item(1)] }) }, { 'status_code': 200, 'headers': { 'Link': '' }, 'body': json.dumps({ 'incomplete_results': False, 'items': [self.generate_pr_item(2)] }) }, ] self.assertEqual( len(self.wpt_github.recent_failing_chromium_exports()), 2) def test_recent_failing_chromium_exports_throws_github_error(self): self.wpt_github.host.web.responses = [ { 'status_code': 204 }, ] with self.assertRaises(GitHubError): self.wpt_github.recent_failing_chromium_exports() def test_all_pull_requests_single_page(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=1) self.wpt_github.host.web.responses = [ { 'status_code': 200, 'headers': { 'Link': '' }, 'body': json.dumps({ 'incomplete_results': False, 'items': [self.generate_pr_item(1)] }) }, ] self.assertEqual(len(self.wpt_github.all_pull_requests()), 1) def test_all_pull_requests_all_pages(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=2) self.wpt_github.host.web.responses = [ { 'status_code': 200, 'headers': { 'Link': '<https://api.github.com/resources?page=2>; rel="next"' }, 'body': json.dumps({ 'incomplete_results': False, 'items': [self.generate_pr_item(1)] }) }, { 'status_code': 200, 'headers': { 'Link': '' }, 'body': json.dumps({ 'incomplete_results': False, 'items': [self.generate_pr_item(2)] }) }, ] self.assertEqual(len(self.wpt_github.all_pull_requests()), 2) def test_all_pull_requests_reaches_pr_history_window(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=2) self.wpt_github.host.web.responses = [ { 'status_code': 200, 'headers': { 'Link': '<https://api.github.com/resources?page=2>; rel="next"' }, 'body': json.dumps({ 'incomplete_results': False, 'items': [self.generate_pr_item(1)] }) }, { 'status_code': 200, 'headers': { 'Link': '' }, 'body': json.dumps({ 'incomplete_results': False, 'items': [self.generate_pr_item(2), self.generate_pr_item(3)] }) }, ] self.assertEqual(len(self.wpt_github.all_pull_requests()), 2) def test_all_pull_requests_throws_github_error_on_non_200(self): self.wpt_github.host.web.responses = [ { 'status_code': 204 }, ] with self.assertRaises(GitHubError): self.wpt_github.all_pull_requests() def test_all_pull_requests_throws_github_error_when_incomplete(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=1) self.wpt_github.host.web.responses = [ { 'status_code': 200, 'body': json.dumps({ 'incomplete_results': True, 'items': [self.generate_pr_item(1)] }) }, ] with self.assertRaises(GitHubError): self.wpt_github.all_pull_requests() def test_all_pull_requests_throws_github_error_when_too_few_prs(self): self.wpt_github = WPTGitHub(MockHost(), user='******', token='decafbad', pr_history_window=2) self.wpt_github.host.web.responses = [ { 'status_code': 200, 'body': json.dumps({ 'incomplete_results': False, 'items': [self.generate_pr_item(1)] }) }, ] with self.assertRaises(GitHubError): self.wpt_github.all_pull_requests() def test_create_pr_success(self): self.wpt_github.host.web.responses = [ { 'status_code': 201, 'body': json.dumps({'number': 1234}) }, ] self.assertEqual(self.wpt_github.create_pr('branch', 'title', 'body'), 1234) def test_create_pr_throws_github_error_on_non_201(self): self.wpt_github.host.web.responses = [ { 'status_code': 200 }, ] with self.assertRaises(GitHubError): self.wpt_github.create_pr('branch', 'title', 'body') def test_get_branch_statuses(self): self.wpt_github.host.web.responses = [ { 'status_code': 200, 'body': json.dumps({'statuses': [{ 'description': 'abc' }]}) }, ] self.assertEqual( self.wpt_github.get_branch_statuses('1234')[0]['description'], 'abc') def test_get_pr_branch(self): self.wpt_github.host.web.responses = [ { 'status_code': 200, 'body': json.dumps({'head': { 'ref': 'fake_branch' }}) }, ] self.assertEqual(self.wpt_github.get_pr_branch(1234), 'fake_branch') def test_is_pr_merged_receives_204(self): self.wpt_github.host.web.responses = [ { 'status_code': 204 }, ] self.assertTrue(self.wpt_github.is_pr_merged(1234)) def test_is_pr_merged_receives_404(self): self.wpt_github.host.web.responses = [ { 'status_code': 404 }, ] self.assertFalse(self.wpt_github.is_pr_merged(1234)) def test_merge_pr_success(self): self.wpt_github.host.web.responses = [ { 'status_code': 200 }, ] self.wpt_github.merge_pr(1234) def test_merge_pr_throws_merge_error_on_405(self): self.wpt_github.host.web.responses = [ { 'status_code': 405 }, ] with self.assertRaises(MergeError): self.wpt_github.merge_pr(5678) def test_remove_label_throws_github_error_on_non_200_or_204(self): self.wpt_github.host.web.responses = [ { 'status_code': 201 }, ] with self.assertRaises(GitHubError): self.wpt_github.remove_label(1234, 'rutabaga') def test_delete_remote_branch_throws_github_error_on_non_204(self): self.wpt_github.host.web.responses = [ { 'status_code': 200 }, ] with self.assertRaises(GitHubError): self.wpt_github.delete_remote_branch('rutabaga') def test_add_comment_throws_github_error_on_non_201(self): self.wpt_github.host.web.responses = [ { 'status_code': 200 }, ] with self.assertRaises(GitHubError): self.wpt_github.add_comment(123, 'rutabaga') def test_pr_for_chromium_commit_change_id_only(self): self.wpt_github.all_pull_requests = lambda: [ PullRequest('PR1', 1, 'body\nChange-Id: I00c0ffee', 'open', []), PullRequest('PR2', 2, 'body\nChange-Id: I00decade', 'open', []), ] chromium_commit = MockChromiumCommit( MockHost(), change_id='I00decade', position='refs/heads/master@{#10}') pull_request = self.wpt_github.pr_for_chromium_commit(chromium_commit) self.assertEqual(pull_request.number, 2) def test_pr_for_chromium_commit_prefers_change_id(self): self.wpt_github.all_pull_requests = lambda: [ PullRequest( 'PR1', 1, 'body\nChange-Id: I00c0ffee\nCr-Commit-Position: refs/heads/master@{#10}', 'open', []), PullRequest( 'PR2', 2, 'body\nChange-Id: I00decade\nCr-Commit-Position: refs/heads/master@{#33}', 'open', []), ] chromium_commit = MockChromiumCommit( MockHost(), change_id='I00decade', position='refs/heads/master@{#10}') pull_request = self.wpt_github.pr_for_chromium_commit(chromium_commit) self.assertEqual(pull_request.number, 2) def test_pr_for_chromium_commit_multiple_change_ids(self): self.wpt_github.all_pull_requests = lambda: [ PullRequest('PR1', 1, 'body\nChange-Id: I00c0ffee\nChange-Id: I00decade', 'open', []), ] chromium_commit = MockChromiumCommit( MockHost(), change_id='I00c0ffee', position='refs/heads/master@{#10}') pull_request = self.wpt_github.pr_for_chromium_commit(chromium_commit) self.assertEqual(pull_request.number, 1) chromium_commit = MockChromiumCommit( MockHost(), change_id='I00decade', position='refs/heads/master@{#33}') pull_request = self.wpt_github.pr_for_chromium_commit(chromium_commit) self.assertEqual(pull_request.number, 1)
def main(self, argv=None): # TODO(robertma): Test this method! Split it to make it easier to test # if necessary. options = self.parse_args(argv) self.verbose = options.verbose log_level = logging.DEBUG if self.verbose else logging.INFO configure_logging(logging_level=log_level, include_time=True) # Having the full output when executive.run_command fails is useful when # investigating a failed import, as all we have are logs. self.executive.error_output_limit = None if options.auto_update and options.auto_upload: _log.error( '--auto-upload and --auto-update cannot be used together.') return 1 if not self.checkout_is_okay(): return 1 credentials = read_credentials(self.host, options.credentials_json) gh_user = credentials.get('GH_USER') gh_token = credentials.get('GH_TOKEN') if not gh_user or not gh_token: _log.warning('You have not set your GitHub credentials. This ' 'script may fail with a network error when making ' 'an API request to GitHub.') _log.warning('See https://chromium.googlesource.com/chromium/src' '/+/master/docs/testing/web_platform_tests.md' '#GitHub-credentials for instructions on how to set ' 'your credentials up.') self.wpt_github = self.wpt_github or WPTGitHub(self.host, gh_user, gh_token) self.git_cl = GitCL( self.host, auth_refresh_token_json=options.auth_refresh_token_json) _log.debug('Noting the current Chromium revision.') chromium_revision = self.chromium_git.latest_git_commit() # Instantiate Git after local_wpt.fetch() to make sure the path exists. local_wpt = LocalWPT(self.host, gh_token=gh_token) local_wpt.fetch() self.wpt_git = self.host.git(local_wpt.path) if options.revision is not None: _log.info('Checking out %s', options.revision) self.wpt_git.run(['checkout', options.revision]) _log.debug('Noting the revision we are importing.') self.wpt_revision = self.wpt_git.latest_git_commit() self.last_wpt_revision = self._get_last_imported_wpt_revision() import_commit = 'wpt@%s' % self.wpt_revision _log.info('Importing %s to Chromium %s', import_commit, chromium_revision) if options.ignore_exportable_commits: commit_message = self._commit_message(chromium_revision, import_commit) else: commits = self.apply_exportable_commits_locally(local_wpt) if commits is None: _log.error('Could not apply some exportable commits cleanly.') _log.error('Aborting import to prevent clobbering commits.') return 1 commit_message = self._commit_message( chromium_revision, import_commit, locally_applied_commits=commits) self._clear_out_dest_path() _log.info('Copying the tests from the temp repo to the destination.') test_copier = TestCopier(self.host, local_wpt.path) test_copier.do_import() # TODO(robertma): Implement `add --all` in Git (it is different from `commit --all`). self.chromium_git.run(['add', '--all', self.dest_path]) # Remove expectations for tests that were deleted and rename tests # in expectations for renamed tests. self._expectations_updater.cleanup_test_expectations_files() self._generate_manifest() # TODO(crbug.com/800570 robertma): Re-enable it once we fix the bug. # self._delete_orphaned_baselines() if not self.chromium_git.has_working_directory_changes(): _log.info('Done: no changes to import.') return 0 if self._only_wpt_manifest_changed(): _log.info('Only manifest was updated; skipping the import.') return 0 self._commit_changes(commit_message) _log.info('Changes imported and committed.') if not options.auto_upload and not options.auto_update: return 0 self._upload_cl() _log.info('Issue: %s', self.git_cl.run(['issue']).strip()) if not self.update_expectations_for_cl(): return 1 if not options.auto_update: return 0 if not self.run_commit_queue_for_cl(): return 1 if not self.send_notifications(local_wpt, options.auto_file_bugs, options.monorail_auth_json): return 1 return 0