| # Copyright 2020 The Dart Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """A recipe that processes go/dart-cbuild results. |
| |
| The cbuild uploads files to a GCS bucket (one per SDK commit). This recipe reads |
| those files and starts a synthethic build of the "google" builder with the same |
| outcome as the cbuild and a link to the cbuild results. Then it comments on the |
| gerrit change for the commit, if any. Finally, the result is deleted from the |
| GCS bucket so that it won't be processed again by the next run. |
| """ |
| |
| import re |
| |
| from google.protobuf import json_format |
| from recipe_engine import post_process |
| |
| from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb2 |
| from PB.recipes.dart.dart.external_build import ExternalBuild |
| |
| DEPS = [ |
| 'depot_tools/gitiles', |
| 'depot_tools/gsutil', |
| 'fuchsia/gerrit', |
| 'recipe_engine/buildbucket', |
| 'recipe_engine/json', |
| 'recipe_engine/properties', |
| 'recipe_engine/raw_io', |
| 'recipe_engine/runtime', |
| 'recipe_engine/step', |
| ] |
| BUILDER = 'google' |
| BUCKET = 'ci.sandbox' |
| CBUILD_BUCKET_URL = 'gs://dart-cbuild-test-results' |
| CBUILD_RESULT_URL = 'https://goto.google.com/dart-cbuild/find/%s' |
| CBUILD_MESSAGE = '''go/dart-cbuild result: %s |
| |
| Details: %s |
| ''' |
| HOST = 'dart.googlesource.com' |
| GERRIT_HOST_URL = 'dart-review.googlesource.com' |
| REF = 'refs/heads/main' |
| REPO = 'sdk' |
| REPO_URL = 'https://%s/%s' % (HOST, REPO) |
| |
| |
| def RunSteps(api): |
| results = _find_cbuild_results(api) |
| requests = [] |
| if not results: |
| # Nothing to do |
| return |
| for result in results: |
| is_presubmit = result['presubmit'] |
| branch = result['branch'] |
| # TODO(athom): Remove master when cbuild only sends main branch messages. |
| if not is_presubmit and branch in ('master', 'main'): |
| requests.append(_create_schedule_build_request(api, result)) |
| api.buildbucket.schedule(requests) |
| with api.step.defer_results(): |
| for result in results: |
| commit_hash = result['commit_hash'] |
| change_id = _get_change_id(api, commit_hash) |
| if not api.runtime.is_experimental and change_id: |
| message = CBUILD_MESSAGE % (result['cbuild_result_text'], |
| result['cbuild_url']) |
| if result['cbuild_regression']: |
| message += 'Bugs: go/dart-cbuild-bug/%s\n' % commit_hash |
| api.gerrit.set_review( |
| 'update gerrit for %s' % commit_hash, |
| change_id, |
| host=GERRIT_HOST_URL, |
| message=message, |
| ) |
| if not api.runtime.is_experimental: |
| api.gsutil.remove_url( |
| result['result_url'], name='delete result for %s' % commit_hash) |
| |
| |
| def _find_cbuild_results(api): |
| listing = api.gsutil.list( |
| CBUILD_BUCKET_URL, name='find cbuild results', stdout=api.raw_io.output()) |
| |
| results = [] |
| result_urls = listing.stdout.splitlines()[:50] # limit to 50 results per run |
| for result_url in result_urls: |
| if result_url.endswith('/'): |
| # ignore subdirectories |
| continue |
| _, commit_hash = result_url.rsplit('/', 1) |
| download = api.gsutil.download_url( |
| result_url, |
| api.json.output(name='result'), |
| name='download cbuild result for %s' % commit_hash) |
| cbuild_result_json = download.json.outputs['result'] |
| cbuild_success = cbuild_result_json['result'] |
| cbuild_regression = cbuild_result_json.get('regression', False) |
| cbuild_presubmit = cbuild_result_json.get('presubmit', False) |
| cbuild_branch = cbuild_result_json.get('branch', None) |
| |
| assert not (cbuild_success and cbuild_regression) |
| |
| cbuild_result = common_pb2.SUCCESS if cbuild_success else common_pb2.FAILURE |
| cbuild_result_text = common_pb2.Status.Name(cbuild_result) |
| if cbuild_regression: |
| cbuild_result_text += ' (REGRESSIONS DETECTED)' |
| elif not cbuild_success and cbuild_regression is not None: |
| cbuild_result_text += ' (NO REGRESSIONS DETECTED)' |
| results.append({ |
| 'result_url': result_url, |
| 'commit_hash': commit_hash, |
| 'cbuild_result': cbuild_result, |
| 'cbuild_result_text': cbuild_result_text, |
| 'cbuild_regression': cbuild_regression, |
| 'cbuild_url': CBUILD_RESULT_URL % commit_hash, |
| 'presubmit': cbuild_presubmit, |
| 'branch': cbuild_branch, |
| }) |
| return results |
| |
| |
| def _create_schedule_build_request(api, result): |
| return api.buildbucket.schedule_request( |
| builder=BUILDER, |
| bucket=BUCKET, |
| properties=json_format.MessageToDict( |
| ExternalBuild( |
| result=result['cbuild_result'], url=result['cbuild_url'])), |
| gitiles_commit=common_pb2.GitilesCommit( |
| host=HOST, |
| project=REPO, |
| ref=REF, |
| id=result['commit_hash'], |
| ), |
| inherit_buildsets=False, |
| ) |
| |
| |
| def _get_change_id(api, commit_hash): |
| result = api.gitiles.commit_log( |
| commit=commit_hash, |
| step_name='download change-id for %s' % commit_hash, |
| url=REPO_URL, |
| ) |
| commit = result.get_result() |
| match = re.search(r'^Change-Id: (.*)$', commit['message'], re.MULTILINE) |
| change_id = match.group(1) if match else None |
| return change_id |
| |
| |
| def GenTests(api): |
| |
| def schedules_build(build, scheduled): |
| |
| def _check(check, step_odict, step): |
| input = str(step_odict[step].stdin) |
| check(('"url": "https://goto.google.com/dart-cbuild/find/%s"' % |
| build in input) == scheduled) |
| |
| return api.post_process(_check, 'buildbucket.schedule') |
| |
| yield api.test( |
| 'no-results', |
| api.post_process(post_process.DoesNotRunRE, |
| '.*(delete|download|schedule).*'), |
| api.post_process(post_process.StatusSuccess), |
| api.post_process(post_process.DropExpectation), |
| ) |
| |
| def _cbuild_results(commit_hash, |
| result=True, |
| regression=False, |
| presubmit=False, |
| branch='main'): |
| cbuild_result = { |
| 'result': result, |
| 'regression': regression, |
| 'presubmit': presubmit, |
| } |
| if branch: |
| cbuild_result['branch'] = branch |
| return sum([ |
| api.step_data('gsutil download cbuild result for %s' % commit_hash, |
| api.json.output(cbuild_result, name='result')), |
| api.step_data( |
| 'download change-id for %s' % commit_hash, |
| api.gitiles.make_commit_test_data( |
| commit_hash, 'Subject\n\nChange-Id: I%s\n' % commit_hash)) |
| ], api.empty_test_data()) |
| |
| RESULT_SUCCESS = _cbuild_results('hash-of-success', result=True) |
| RESULT_REGRESSION = _cbuild_results( |
| 'hash-of-regression', result=False, regression=True) |
| RESULT_PRESUBMIT = _cbuild_results( |
| 'hash-of-presubmit', result=False, presubmit=True) |
| RESULT_FAILURE = _cbuild_results( |
| 'hash-of-failure', result=False, regression=False) |
| RESULT_BRANCH = _cbuild_results('hash-of-branch', branch='branch-name') |
| |
| CBUILD_RESULT_URLS = '''gs://dart-cbuild-test-results/hash-of-success |
| gs://dart-cbuild-test-results/ignore-this-directory/ |
| gs://dart-cbuild-test-results/hash-of-regression |
| gs://dart-cbuild-test-results/hash-of-presubmit |
| gs://dart-cbuild-test-results/hash-of-failure |
| gs://dart-cbuild-test-results/hash-of-branch |
| ''' |
| yield api.test( |
| 'with-results', |
| api.step_data( |
| 'gsutil find cbuild results', |
| stdout=api.raw_io.output(CBUILD_RESULT_URLS)), |
| RESULT_SUCCESS, |
| RESULT_REGRESSION, |
| RESULT_PRESUBMIT, |
| RESULT_FAILURE, |
| RESULT_BRANCH, |
| schedules_build('hash-of-success', True), |
| schedules_build('hash-of-regression', True), |
| schedules_build('hash-of-presubmit', False), |
| schedules_build('hash-of-failure', True), |
| schedules_build('hash-of-branch', False), |
| api.post_process(post_process.MustRun, |
| 'update gerrit for hash-of-success'), |
| api.post_process(post_process.MustRun, |
| 'update gerrit for hash-of-regression'), |
| api.post_process(post_process.MustRun, |
| 'update gerrit for hash-of-presubmit'), |
| api.post_process(post_process.MustRun, |
| 'update gerrit for hash-of-failure'), |
| api.post_process(post_process.MustRun, |
| 'update gerrit for hash-of-branch'), |
| api.post_process(post_process.MustRun, |
| 'gsutil delete result for hash-of-success'), |
| api.post_process(post_process.MustRun, |
| 'gsutil delete result for hash-of-regression'), |
| api.post_process(post_process.MustRun, |
| 'gsutil delete result for hash-of-presubmit'), |
| api.post_process(post_process.MustRun, |
| 'gsutil delete result for hash-of-failure'), |
| api.post_process(post_process.MustRun, |
| 'gsutil delete result for hash-of-branch'), |
| api.post_process(post_process.StatusSuccess), |
| ) |
| |
| yield api.test( |
| 'experimental-with-results', |
| api.runtime(is_experimental=True), |
| api.step_data( |
| 'gsutil find cbuild results', |
| stdout=api.raw_io.output(CBUILD_RESULT_URLS)), |
| RESULT_SUCCESS, |
| RESULT_REGRESSION, |
| RESULT_PRESUBMIT, |
| RESULT_FAILURE, |
| RESULT_BRANCH, |
| api.post_process(post_process.DoesNotRunRE, '.*(delete|update).*'), |
| api.post_process(post_process.StatusSuccess), |
| ) |
| |
| yield api.test( |
| 'no-change-id', |
| api.step_data( |
| 'gsutil find cbuild results', |
| stdout=api.raw_io.output('gs://dart-cbuild-test-results/hash')), |
| api.step_data( |
| 'gsutil download cbuild result for hash', |
| api.json.output({ |
| 'result': False, |
| 'regression': False |
| }, name='result')), |
| api.step_data('download change-id for hash', |
| api.gitiles.make_commit_test_data('hash', 'Subject\n')), |
| api.post_process(post_process.DoesNotRunRE, '.*update gerrit.*'), |
| api.post_process(post_process.MustRun, 'gsutil delete result for hash'), |
| api.post_process(post_process.StatusSuccess), |
| api.post_process(post_process.DropExpectation), |
| ) |