blob: 7f81f5f6a27ca2449ec254afcd67adfc4f998078 [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/cocoon_service.dart';
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/model/luci/buildbucket.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/request_handling/fake_pubsub.dart';
import '../../src/request_handling/request_handler_tester.dart';
import '../../src/service/fake_luci_build_service.dart';
import '../../src/service/fake_scheduler.dart';
import '../../src/utilities/entity_generators.dart';
import '../../src/utilities/mocks.dart';
final List<Commit> commits = <Commit>[
generateCommit(3),
generateCommit(2),
generateCommit(1),
];
void main() {
late BatchBackfiller handler;
late RequestHandlerTester tester;
late FakeDatastoreDB db;
late FakePubSub pubsub;
late FakeScheduler scheduler;
late MockGithubChecksUtil mockGithubChecksUtil;
late Config config;
group('BatchBackfiller', () {
setUp(() async {
db = FakeDatastoreDB()..addOnQuery<Commit>((Iterable<Commit> results) => commits);
config = FakeConfig(dbValue: db, backfillerTargetLimitValue: 2);
pubsub = FakePubSub();
mockGithubChecksUtil = MockGithubChecksUtil();
when(
mockGithubChecksUtil.createCheckRun(
any,
any,
any,
any,
output: anyNamed('output'),
),
).thenAnswer((_) async => generateCheckRun(1));
scheduler = FakeScheduler(
config: config,
ciYaml: batchPolicyConfig,
githubChecksUtil: mockGithubChecksUtil,
luciBuildService: FakeLuciBuildService(
config: config,
pubsub: pubsub,
githubChecksUtil: mockGithubChecksUtil,
),
);
handler = BatchBackfiller(
config: config,
scheduler: scheduler,
);
tester = RequestHandlerTester();
});
test('does not backfill on completed task column', () async {
final List<Task> allGreen = <Task>[
generateTask(1, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(2, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(3, name: 'Linux_android A', status: Task.statusSucceeded),
];
db.addOnQuery<Task>((Iterable<Task> results) => allGreen);
await tester.get(handler);
expect(pubsub.messages, isEmpty);
});
test('does not backfill when there is a running task', () async {
final List<Task> middleTaskInProgress = <Task>[
generateTask(1, name: 'Linux_android A', status: Task.statusNew),
generateTask(2, name: 'Linux_android A', status: Task.statusInProgress),
generateTask(3, name: 'Linux_android A', status: Task.statusNew),
];
db.addOnQuery<Task>((Iterable<Task> results) => middleTaskInProgress);
await tester.get(handler);
expect(pubsub.messages, isEmpty);
});
test('does not backfill when task does not exist in TOT', () async {
scheduler = FakeScheduler(
config: config,
ciYaml: notInToTConfig,
githubChecksUtil: mockGithubChecksUtil,
luciBuildService: FakeLuciBuildService(
config: config,
pubsub: pubsub,
githubChecksUtil: mockGithubChecksUtil,
),
);
handler = BatchBackfiller(
config: config,
scheduler: scheduler,
);
final List<Task> allGray = <Task>[
generateTask(1, name: 'Linux_android B', status: Task.statusNew),
];
db.addOnQuery<Task>((Iterable<Task> results) => allGray);
await tester.get(handler);
expect(pubsub.messages.length, 0);
});
test('backfills latest task', () async {
final List<Task> allGray = <Task>[
generateTask(1, name: 'Linux_android A', status: Task.statusNew),
generateTask(2, name: 'Linux_android A', status: Task.statusNew),
generateTask(3, name: 'Linux_android A', status: Task.statusNew),
];
db.addOnQuery<Task>((Iterable<Task> results) => allGray);
await tester.get(handler);
expect(pubsub.messages.length, 1);
final ScheduleBuildRequest scheduleBuildRequest =
(pubsub.messages.single as BatchRequest).requests!.single.scheduleBuild!;
expect(scheduleBuildRequest.priority, LuciBuildService.kBackfillPriority);
});
test('does not backfill targets when number of available tasks is less than BatchPolicy.kBatchSize', () async {
final List<Task> scheduleA = <Task>[
// Linux_android A
generateTask(1, name: 'Linux_android A', status: Task.statusNew),
];
db.addOnQuery<Task>((Iterable<Task> results) => scheduleA);
await tester.get(handler);
expect(pubsub.messages.length, 0);
});
test('backfills earlier failed task with higher priority', () async {
final List<Task> allGray = <Task>[
generateTask(1, name: 'Linux_android A', status: Task.statusNew),
generateTask(2, name: 'Linux_android A', status: Task.statusNew),
generateTask(3, name: 'Linux_android A', status: Task.statusFailed),
];
db.addOnQuery<Task>((Iterable<Task> results) => allGray);
await tester.get(handler);
expect(pubsub.messages.length, 1);
final ScheduleBuildRequest scheduleBuildRequest =
(pubsub.messages.single as BatchRequest).requests!.single.scheduleBuild!;
expect(scheduleBuildRequest.priority, LuciBuildService.kRerunPriority);
});
test('backfills task successfully with retry', () async {
pubsub.exceptionFlag = true;
pubsub.exceptionRepetition = 1;
final List<Task> allGray = <Task>[
generateTask(1, name: 'Linux_android A', status: Task.statusNew),
generateTask(2, name: 'Linux_android A', status: Task.statusNew),
generateTask(3, name: 'Linux_android A', status: Task.statusFailed),
];
db.addOnQuery<Task>((Iterable<Task> results) => allGray);
await tester.get(handler);
expect(pubsub.messages.length, 1);
final ScheduleBuildRequest scheduleBuildRequest =
(pubsub.messages.single as BatchRequest).requests!.single.scheduleBuild!;
expect(scheduleBuildRequest.priority, LuciBuildService.kRerunPriority);
});
test('fails to backfill tasks when retry limit is hit', () async {
pubsub.exceptionFlag = true;
pubsub.exceptionRepetition = 3;
final List<Task> allGray = <Task>[
generateTask(1, name: 'Linux_android A', status: Task.statusNew),
generateTask(2, name: 'Linux_android A', status: Task.statusNew),
generateTask(3, name: 'Linux_android A', status: Task.statusFailed),
];
db.addOnQuery<Task>((Iterable<Task> results) => allGray);
await tester.get(handler);
expect(pubsub.messages.length, 0);
});
test('backfills older task', () async {
final List<Task> oldestGray = <Task>[
generateTask(1, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(2, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(3, name: 'Linux_android A', status: Task.statusNew),
];
db.addOnQuery<Task>((Iterable<Task> results) => oldestGray);
await tester.get(handler);
expect(pubsub.messages.length, 1);
});
test('updates task as in-progress after backfilling', () async {
final List<Task> oldestGray = <Task>[
generateTask(1, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(2, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(3, name: 'Linux_android A', status: Task.statusNew),
];
db.addOnQuery<Task>((Iterable<Task> results) => oldestGray);
final Task task = oldestGray[2];
expect(db.values.length, 0);
expect(task.status, Task.statusNew);
await tester.get(handler);
expect(db.values.length, 1);
expect(task.status, Task.statusInProgress);
});
test('skip scheduling builds if datastore commit fails', () async {
db.commitException = true;
final List<Task> oldestGray = <Task>[
generateTask(1, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(2, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(3, name: 'Linux_android A', status: Task.statusNew),
];
db.addOnQuery<Task>((Iterable<Task> results) => oldestGray);
expect(db.values.length, 0);
await tester.get(handler);
expect(db.values.length, 0);
expect(pubsub.messages.length, 0);
});
test('backfills only column A when B does need backfill', () async {
final List<Task> scheduleA = <Task>[
// Linux_android A
generateTask(1, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(2, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(3, name: 'Linux_android A', status: Task.statusNew),
// Linux_android B
generateTask(1, name: 'Linux_android B', status: Task.statusSucceeded),
generateTask(2, name: 'Linux_android B', status: Task.statusSucceeded),
generateTask(3, name: 'Linux_android B', status: Task.statusSucceeded),
];
db.addOnQuery<Task>((Iterable<Task> results) => scheduleA);
await tester.get(handler);
expect(pubsub.messages.length, 1);
});
test('backfills both column A and B', () async {
final List<Task> scheduleA = <Task>[
// Linux_android A
generateTask(1, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(2, name: 'Linux_android A', status: Task.statusSucceeded),
generateTask(3, name: 'Linux_android A', status: Task.statusNew),
// Linux_android B
generateTask(1, name: 'Linux_android B', status: Task.statusSucceeded),
generateTask(2, name: 'Linux_android B', status: Task.statusSucceeded),
generateTask(3, name: 'Linux_android B', status: Task.statusNew),
];
db.addOnQuery<Task>((Iterable<Task> results) => scheduleA);
await tester.get(handler);
expect(pubsub.messages.length, 2);
});
test('backfills limited targets when number of available targets exceeds backfillerTargetLimit ', () async {
final List<Task> scheduleA = <Task>[
// Linux_android A
generateTask(1, name: 'Linux_android A', status: Task.statusNew),
generateTask(2, name: 'Linux_android A', status: Task.statusNew),
// Linux_android B
generateTask(1, name: 'Linux_android B', status: Task.statusNew),
generateTask(2, name: 'Linux_android B', status: Task.statusNew),
// Linux_android C
generateTask(1, name: 'Linux_android C', status: Task.statusNew),
generateTask(2, name: 'Linux_android C', status: Task.statusNew),
];
db.addOnQuery<Task>((Iterable<Task> results) => scheduleA);
await tester.get(handler);
expect(pubsub.messages.length, 2);
});
group('getFilteredBackfill', () {
test('backfills high priorty targets first', () async {
final List<Tuple<Target, FullTask, int>> backfill = <Tuple<Target, FullTask, int>>[
Tuple(generateTarget(1), FullTask(generateTask(1), generateCommit(1)), LuciBuildService.kRerunPriority),
Tuple(generateTarget(2), FullTask(generateTask(2), generateCommit(2)), LuciBuildService.kBackfillPriority),
Tuple(generateTarget(3), FullTask(generateTask(3), generateCommit(3)), LuciBuildService.kRerunPriority),
];
final List<Tuple<Target, FullTask, int>> filteredBackfill = handler.getFilteredBackfill(backfill);
expect(filteredBackfill.length, 2);
expect(filteredBackfill[0].third, LuciBuildService.kRerunPriority);
expect(filteredBackfill[1].third, LuciBuildService.kRerunPriority);
});
});
});
}