blob: 283addd0673c2056912550008d84a737d89e88d2 [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/service/build_status_provider.dart';
import 'package:cocoon_service/src/service/datastore.dart';
import 'package:gcloud/db.dart';
import 'package:github/github.dart';
import 'package:test/test.dart';
import '../src/datastore/fake_config.dart';
import '../src/datastore/fake_datastore.dart';
import '../src/utilities/entity_generators.dart';
List<Commit> oneCommit = <Commit>[
Commit(
key: Key<String>.emptyKey(Partition('ns')).append(Commit, id: 'sha1'),
repository: 'flutter/flutter',
sha: 'sha1',
),
];
List<Commit> twoCommits = <Commit>[
Commit(
key: Key<String>.emptyKey(Partition('ns')).append(Commit, id: 'sha1'),
repository: 'flutter/flutter',
sha: 'sha1',
),
Commit(
key: Key<String>.emptyKey(Partition('ns')).append(Commit, id: 'sha2'),
repository: 'flutter/flutter',
sha: 'sha2',
),
];
List<Task> allGreen = <Task>[
generateTask(1, status: Task.statusSucceeded),
generateTask(2, status: Task.statusSucceeded),
generateTask(3, status: Task.statusSucceeded),
];
List<Task> allRed = <Task>[
generateTask(1, status: Task.statusFailed),
generateTask(2, status: Task.statusFailed),
generateTask(3, status: Task.statusFailed),
];
List<Task> middleTaskFailed = <Task>[
generateTask(1, status: Task.statusSucceeded),
generateTask(2, status: Task.statusFailed),
generateTask(3, status: Task.statusSucceeded),
];
List<Task> middleTaskFlakyFailed = <Task>[
generateTask(1, status: Task.statusSucceeded),
generateTask(2, status: Task.statusFailed, isFlaky: true),
generateTask(3, status: Task.statusSucceeded),
];
List<Task> middleTaskInProgress = <Task>[
generateTask(1, status: Task.statusSucceeded),
generateTask(2, status: Task.statusInProgress),
generateTask(3, status: Task.statusSucceeded),
];
List<Task> middleTaskRerunning = <Task>[
generateTask(1, status: Task.statusSucceeded),
generateTask(2, status: Task.statusNew, attempts: 2),
generateTask(3, status: Task.statusSucceeded),
];
List<Task> middleTaskRerunGreen = <Task>[
generateTask(1, status: Task.statusSucceeded),
generateTask(2, status: Task.statusSucceeded, attempts: 2),
generateTask(3, status: Task.statusSucceeded),
];
List<Task> middleTaskInfraFailure = <Task>[
generateTask(1, status: Task.statusSucceeded),
generateTask(2, status: Task.statusInfraFailure),
generateTask(3, status: Task.statusSucceeded),
];
void main() {
group('BuildStatusProvider', () {
late FakeDatastoreDB db;
late BuildStatusService buildStatusService;
FakeConfig config;
DatastoreService datastoreService;
final RepositorySlug slug = RepositorySlug('flutter', 'flutter');
setUp(() {
db = FakeDatastoreDB();
config = FakeConfig(dbValue: db);
datastoreService = DatastoreService(config.db, 5);
buildStatusService = BuildStatusService.defaultProvider(datastoreService);
});
group('calculateStatus', () {
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 {
db.addOnQuery<Commit>((Iterable<Commit> results) => oneCommit);
db.addOnQuery<Task>((Iterable<Task> results) => allGreen);
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 {
db.addOnQuery<Commit>((Iterable<Commit> results) => twoCommits);
int row = 0;
db.addOnQuery<Task>((Iterable<Task> results) {
return row++ == 0 ? allGreen : middleTaskFailed;
});
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.success());
});
test('returns failure if last commit contains any red tasks', () async {
db.addOnQuery<Commit>((Iterable<Commit> results) => oneCommit);
db.addOnQuery<Task>((Iterable<Task> results) => middleTaskFailed);
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 {
db.addOnQuery<Commit>((Iterable<Commit> results) => twoCommits);
db.addOnQuery<Task>((Iterable<Task> results) => middleTaskFailed);
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.failure(const <String>['task2']));
});
test('ignores failures on flaky commits', () async {
db.addOnQuery<Commit>((Iterable<Commit> results) => oneCommit);
db.addOnQuery<Task>((Iterable<Task> results) => middleTaskFlakyFailed);
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 {
db.addOnQuery<Commit>((Iterable<Commit> results) => twoCommits);
int row = 0;
db.addOnQuery<Task>((Iterable<Task> results) {
return row++ == 0 ? middleTaskInProgress : allGreen;
});
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 {
db.addOnQuery<Commit>((Iterable<Commit> results) => twoCommits);
int row = 0;
db.addOnQuery<Task>((Iterable<Task> results) {
return row++ == 0 ? middleTaskInProgress : middleTaskFailed;
});
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 {
db.addOnQuery<Commit>((Iterable<Commit> results) => twoCommits);
int row = 0;
db.addOnQuery<Task>((Iterable<Task> results) {
return row++ == 0 ? middleTaskRerunning : allGreen;
});
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 {
db.addOnQuery<Commit>((Iterable<Commit> results) => twoCommits);
int row = 0;
db.addOnQuery<Task>((Iterable<Task> results) {
return row++ == 0 ? middleTaskInfraFailure : allGreen;
});
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 {
db.addOnQuery<Commit>((Iterable<Commit> results) => twoCommits);
int row = 0;
db.addOnQuery<Task>((Iterable<Task> results) {
return row++ == 0 ? middleTaskRerunGreen : allRed;
});
final BuildStatus? status = await buildStatusService.calculateCumulativeStatus(slug);
expect(status, BuildStatus.success());
});
test('return status when with branch parameter', () async {
final Commit commit1 = generateCommit(1, branch: 'flutter-0.0-candidate.0');
final Commit commit2 = generateCommit(2, branch: 'master');
db.values[commit1.key] = commit1;
db.values[commit2.key] = commit2;
final Task task1 = Task(
key: commit1.key.append(Task, id: 1),
commitKey: commit1.key,
name: 'task1',
status: Task.statusSucceeded,
stageName: 'stage1');
db.values[task1.key] = task1;
// Test master branch.
final List<CommitStatus> statuses1 = await buildStatusService
.retrieveCommitStatus(
limit: 5,
slug: slug,
)
.toList();
expect(statuses1.length, 1);
expect(statuses1.first.commit.branch, 'master');
// Test dev branch.
final List<CommitStatus> statuses2 = await buildStatusService
.retrieveCommitStatus(
limit: 5,
branch: 'flutter-0.0-candidate.0',
slug: slug,
)
.toList();
expect(statuses2.length, 1);
expect(statuses2.first.commit.branch, 'flutter-0.0-candidate.0');
});
});
});
}