blob: a8ef8051f416eadff781a0a45a2c348215f62935 [file] [log] [blame]
// Copyright 2020 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 'dart:convert';
import 'package:cocoon_service/src/model/ci_yaml/target.dart';
import 'package:cocoon_service/src/model/luci/buildbucket.dart';
import 'package:cocoon_service/src/model/luci/push_message.dart' as push_message;
import 'package:cocoon_service/src/service/github_checks_service.dart';
import 'package:github/github.dart' as github;
import 'package:github/github.dart';
import 'package:github/hooks.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import '../model/github/checks_test_data.dart';
import '../src/datastore/fake_config.dart';
import '../src/service/fake_scheduler.dart';
import '../src/utilities/entity_generators.dart';
import '../src/utilities/mocks.dart';
void main() {
FakeConfig config;
late FakeScheduler scheduler;
MockGithubService mockGithubService;
late MockGithubChecksUtil mockGithubChecksUtil;
late MockLuciBuildService mockLuciBuildService;
late GithubChecksService githubChecksService;
late github.CheckRun checkRun;
late RepositorySlug slug;
setUp(() {
mockGithubService = MockGithubService();
mockLuciBuildService = MockLuciBuildService();
when(mockGithubService.listFiles(any)).thenAnswer((_) async => <String>[]);
mockGithubChecksUtil = MockGithubChecksUtil();
config = FakeConfig(githubService: mockGithubService, rollerAccountsValue: {'engine-flutter-autoroll'});
githubChecksService = GithubChecksService(
config,
githubChecksUtil: mockGithubChecksUtil,
);
slug = RepositorySlug('flutter', 'cocoon');
scheduler = FakeScheduler(
config: config,
luciBuildService: mockLuciBuildService,
githubChecksUtil: mockGithubChecksUtil,
ciYaml: exampleConfig,
);
checkRun = github.CheckRun.fromJson(
jsonDecode(
'{"name": "Linux Coverage", "id": 123, "external_id": "678", "status": "completed", "started_at": "2020-05-10T02:49:31Z", "head_sha": "the_sha", "check_suite": {"id": 456}}',
) as Map<String, dynamic>,
);
final Map<String, github.CheckRun> checkRuns = <String, github.CheckRun>{'Cocoon': checkRun};
when(mockGithubChecksUtil.allCheckRuns(any, any)).thenAnswer((_) async {
return checkRuns;
});
});
group('handleCheckSuiteEvent', () {
test('requested triggers all builds', () async {
final CheckSuiteEvent checkSuiteEvent =
CheckSuiteEvent.fromJson(jsonDecode(checkSuiteString) as Map<String, dynamic>);
when(mockGithubChecksUtil.createCheckRun(any, any, any, any, output: anyNamed('output')))
.thenAnswer((_) async => generateCheckRun(1));
final PullRequest pullRequest = generatePullRequest(id: 758);
await githubChecksService.handleCheckSuite(pullRequest, checkSuiteEvent, scheduler);
final List<Target> scheduledTargets = verify(
mockLuciBuildService.scheduleTryBuilds(
targets: captureAnyNamed('targets'),
pullRequest: anyNamed('pullRequest'),
checkSuiteEvent: anyNamed('checkSuiteEvent'),
),
).captured.single as List<Target>;
final Iterable<String> scheduledTargetNames = scheduledTargets.map((Target target) => target.value.name);
expect(scheduledTargetNames, <String>[
'Linux A',
'Mac A',
'Windows A',
]);
});
});
group('updateCheckStatus', () {
test('Userdata is empty', () async {
final push_message.BuildPushMessage buildMessage =
push_message.BuildPushMessage.fromJson(jsonDecode(buildPushMessageJsonTemplate('')) as Map<String, dynamic>);
final bool success = await githubChecksService.updateCheckStatus(buildMessage, mockLuciBuildService, slug);
expect(success, isFalse);
});
test('Userdata does not contain check_run_id', () async {
final push_message.BuildPushMessage buildMessage = push_message.BuildPushMessage.fromJson(
jsonDecode(buildPushMessageJsonTemplate('{\\"retries\\": 1}')) as Map<String, dynamic>,
);
final bool success = await githubChecksService.updateCheckStatus(buildMessage, mockLuciBuildService, slug);
expect(success, isFalse);
});
test('Userdata contain check_run_id', () async {
when(mockGithubChecksUtil.getCheckRun(any, any, any)).thenAnswer((_) async => checkRun);
when(
mockLuciBuildService.getBuildById(
'8905920700440101120',
fields: 'id,builder,summaryMarkdown',
),
).thenAnswer(
(_) async => const Build(
id: '8905920700440101120',
builderId: BuilderId(bucket: 'luci.flutter.prod', project: 'flutter', builder: 'Linux Coverage'),
summaryMarkdown: 'test summary',
),
);
final push_message.BuildPushMessage buildPushMessage = push_message.BuildPushMessage.fromJson(
jsonDecode(
buildPushMessageJsonTemplate('{\\"check_run_id\\": 123,'
'\\"repo_owner\\": \\"flutter\\",'
'\\"repo_name\\": \\"cocoon\\"}'),
) as Map<String, dynamic>,
);
await githubChecksService.updateCheckStatus(buildPushMessage, mockLuciBuildService, slug);
final github.CheckRun checkRunCaptured = await verify(
mockGithubChecksUtil.updateCheckRun(
any,
any,
captureAny,
status: anyNamed('status'),
conclusion: anyNamed('conclusion'),
detailsUrl: anyNamed('detailsUrl'),
output: anyNamed('output'),
),
).captured.first;
expect(checkRunCaptured.id, checkRun.id);
expect(checkRunCaptured.name, checkRun.name);
});
test('Should rerun a failed task for a roller account', () async {
when(mockGithubChecksUtil.getCheckRun(any, any, any)).thenAnswer((_) async => checkRun);
final push_message.BuildPushMessage buildPushMessage = push_message.BuildPushMessage.fromJson(
jsonDecode(
buildPushMessageJsonTemplate('{\\"check_run_id\\": 1,'
'\\"repo_owner\\": \\"flutter\\",'
'\\"repo_name\\": \\"cocoon\\",'
'\\"user_login\\": \\"engine-flutter-autoroll\\"}'),
) as Map<String, dynamic>,
);
when(
mockLuciBuildService.rescheduleBuild(
builderName: 'Linux Coverage',
buildPushMessage: buildPushMessage,
rescheduleAttempt: 1,
),
).thenAnswer(
(_) async => const Build(
id: '8905920700440101120',
builderId: BuilderId(bucket: 'luci.flutter.prod', project: 'flutter', builder: 'Linux Coverage'),
),
);
expect(checkRun.status, github.CheckRunStatus.completed);
await githubChecksService.updateCheckStatus(buildPushMessage, mockLuciBuildService, slug, rescheduled: true);
final List<dynamic> captured = verify(
mockGithubChecksUtil.updateCheckRun(
any,
any,
captureAny,
status: captureAnyNamed('status'),
conclusion: captureAnyNamed('conclusion'),
detailsUrl: anyNamed('detailsUrl'),
output: anyNamed('output'),
),
).captured;
expect(captured.length, 3);
expect(captured[1], github.CheckRunStatus.queued);
expect(captured[2], isNull);
});
test('Should not rerun a failed task for a non roller account', () async {
when(mockGithubChecksUtil.getCheckRun(any, any, any)).thenAnswer((_) async => checkRun);
final push_message.BuildPushMessage buildPushMessage = push_message.BuildPushMessage.fromJson(
jsonDecode(
buildPushMessageJsonTemplate('{\\"check_run_id\\": 1,'
'\\"repo_owner\\": \\"flutter\\",'
'\\"repo_name\\": \\"cocoon\\",'
'\\"user_login\\": \\"test-account\\"}'),
) as Map<String, dynamic>,
);
when(
mockLuciBuildService.rescheduleBuild(
builderName: 'Linux Coverage',
buildPushMessage: buildPushMessage,
rescheduleAttempt: 1,
),
).thenAnswer(
(_) async => const Build(
id: '8905920700440101120',
builderId: BuilderId(bucket: 'luci.flutter.prod', project: 'flutter', builder: 'Linux Coverage'),
),
);
when(
mockLuciBuildService.getBuildById(
'8905920700440101120',
fields: 'id,builder,summaryMarkdown',
),
).thenAnswer(
(_) async => const Build(
id: '8905920700440101120',
builderId: BuilderId(bucket: 'luci.flutter.prod', project: 'flutter', builder: 'Linux Coverage'),
summaryMarkdown: 'test summary',
),
);
await githubChecksService.updateCheckStatus(buildPushMessage, mockLuciBuildService, slug);
final List<dynamic> captured = verify(
mockGithubChecksUtil.updateCheckRun(
any,
any,
any,
status: captureAnyNamed('status'),
conclusion: captureAnyNamed('conclusion'),
detailsUrl: anyNamed('detailsUrl'),
output: captureAnyNamed('output'),
),
).captured;
expect(captured.length, 3);
expect(captured[0], github.CheckRunStatus.completed);
expect(captured[1], github.CheckRunConclusion.failure);
});
});
group('getGithubSummary', () {
test('nonempty summaryMarkdown', () async {
const String summaryMarkdown = 'test';
const String expectedSummary = '$kGithubSummary$summaryMarkdown';
expect(githubChecksService.getGithubSummary(summaryMarkdown), expectedSummary);
});
test('empty summaryMarkdown', () async {
const String expectedSummary = '${kGithubSummary}Empty summaryMarkdown';
expect(githubChecksService.getGithubSummary(null), expectedSummary);
});
test('really large summaryMarkdown', () async {
String summaryMarkdown = '';
for (int i = 0; i < 20000; i++) {
summaryMarkdown += 'test ';
}
expect(githubChecksService.getGithubSummary(summaryMarkdown), startsWith('$kGithubSummary[TRUNCATED...]'));
expect(githubChecksService.getGithubSummary(summaryMarkdown).length, lessThan(65535));
});
});
}
String buildPushMessageJsonTemplate(String jsonUserData) => '''{
"build": {
"bucket": "luci.flutter.prod",
"canary": false,
"canary_preference": "PROD",
"created_by": "user:dnfield@google.com",
"created_ts": "1565049186247524",
"experimental": true,
"id": "8905920700440101120",
"parameters_json": "{\\"builder_name\\": \\"Linux Coverage\\", \\"properties\\": {\\"git_ref\\": \\"refs/pull/37647/head\\", \\"git_url\\": \\"https://github.com/flutter/flutter\\"}}",
"project": "flutter",
"result_details_json": "{\\"properties\\": {}, \\"swarming\\": {\\"bot_dimensions\\": {\\"caches\\": [\\"flutter_openjdk_install\\", \\"git\\", \\"goma_v2\\", \\"vpython\\"], \\"cores\\": [\\"8\\"], \\"cpu\\": [\\"x86\\", \\"x86-64\\", \\"x86-64-Broadwell_GCE\\", \\"x86-64-avx2\\"], \\"gce\\": [\\"1\\"], \\"gpu\\": [\\"none\\"], \\"id\\": [\\"luci-flutter-prod-xenial-2-bnrz\\"], \\"image\\": [\\"chrome-xenial-19052201-9cb74617499\\"], \\"inside_docker\\": [\\"0\\"], \\"kvm\\": [\\"1\\"], \\"locale\\": [\\"en_US.UTF-8\\"], \\"machine_type\\": [\\"n1-standard-8\\"], \\"os\\": [\\"Linux\\", \\"Ubuntu\\", \\"Ubuntu-16.04\\"], \\"pool\\": [\\"luci.flutter.prod\\"], \\"python\\": [\\"2.7.12\\"], \\"server_version\\": [\\"4382-5929880\\"], \\"ssd\\": [\\"0\\"], \\"zone\\": [\\"us\\", \\"us-central\\", \\"us-central1\\", \\"us-central1-c\\"]}}}",
"service_account": "flutter-prod-builder@chops-service-accounts.iam.gserviceaccount.com",
"started_ts": "1565049193786080",
"status": "COMPLETED",
"result": "FAILURE",
"status_changed_ts": "1565049194386647",
"tags": [
"build_address:luci.flutter.prod/Linux Coverage/1698",
"builder:Linux Coverage",
"buildset:pr/git/37647",
"buildset:sha/git/0d78fc94f890a64af140ce0a2671ac5fc636f59b",
"swarming_hostname:chromium-swarm.appspot.com",
"swarming_tag:log_location:logdog://logs.chromium.org/flutter/buildbucket/cr-buildbucket.appspot.com/8905920700440101120/+/annotations",
"swarming_tag:luci_project:flutter",
"swarming_tag:os:Linux",
"swarming_tag:recipe_name:flutter/flutter",
"swarming_tag:recipe_package:infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",
"swarming_task_id:467d04f2f022d510"
],
"updated_ts": "1565049194391321",
"url": "https://ci.chromium.org/b/8905920700440101120",
"utcnow_ts": "1565049194653640"
},
"hostname": "cr-buildbucket.appspot.com",
"user_data": "$jsonUserData"
}''';