blob: fb445fcbbafcc1ae825c66f04d758e89daa0c752 [file] [log] [blame]
// Copyright 2019 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:cocoon_service/src/model/appengine/commit.dart';
import 'package:cocoon_service/src/model/appengine/task.dart';
import 'package:cocoon_service/src/model/ci_yaml/target.dart';
import 'package:cocoon_service/src/request_handlers/refresh_chromebot_status.dart';
import 'package:cocoon_service/src/service/config.dart';
import 'package:cocoon_service/src/service/datastore.dart';
import 'package:cocoon_service/src/service/luci.dart';
import 'package:gcloud/db.dart';
import 'package:googleapis/bigquery/v2.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import '../src/bigquery/fake_tabledata_resource.dart';
import '../src/datastore/fake_config.dart';
import '../src/request_handling/api_request_handler_tester.dart';
import '../src/request_handling/fake_authentication.dart';
import '../src/service/fake_scheduler.dart';
import '../src/utilities/mocks.dart';
void main() {
group('RefreshChromebotStatus', () {
late FakeConfig config;
late ApiRequestHandlerTester tester;
late MockLuciService mockLuciService;
late RefreshChromebotStatus handler;
late MockClient branchHttpClient;
late FakeScheduler scheduler;
late FakeTabledataResource tabledataResource;
late MockLuciBuildService mockLuciBuildService;
late Commit commit;
late List<LuciBuilder> builders;
setUp(() async {
tabledataResource = FakeTabledataResource();
config = FakeConfig(tabledataResource: tabledataResource);
config.flutterBranchesValue = <String>[Config.defaultBranch(Config.flutterSlug)];
tester = ApiRequestHandlerTester();
mockLuciService = MockLuciService();
mockLuciBuildService = MockLuciBuildService();
branchHttpClient = MockClient((_) async => http.Response('', 200));
scheduler = FakeScheduler(
config: config,
ciYaml: exampleConfig,
);
handler = RefreshChromebotStatus(
config,
FakeAuthenticationProvider(),
mockLuciBuildService,
luciServiceProvider: (_) => mockLuciService,
datastoreProvider: (DatastoreDB db) => DatastoreService(config.db, 5),
branchHttpClientProvider: () => branchHttpClient,
gitHubBackoffCalculator: (int attempt) => Duration.zero,
scheduler: scheduler,
);
commit = Commit(
key: config.db.emptyKey.append(Commit, id: 'flutter/flutter/master/abc'),
sha: 'abc',
branch: Config.defaultBranch(Config.flutterSlug),
repository: Config.flutterSlug.fullName,
);
builders = await scheduler.getPostSubmitBuilders(exampleConfig);
});
group('without builder rerun', () {
setUp(() {
when(mockLuciBuildService.checkRerunBuilder(
commit: anyNamed('commit'),
target: anyNamed('target'),
task: anyNamed('task'),
datastore: anyNamed('datastore'),
ignoreChecks: false,
)).thenAnswer((_) => Future<bool>.value(false));
});
test('do not update task status when SHA does not match', () async {
final Task task = Task(
key: commit.key.append(Task, id: 123),
commitKey: commit.key,
name: 'Linux A',
status: Task.statusNew,
);
config.db.values[commit.key] = commit;
config.db.values[task.key] = task;
final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> luciTasks = {
for (LuciBuilder builder in builders)
BranchLuciBuilder(luciBuilder: builder, branch: 'master'): <String, List<LuciTask>>{
'def': <LuciTask>[
const LuciTask(
commitSha: 'def',
ref: 'refs/heads/master',
status: Task.statusSucceeded,
buildNumber: 1,
builderName: 'abc')
],
}
};
when(mockLuciService.getBranchRecentTasks(builders: anyNamed('builders'), requireTaskName: true))
.thenAnswer((Invocation invocation) {
return Future<Map<BranchLuciBuilder, Map<String, List<LuciTask>>>>.value(luciTasks);
});
expect(task.status, Task.statusNew);
await tester.get(handler);
expect(task.status, Task.statusNew);
});
test('do not update task status when commitSha/ref is unknown', () async {
final Task task = Task(
key: commit.key.append(Task, id: 123),
commitKey: commit.key,
name: 'Linux A',
status: Task.statusNew,
);
config.db.values[commit.key] = commit;
config.db.values[task.key] = task;
final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> luciTasks = {
for (LuciBuilder builder in builders)
BranchLuciBuilder(luciBuilder: builder, branch: 'master'): <String, List<LuciTask>>{
'def': <LuciTask>[
const LuciTask(
commitSha: 'unknown',
ref: 'unknown',
status: Task.statusSucceeded,
buildNumber: 1,
builderName: 'abc')
],
}
};
when(mockLuciService.getBranchRecentTasks(builders: anyNamed('builders'), requireTaskName: true))
.thenAnswer((Invocation invocation) {
return Future<Map<BranchLuciBuilder, Map<String, List<LuciTask>>>>.value(luciTasks);
});
expect(task.status, Task.statusNew);
await tester.get(handler);
expect(task.status, Task.statusNew);
});
test('do not update task status when branch does not match', () async {
final Commit branchCommit = Commit(
key: config.db.emptyKey.append(Commit, id: 'flutter/flutter/test/abc'),
sha: 'abc',
branch: 'test',
repository: Config.flutterSlug.fullName,
);
final Task task = Task(
key: branchCommit.key.append(Task, id: 123),
commitKey: branchCommit.key,
name: 'Linux A',
status: Task.statusNew,
);
config.db.values[branchCommit.key] = branchCommit;
config.db.values[commit.key] = commit;
config.db.values[task.key] = task;
final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> luciTasks = {
for (LuciBuilder builder in builders)
BranchLuciBuilder(luciBuilder: builder, branch: 'master'): <String, List<LuciTask>>{
'abc': <LuciTask>[
const LuciTask(
commitSha: 'abc',
ref: 'refs/heads/master',
status: Task.statusSucceeded,
buildNumber: 1,
builderName: 'abc')
],
}
};
when(mockLuciService.getBranchRecentTasks(builders: anyNamed('builders'), requireTaskName: true))
.thenAnswer((Invocation invocation) {
return Future<Map<BranchLuciBuilder, Map<String, List<LuciTask>>>>.value(luciTasks);
});
expect(task.status, Task.statusNew);
await tester.get(handler);
expect(task.status, Task.statusNew);
});
test('update task status and buildNumber when buildNumberList does not match', () async {
final Task task =
Task(key: commit.key.append(Task, id: 123), commitKey: commit.key, name: 'Linux A', status: Task.statusNew);
config.db.values[commit.key] = commit;
config.db.values[task.key] = task;
final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> luciTasks = {
for (LuciBuilder builder in builders)
BranchLuciBuilder(luciBuilder: builder, branch: 'master'): <String, List<LuciTask>>{
'abc': <LuciTask>[
const LuciTask(
commitSha: 'abc',
ref: 'refs/heads/master',
status: Task.statusSucceeded,
buildNumber: 1,
builderName: 'abc')
],
}
};
when(mockLuciService.getBranchRecentTasks(builders: anyNamed('builders'), requireTaskName: true))
.thenAnswer((Invocation invocation) {
return Future<Map<BranchLuciBuilder, Map<String, List<LuciTask>>>>.value(luciTasks);
});
expect(task.status, Task.statusNew);
expect(task.buildNumberList, isNull);
await tester.get(handler);
expect(task.status, Task.statusSucceeded);
expect(task.buildNumberList, '1');
});
test('save data to BigQuery when task finishes', () async {
final Task task =
Task(key: commit.key.append(Task, id: 123), commitKey: commit.key, name: 'Linux A', status: Task.statusNew);
config.db.values[commit.key] = commit;
config.db.values[task.key] = task;
final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> luciTasks = {
for (LuciBuilder builder in builders)
BranchLuciBuilder(luciBuilder: builder, branch: 'master'): <String, List<LuciTask>>{
'abc': <LuciTask>[
const LuciTask(
commitSha: 'abc',
ref: 'refs/heads/master',
status: Task.statusSucceeded,
buildNumber: 1,
builderName: 'abc')
],
}
};
when(mockLuciService.getBranchRecentTasks(builders: anyNamed('builders'), requireTaskName: true))
.thenAnswer((Invocation invocation) {
return Future<Map<BranchLuciBuilder, Map<String, List<LuciTask>>>>.value(luciTasks);
});
await tester.get(handler);
final TableDataList tableDataList = await tabledataResource.list('test', 'test', 'test');
expect(tableDataList.totalRows, '1');
});
test('update task status and buildNumber when status does not match', () async {
final Task task = Task(
key: commit.key.append(Task, id: 123),
commitKey: commit.key,
name: 'Linux A',
status: Task.statusNew,
buildNumberList: '1');
config.db.values[commit.key] = commit;
config.db.values[task.key] = task;
final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> luciTasks = {
for (LuciBuilder builder in builders)
BranchLuciBuilder(luciBuilder: builder, branch: 'master'): <String, List<LuciTask>>{
'abc': <LuciTask>[
const LuciTask(
commitSha: 'abc',
ref: 'refs/heads/master',
status: Task.statusSucceeded,
buildNumber: 1,
builderName: 'abc')
],
}
};
when(mockLuciService.getBranchRecentTasks(builders: anyNamed('builders'), requireTaskName: true))
.thenAnswer((Invocation invocation) {
return Future<Map<BranchLuciBuilder, Map<String, List<LuciTask>>>>.value(luciTasks);
});
expect(task.status, Task.statusNew);
await tester.get(handler);
expect(task.status, Task.statusSucceeded);
});
test('update task status with latest status when multilple reruns exist', () async {
final Task task = Task(
key: commit.key.append(Task, id: 123),
commitKey: commit.key,
name: 'Linux A',
status: Task.statusNew,
buildNumberList: '1');
config.db.values[commit.key] = commit;
config.db.values[task.key] = task;
final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> luciTasks = {
for (LuciBuilder builder in builders)
BranchLuciBuilder(luciBuilder: builder, branch: 'master'): <String, List<LuciTask>>{
'abc': <LuciTask>[
const LuciTask(
commitSha: 'abc',
ref: 'refs/heads/master',
status: Task.statusSucceeded,
buildNumber: 2,
builderName: 'abc'),
const LuciTask(
commitSha: 'abc',
ref: 'refs/heads/master',
status: Task.statusFailed,
buildNumber: 1,
builderName: 'abc')
],
}
};
when(mockLuciService.getBranchRecentTasks(builders: anyNamed('builders'), requireTaskName: true))
.thenAnswer((Invocation invocation) {
return Future<Map<BranchLuciBuilder, Map<String, List<LuciTask>>>>.value(luciTasks);
});
expect(task.status, Task.statusNew);
await tester.get(handler);
expect(task.status, Task.statusSucceeded);
expect(task.buildNumberList, '1,2');
});
test('update task status with latest status when ci yaml targets exist', () async {
final Task task = Task(
key: commit.key.append(Task, id: 123),
commitKey: commit.key,
name: 'Linux A',
builderName: 'Linux A',
status: Task.statusNew,
buildNumberList: '1');
config.db.values[commit.key] = commit;
config.db.values[task.key] = task;
scheduler.ciYaml = exampleConfig;
final List<LuciBuilder> builders =
scheduler.ciYaml!.postsubmitTargets.map((Target target) => LuciBuilder.fromTarget(target)).toList();
final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> luciTasks = {
for (LuciBuilder builder in builders)
BranchLuciBuilder(luciBuilder: builder, branch: 'master'): <String, List<LuciTask>>{
'abc': <LuciTask>[
const LuciTask(
commitSha: 'abc',
ref: 'refs/heads/master',
status: Task.statusSucceeded,
buildNumber: 2,
builderName: 'Linux A',
),
],
}
};
when(mockLuciService.getBranchRecentTasks(builders: anyNamed('builders'), requireTaskName: true))
.thenAnswer((Invocation invocation) {
return Future<Map<BranchLuciBuilder, Map<String, List<LuciTask>>>>.value(luciTasks);
});
expect(task.status, Task.statusNew);
await tester.get(handler);
expect(task.status, Task.statusSucceeded);
expect(task.buildNumberList, '2');
});
test('update task status for non master branch', () async {
final Commit branchCommit = Commit(
key: config.db.emptyKey.append(Commit, id: 'flutter/flutter/test/def'),
sha: 'def',
branch: 'test',
repository: Config.flutterSlug.fullName,
);
final Task task = Task(
key: branchCommit.key.append(Task, id: 456),
commitKey: branchCommit.key,
name: 'Linux A',
status: Task.statusNew);
config.flutterBranchesValue = <String>[Config.defaultBranch(Config.flutterSlug), 'test'];
config.db.values[commit.key] = commit;
config.db.values[branchCommit.key] = branchCommit;
config.db.values[task.key] = task;
final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> luciTasks = {
for (LuciBuilder builder in builders)
BranchLuciBuilder(luciBuilder: builder, branch: 'master'): <String, List<LuciTask>>{
'def': <LuciTask>[
const LuciTask(
commitSha: 'def',
ref: 'refs/heads/master',
status: Task.statusFailed,
buildNumber: 1,
builderName: 'abc'),
],
}
};
final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> testLuciTasks = {
for (LuciBuilder builder in builders)
BranchLuciBuilder(luciBuilder: builder, branch: 'test'): <String, List<LuciTask>>{
'def': <LuciTask>[
const LuciTask(
commitSha: 'def',
ref: 'refs/heads/test',
status: Task.statusSucceeded,
buildNumber: 2,
builderName: 'abc')
],
}
};
luciTasks.addAll(testLuciTasks);
when(mockLuciService.getBranchRecentTasks(builders: anyNamed('builders'), requireTaskName: true))
.thenAnswer((Invocation invocation) {
return Future<Map<BranchLuciBuilder, Map<String, List<LuciTask>>>>.value(luciTasks);
});
expect(task.status, Task.statusNew);
await tester.get(handler);
expect(task.status, Task.statusSucceeded);
expect(task.luciBucket, 'luci.flutter.prod');
});
});
group('with builder rerun', () {
setUp(() {
when(mockLuciBuildService.checkRerunBuilder(
commit: anyNamed('commit'),
target: anyNamed('target'),
task: anyNamed('task'),
datastore: anyNamed('datastore'),
)).thenAnswer((_) => Future<bool>.value(true));
});
test('rerun Mac builder when hiting recipe infra failure', () async {
config.maxTaskRetriesValue = 2;
final Task task = Task(
key: commit.key.append(Task, id: 123),
commitKey: commit.key,
name: 'Linux A',
status: Task.statusInProgress,
buildNumberList: '1',
attempts: 0,
builderName: 'Mac abc');
config.db.values[commit.key] = commit;
config.db.values[task.key] = task;
final Map<BranchLuciBuilder, Map<String, List<LuciTask>>> luciTasks = {
for (LuciBuilder builder in <LuciBuilder>[
LuciBuilder(name: 'Mac abc', repo: Config.flutterSlug.name, taskName: 'def', flaky: false)
])
BranchLuciBuilder(luciBuilder: builder, branch: 'master'): <String, List<LuciTask>>{
'abc': <LuciTask>[
const LuciTask(
commitSha: 'abc',
ref: 'refs/heads/master',
status: Task.statusInfraFailure,
buildNumber: 1,
builderName: 'Mac abc')
],
}
};
when(mockLuciService.getBranchRecentTasks(builders: anyNamed('builders'), requireTaskName: true))
.thenAnswer((Invocation invocation) {
return Future<Map<BranchLuciBuilder, Map<String, List<LuciTask>>>>.value(luciTasks);
});
expect(task.status, Task.statusInProgress);
await tester.get(handler);
verify(mockLuciBuildService.checkRerunBuilder(
commit: anyNamed('commit'),
target: anyNamed('target'),
task: anyNamed('task'),
datastore: anyNamed('datastore'),
tags: anyNamed('tags'),
ignoreChecks: false))
.called(1);
});
});
});
}