blob: 4f973e7662f5013742dc94e2cbb00fa35c62551e [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 'dart:core';
import 'package:cocoon_service/src/model/appengine/service_account_info.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/request_handling/exceptions.dart';
import 'package:cocoon_service/src/service/luci_build_service.dart';
import 'package:github/github.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import '../src/datastore/fake_cocoon_config.dart';
import '../src/request_handling/fake_logging.dart';
import '../src/utilities/mocks.dart';
import '../src/utilities/push_message.dart';
void main() {
ServiceAccountInfo serviceAccountInfo;
FakeConfig config;
MockBuildBucketClient mockBuildBucketClient;
LuciBuildService service;
RepositorySlug slug;
group('buildsForRepositoryAndPr', () {
const Build macBuild = Build(
id: 999,
builderId: BuilderId(
project: 'flutter',
bucket: 'prod',
builder: 'Mac',
),
status: Status.started,
);
const Build linuxBuild = Build(
id: 998,
builderId: BuilderId(
project: 'flutter',
bucket: 'prod',
builder: 'Linux',
),
status: Status.started,
);
setUp(() {
serviceAccountInfo = const ServiceAccountInfo(email: 'abc@abcd.com');
config = FakeConfig(deviceLabServiceAccountValue: serviceAccountInfo);
mockBuildBucketClient = MockBuildBucketClient();
service =
LuciBuildService(config, mockBuildBucketClient, serviceAccountInfo);
slug = RepositorySlug('flutter', 'cocoon');
});
test('Empty responses are handled correctly', () async {
when(mockBuildBucketClient.batch(any)).thenAnswer((_) async {
return const BatchResponse(
responses: <Response>[
Response(
searchBuilds: SearchBuildsResponse(
builds: <Build>[],
),
),
],
);
});
final Map<String, Build> builds =
await service.buildsForRepositoryAndPr(slug, 1, 'abcd');
expect(builds.keys, isEmpty);
});
test('Response returning a couple of builds', () async {
when(mockBuildBucketClient.batch(any)).thenAnswer((_) async {
return const BatchResponse(
responses: <Response>[
Response(
searchBuilds: SearchBuildsResponse(
builds: <Build>[macBuild],
),
),
Response(
searchBuilds: SearchBuildsResponse(
builds: <Build>[linuxBuild],
),
),
],
);
});
final Map<String, Build> builds =
await service.buildsForRepositoryAndPr(slug, 1, 'abcd');
expect(builds,
equals(<String, Build>{'Mac': macBuild, 'Linux': linuxBuild}));
});
});
group('scheduleBuilds', () {
setUp(() {
serviceAccountInfo = const ServiceAccountInfo(email: 'abc@abcd.com');
config = FakeConfig(deviceLabServiceAccountValue: serviceAccountInfo);
mockBuildBucketClient = MockBuildBucketClient();
service =
LuciBuildService(config, mockBuildBucketClient, serviceAccountInfo);
service.setLogger(FakeLogging());
slug = RepositorySlug('flutter', 'cocoon');
});
test('try to schedule builds already started', () async {
when(mockBuildBucketClient.batch(any)).thenAnswer((_) async {
return const BatchResponse(
responses: <Response>[
Response(
searchBuilds: SearchBuildsResponse(
builds: <Build>[
Build(
id: 998,
builderId: BuilderId(
project: 'flutter',
bucket: 'prod',
builder: 'Linux',
),
status: Status.started,
)
],
),
),
],
);
});
final bool result = await service.scheduleBuilds(
prNumber: 1,
commitSha: 'abc',
slug: slug,
);
expect(result, isFalse);
});
test('try to schedule builds already scheduled', () async {
when(mockBuildBucketClient.batch(any)).thenAnswer((_) async {
return const BatchResponse(
responses: <Response>[
Response(
searchBuilds: SearchBuildsResponse(
builds: <Build>[
Build(
id: 998,
builderId: BuilderId(
project: 'flutter',
bucket: 'prod',
builder: 'Linux',
),
status: Status.scheduled,
)
],
),
),
],
);
});
final bool result = await service.scheduleBuilds(
prNumber: 1,
commitSha: 'abc',
slug: slug,
);
expect(result, isFalse);
});
test('Schedule builds when the current list of builds is empty', () async {
when(mockBuildBucketClient.batch(any)).thenAnswer((_) async {
return const BatchResponse(
responses: <Response>[],
);
});
config.luciTryBuildersValue =
(json.decode('[{"name": "Cocoon", "repo": "cocoon"}]')
as List<dynamic>)
.cast<Map<String, dynamic>>();
final bool result = await service.scheduleBuilds(
prNumber: 1,
commitSha: 'abc',
slug: slug,
);
expect(result, isTrue);
});
test('Try to schedule build on a unsupported repo', () async {
slug = RepositorySlug('flutter', 'notsupported');
expect(
() async => await service.scheduleBuilds(
prNumber: 1,
commitSha: 'abc',
slug: slug,
),
throwsA(const TypeMatcher<BadRequestException>()));
});
});
group('cancelBuilds', () {
setUp(() {
serviceAccountInfo = const ServiceAccountInfo(email: 'abc@abcd.com');
config = FakeConfig(deviceLabServiceAccountValue: serviceAccountInfo);
mockBuildBucketClient = MockBuildBucketClient();
service =
LuciBuildService(config, mockBuildBucketClient, serviceAccountInfo);
slug = RepositorySlug('flutter', 'cocoon');
});
test('Cancel builds when build list is empty', () async {
when(mockBuildBucketClient.batch(any)).thenAnswer((_) async {
return const BatchResponse(
responses: <Response>[],
);
});
await service.cancelBuilds(slug, 1, 'abc', 'new builds');
verify(mockBuildBucketClient.batch(any)).called(1);
});
test('Cancel builds that are scheduled', () async {
when(mockBuildBucketClient.batch(any)).thenAnswer((_) async {
return const BatchResponse(
responses: <Response>[
Response(
searchBuilds: SearchBuildsResponse(builds: <Build>[
Build(
id: 998,
builderId: BuilderId(
project: 'flutter',
bucket: 'prod',
builder: 'Linux',
),
status: Status.started,
)
]))
],
);
});
await service.cancelBuilds(slug, 1, 'abc', 'new builds');
expect(
verify(mockBuildBucketClient.batch(captureAny))
.captured[1]
.requests[0]
.cancelBuild
.toJson(),
json.decode('{"id": "998", "summaryMarkdown": "new builds"}'));
});
test('Cancel builds from unsuported repo', () async {
slug = RepositorySlug('flutter', 'notsupported');
expect(
() async => await service.cancelBuilds(
slug,
1,
'abc',
'new builds',
),
throwsA(const TypeMatcher<BadRequestException>()));
});
});
group('failedBuilds', () {
setUp(() {
serviceAccountInfo = const ServiceAccountInfo(email: 'abc@abcd.com');
config = FakeConfig(deviceLabServiceAccountValue: serviceAccountInfo);
mockBuildBucketClient = MockBuildBucketClient();
service =
LuciBuildService(config, mockBuildBucketClient, serviceAccountInfo);
slug = RepositorySlug('flutter', 'cocoon');
});
test('Failed builds from an empty list', () async {
when(mockBuildBucketClient.batch(any)).thenAnswer((_) async {
return const BatchResponse(
responses: <Response>[],
);
});
config.luciTryBuildersValue = <Map<String, dynamic>>[];
final List<Build> result = await service.failedBuilds(slug, 1, 'abc');
expect(result, isEmpty);
});
test('Failed builds from a list of builds with failures', () async {
when(mockBuildBucketClient.batch(any)).thenAnswer((_) async {
return const BatchResponse(
responses: <Response>[
Response(
searchBuilds: SearchBuildsResponse(builds: <Build>[
Build(
id: 998,
builderId: BuilderId(
project: 'flutter',
bucket: 'prod',
builder: 'Linux',
),
status: Status.failure,
)
]))
],
);
});
config.luciTryBuildersValue = (json.decode(
'[{"name": "Linux", "repo": "flutter", "taskName": "linux_bot"}]')
as List<dynamic>)
.cast<Map<String, dynamic>>();
final List<Build> result = await service.failedBuilds(slug, 1, 'abc');
expect(result, hasLength(1));
});
});
group('rescheduleBuild', () {
push_message.BuildPushMessage buildPushMessage;
setUp(() {
serviceAccountInfo = const ServiceAccountInfo(email: 'abc@abcd.com');
config = FakeConfig(deviceLabServiceAccountValue: serviceAccountInfo);
mockBuildBucketClient = MockBuildBucketClient();
service =
LuciBuildService(config, mockBuildBucketClient, serviceAccountInfo);
config.luciTryInfraFailureRetriesValue = 3;
final Map<String, dynamic> json = jsonDecode(buildPushMessageString(
'COMPLETED',
result: 'FAILURE',
builderName: 'Linux Host Engine',
)) as Map<String, dynamic>;
buildPushMessage = push_message.BuildPushMessage.fromJson(json);
});
test('Reschedule an existing build', () async {
final bool rescheduled = await service.rescheduleBuild(
commitSha: 'abc',
builderName: 'mybuild',
buildPushMessage: buildPushMessage,
);
expect(rescheduled, isTrue);
verify(mockBuildBucketClient.scheduleBuild(any)).called(1);
});
});
}