blob: c4bafbdccaa9d9f31398a864f37f26000707808b [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/firestore/commit.dart';
import 'package:cocoon_service/src/model/firestore/task.dart';
import 'package:cocoon_service/src/service/build_status_provider.dart';
import 'package:cocoon_service/src/service/datastore.dart';
import 'package:github/github.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import '../src/datastore/fake_config.dart';
import '../src/datastore/fake_datastore.dart';
import '../src/utilities/entity_generators.dart';
import '../src/utilities/mocks.dart';
void main() {
group('BuildStatusProvider', () {
late FakeDatastoreDB db;
late BuildStatusService buildStatusService;
FakeConfig config;
DatastoreService datastoreService;
late MockFirestoreService mockFirestoreService;
final RepositorySlug slug = RepositorySlug('flutter', 'flutter');
setUp(() {
db = FakeDatastoreDB();
mockFirestoreService = MockFirestoreService();
config = FakeConfig(dbValue: db, firestoreService: mockFirestoreService);
datastoreService = DatastoreService(config.db, 5);
buildStatusService = BuildStatusService.defaultProvider(datastoreService, mockFirestoreService);
});
group('calculateStatus', () {
List<Commit> commits;
List<Task> tasks1;
List<Task> tasks2;
late int row;
setUp(() {
row = 0;
tasks1 = <Task>[];
tasks2 = <Task>[];
commits = <Commit>[];
when(
mockFirestoreService.queryRecentCommits(
limit: captureAnyNamed('limit'),
slug: captureAnyNamed('slug'),
branch: captureAnyNamed('branch'),
),
).thenAnswer((Invocation invocation) {
return Future<List<Commit>>.value(
commits,
);
});
when(
mockFirestoreService.queryCommitTasks(
captureAny,
),
).thenAnswer((Invocation invocation) {
if (row == 0) {
row++;
return Future<List<Task>>.value(
tasks1,
);
} else {
return Future<List<Task>>.value(
tasks2,
);
}
});
});
test('returns failure if there are no commits', () async {
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.failure(const <String>[]));
});
test('returns success if top commit is all green', () async {
commits = <Commit>[generateFirestoreCommit(1)];
tasks1 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusSucceeded),
];
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.success());
});
test('returns success if top commit is all green followed by red commit', () async {
commits = <Commit>[
generateFirestoreCommit(1),
generateFirestoreCommit(2),
];
tasks1 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusSucceeded),
];
tasks2 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusFailed),
];
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.success());
});
test('returns failure if last commit contains any red tasks', () async {
commits = <Commit>[generateFirestoreCommit(1)];
tasks1 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusFailed),
];
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.failure(const <String>['task2']));
});
test('returns failure if last commit contains any canceled tasks', () async {
commits = <Commit>[generateFirestoreCommit(1)];
tasks1 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusCancelled),
];
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.failure(const <String>['task2']));
});
test('ensure failed task do not have duplicates when last consecutive commits contains red tasks', () async {
commits = <Commit>[
generateFirestoreCommit(1),
generateFirestoreCommit(2),
];
tasks1 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusFailed),
];
tasks2 = tasks1;
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.failure(const <String>['task2']));
});
test('ignores failures on flaky commits', () async {
commits = <Commit>[generateFirestoreCommit(1)];
tasks1 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusFailed, bringup: true),
];
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.success());
});
test('returns success if partial green, and all unfinished tasks were last green', () async {
commits = <Commit>[
generateFirestoreCommit(1),
generateFirestoreCommit(2),
];
tasks1 = <Task>[
generateFirestoreTask(1, status: Task.statusInProgress),
generateFirestoreTask(2, status: Task.statusSucceeded),
];
tasks2 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusSucceeded),
];
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.success());
});
test('returns failure if partial green, and any unfinished task was last red', () async {
commits = <Commit>[
generateFirestoreCommit(1),
generateFirestoreCommit(2),
];
tasks1 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusInProgress),
];
tasks2 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusFailed),
];
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.failure(const <String>['task2']));
});
test('returns failure when green but a task is rerunning', () async {
commits = <Commit>[
generateFirestoreCommit(1),
generateFirestoreCommit(2),
];
tasks1 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusInProgress, attempts: 2),
];
tasks2 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusSucceeded),
];
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.failure(const <String>['task2']));
});
test('returns failure when a task has an infra failure', () async {
commits = <Commit>[
generateFirestoreCommit(1),
generateFirestoreCommit(2),
];
tasks1 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusInfraFailure),
];
tasks2 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusSucceeded),
];
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.failure(const <String>['task2']));
});
test('returns success when all green with a successful rerun', () async {
commits = <Commit>[
generateFirestoreCommit(1),
generateFirestoreCommit(2),
];
tasks1 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusSucceeded, attempts: 2),
];
tasks2 = <Task>[
generateFirestoreTask(1, status: Task.statusSucceeded),
generateFirestoreTask(2, status: Task.statusFailed),
];
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.success());
});
});
});
}