Rerun devicelab task from test runner (#86394)

This commit is contained in:
keyonghan 2021-07-14 15:23:10 -07:00 committed by GitHub
parent d056500bfe
commit 4d96a3fd50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 138 additions and 21 deletions

View File

@ -27,7 +27,9 @@ When a device in the lab is free, it will pickup tasks that need to be completed
1. If the task succeeds, the test runner reports the success and uploads its performance metrics to Flutter's infrastructure. Not 1. If the task succeeds, the test runner reports the success and uploads its performance metrics to Flutter's infrastructure. Not
all tasks record performance metrics. all tasks record performance metrics.
2. If the task fails, the test runner reports the failure to Flutter's infrastructure and no performance metrics are collected 2. If task fails, an auto rerun happens. Whenever the last run succeeds, the task will be reported as a success. For this case,
a flake will be flagged and populated to the test result.
3. If the task fails in all reruns, the test runner reports the failure to Flutter's infrastructure and no performance metrics are collected
## Running tests locally ## Running tests locally

View File

@ -47,6 +47,9 @@ class Cocoon {
/// Url used to send results to. /// Url used to send results to.
static const String baseCocoonApiUrl = 'https://flutter-dashboard.appspot.com/api'; static const String baseCocoonApiUrl = 'https://flutter-dashboard.appspot.com/api';
/// Threshold to auto retry a failed test.
static const int retryNumber = 2;
/// Underlying [FileSystem] to use. /// Underlying [FileSystem] to use.
final FileSystem fs; final FileSystem fs;

View File

@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
// import 'dart:core' as core;
import 'dart:io'; import 'dart:io';
import 'package:flutter_devicelab/common.dart'; import 'package:flutter_devicelab/common.dart';
@ -15,6 +16,16 @@ import 'devices.dart';
import 'task_result.dart'; import 'task_result.dart';
import 'utils.dart'; import 'utils.dart';
/// Run a list of tasks.
///
/// For each task, an auto rerun will be triggered when task fails.
///
/// If the task succeeds the first time, it will be recorded as successful.
///
/// If the task fails first, but gets passed in the end, the
/// test will be recorded as successful but with a flake flag.
///
/// If the task fails all reruns, it will be recorded as failed.
Future<void> runTasks( Future<void> runTasks(
List<String> taskNames, { List<String> taskNames, {
bool exitOnFirstTestFailure = false, bool exitOnFirstTestFailure = false,
@ -26,8 +37,68 @@ Future<void> runTasks(
String? luciBuilder, String? luciBuilder,
String? resultsPath, String? resultsPath,
List<String>? taskArgs, List<String>? taskArgs,
@visibleForTesting Map<String, String>? isolateParams,
@visibleForTesting Function(String) print = print,
@visibleForTesting List<String>? logs,
}) async { }) async {
for (final String taskName in taskNames) { for (final String taskName in taskNames) {
TaskResult result = TaskResult.success(null);
int retry = 0;
while (retry <= Cocoon.retryNumber) {
result = await rerunTask(
taskName,
deviceId: deviceId,
localEngine: localEngine,
localEngineSrcPath: localEngineSrcPath,
silent: silent,
taskArgs: taskArgs,
resultsPath: resultsPath,
gitBranch: gitBranch,
luciBuilder: luciBuilder,
isolateParams: isolateParams,
);
section('Flaky status for "$taskName"');
if (!result.succeeded) {
retry++;
} else {
if (retry > 0) {
print('Total ${retry+1} executions: $retry failures and 1 success');
print('flaky: true');
} else {
print('Total ${retry+1} executions: 1 success');
print('flaky: false');
}
break;
}
}
if (!result.succeeded) {
print('Total $retry executions: 0 success');
print('flaky: false');
exitCode = 1;
if (exitOnFirstTestFailure) {
return;
}
}
}
}
/// A rerun wrapper for `runTask`.
///
/// This separates reruns in separate sections.
Future<TaskResult> rerunTask(
String taskName, {
String? deviceId,
String? localEngine,
String? localEngineSrcPath,
bool silent = false,
List<String>? taskArgs,
String? resultsPath,
String? gitBranch,
String? luciBuilder,
@visibleForTesting Map<String, String>? isolateParams,
}) async {
section('Running task "$taskName"'); section('Running task "$taskName"');
final TaskResult result = await runTask( final TaskResult result = await runTask(
taskName, taskName,
@ -36,6 +107,7 @@ Future<void> runTasks(
localEngineSrcPath: localEngineSrcPath, localEngineSrcPath: localEngineSrcPath,
silent: silent, silent: silent,
taskArgs: taskArgs, taskArgs: taskArgs,
isolateParams: isolateParams,
); );
print('Task result:'); print('Task result:');
@ -51,14 +123,7 @@ Future<void> runTasks(
resultsPath: resultsPath, resultsPath: resultsPath,
); );
} }
return result;
if (!result.succeeded) {
exitCode = 1;
if (exitOnFirstTestFailure) {
return;
}
}
}
} }
/// Runs a task in a separate Dart VM and collects the result using the VM /// Runs a task in a separate Dart VM and collects the result using the VM

View File

@ -0,0 +1,47 @@
// Copyright 2014 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.
// @dart = 2.8
import 'package:flutter_devicelab/framework/runner.dart';
import 'common.dart';
void main() {
final Map<String, String> isolateParams = <String, String>{
'runFlutterConfig': 'false',
'runProcessCleanup': 'false',
'timeoutInMinutes': '1',
};
List<String> printLog;
void print(String s) => printLog.add(s);
group('run.dart script', () {
test('Reruns - Test passes the first time.', () async {
printLog = <String>[];
await runTasks(
<String>['smoke_test_success'],
isolateParams: isolateParams,
print: print,
logs: printLog,
);
expect(printLog.length, 2);
expect(printLog[0], 'Total 1 executions: 1 success');
expect(printLog[1], 'flaky: false');
});
test('Reruns - Test fails all reruns.', () async {
printLog = <String>[];
await runTasks(
<String>['smoke_test_failure'],
isolateParams: isolateParams,
print: print,
logs: printLog,
);
expect(printLog.length, 2);
expect(printLog[0], 'Total 3 executions: 0 success');
expect(printLog[1], 'flaky: false');
});
});
}