blob: 47df5cff7772a99100d0be53a6d2e259dcefd0ca [file] [log] [blame]
// Copyright 2022 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:auto_submit/model/auto_submit_query_result.dart';
import 'package:auto_submit/service/github_service.dart';
import 'package:auto_submit/validations/validation.dart';
import 'package:github/github.dart' as github;
import '../service/config.dart';
import '../service/log.dart';
/// Validates all the CI build/tests ran and were successful.
class CiSuccessful extends Validation {
CiSuccessful({
required Config config,
}) : super(config: config);
@override
/// Implements the CI build/tests validations.
Future<ValidationResult> validate(QueryResult result, github.PullRequest messagePullRequest) async {
bool allSuccess = true;
github.RepositorySlug slug = messagePullRequest.base!.repo!.slug();
final PullRequest pullRequest = result.repository!.pullRequest!;
Set<FailureDetail> failures = <FailureDetail>{};
// The status checks that are not related to changes in this PR.
const Set<String> notInAuthorsControl = <String>{
'luci-flutter', // flutter repo
'luci-engine', // engine repo
'submit-queue', // plugins repo
};
List<ContextNode> statuses = <ContextNode>[];
Commit commit = pullRequest.commits!.nodes!.single.commit!;
// Recently most of the repositories have migrated away of using the status
// APIs and for those repos commit.status is null.
if (commit.status != null && commit.status!.contexts!.isNotEmpty) {
statuses.addAll(commit.status!.contexts!);
}
// Ensure repos with tree statuses have it set
if (Config.reposWithTreeStatus.contains(slug)) {
bool treeStatusExists = false;
final String treeStatusName = 'luci-${slug.name}';
// Scan list of statuses to see if the tree status exists (this list is expected to be <5 items)
for (ContextNode status in statuses) {
if (status.context == treeStatusName) {
treeStatusExists = true;
}
}
if (!treeStatusExists) {
failures.add(FailureDetail('tree status $treeStatusName', 'https://flutter-dashboard.appspot.com/#/build'));
}
}
// List of labels associated with the pull request.
final List<String> labelNames = (messagePullRequest.labels as List<github.IssueLabel>)
.map<String>((github.IssueLabel labelMap) => labelMap.name)
.toList();
final String overrideTreeStatusLabel = config.overrideTreeStatusLabel;
log.info('Validating name: ${slug.name}, status: $statuses');
for (ContextNode status in statuses) {
final String? name = status.context;
if (status.state != STATUS_SUCCESS) {
if (notInAuthorsControl.contains(name) && labelNames.contains(overrideTreeStatusLabel)) {
continue;
}
allSuccess = false;
if (status.state == STATUS_FAILURE && !notInAuthorsControl.contains(name)) {
failures.add(FailureDetail(name!, status.targetUrl!));
}
}
}
final GithubService gitHubService = await config.createGithubService(slug);
final String? sha = commit.oid;
List<github.CheckRun> checkRuns = <github.CheckRun>[];
if (messagePullRequest.head != null && sha != null) {
checkRuns.addAll(await gitHubService.getCheckRuns(slug, sha));
}
log.info('Validating name: ${slug.name}, checks: $checkRuns');
for (github.CheckRun checkRun in checkRuns) {
final String? name = checkRun.name;
if (checkRun.conclusion == github.CheckRunConclusion.success ||
(checkRun.status == github.CheckRunStatus.completed &&
checkRun.conclusion == github.CheckRunConclusion.neutral)) {
continue;
} else if (checkRun.status == github.CheckRunStatus.completed) {
failures.add(FailureDetail(name!, checkRun.detailsUrl as String));
}
allSuccess = false;
}
if (!allSuccess && failures.isEmpty) {
return ValidationResult(allSuccess, Action.IGNORE_TEMPORARILY, '');
}
Action action = labelNames.contains(config.overrideTreeStatusLabel) ? Action.IGNORE_FAILURE : Action.REMOVE_LABEL;
return ValidationResult(allSuccess, action, '');
}
}