mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Rebase ios-experimental branch onto main. This will make the PRs experimenting with newer versions of Xcode (like https://github.com/flutter/flutter/pull/173123) smaller and easier to reason about. Rebases #168860 and #170274 ``` $ git rebase main -Xtheirs ``` --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Co-authored-by: Siva <a-siva@users.noreply.github.com> Co-authored-by: engine-flutter-autoroll <engine-flutter-autoroll@skia.org> Co-authored-by: Jamil Saadeh <jssaadeh@outlook.com> Co-authored-by: Dara Adedeji <76637177+SunkenInTime@users.noreply.github.com> Co-authored-by: Greg Price <gnprice@gmail.com> Co-authored-by: Ben Konyi <bkonyi@google.com> Co-authored-by: Ricardo Dalarme <ricardodalarme@outlook.com> Co-authored-by: Flutter GitHub Bot <fluttergithubbot@gmail.com> Co-authored-by: Justin McCandless <jmccandless@google.com> Co-authored-by: Alex Talebi <31685655+SalehTZ@users.noreply.github.com> Co-authored-by: Qun Cheng <36861262+QuncCccccc@users.noreply.github.com> Co-authored-by: Mouad Debbar <mdebbar@google.com> Co-authored-by: Zuckjet <1083941774@qq.com> Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com> Co-authored-by: auto-submit[bot] <98614782+auto-submit[bot]@users.noreply.github.com> Co-authored-by: auto-submit[bot] <flutter-engprod-team@google.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: yim <ybz975218925@gmail.com> Co-authored-by: bufffun <chenmingding.cmd@alibaba-inc.com> Co-authored-by: Chinmay Garde <chinmaygarde@google.com> Co-authored-by: Hannah Jin <jhy03261997@gmail.com> Co-authored-by: Kate Lovett <katelovett@google.com> Co-authored-by: Valentin Vignal <32538273+ValentinVignal@users.noreply.github.com> Co-authored-by: Derek Xu <derekx@google.com> Co-authored-by: Yash Dhrangdhariya <72062416+Yash-Dhrangdhariya@users.noreply.github.com> Co-authored-by: bungeman <bungeman@chromium.org> Co-authored-by: Ahmed Mohamed Sameh <ahmedsameha1@gmail.com> Co-authored-by: John "codefu" McDole <codefu@google.com> Co-authored-by: Dmitry Grand <dmgr@google.com> Co-authored-by: Kostia Sokolovskyi <sokolovskyi.konstantin@gmail.com> Co-authored-by: Reid Baker <1063596+reidbaker@users.noreply.github.com> Co-authored-by: Matthew Kosarek <matt.kosarek@canonical.com> Co-authored-by: Jason Simmons <jason-simmons@users.noreply.github.com> Co-authored-by: Jim Graham <flar@google.com> Co-authored-by: Michael Goderbauer <goderbauer@google.com> Co-authored-by: Gray Mackall <34871572+gmackall@users.noreply.github.com> Co-authored-by: Gray Mackall <mackall@google.com> Co-authored-by: Tong Mu <dkwingsmt@users.noreply.github.com> Co-authored-by: Jon Ihlas <jon.i@hotmail.fr> Co-authored-by: Micael Cid <micaelcid10@gmail.com> Co-authored-by: Alexander Aprelev <aam@google.com> Co-authored-by: hellohuanlin <41930132+hellohuanlin@users.noreply.github.com> Co-authored-by: Luke Memet <1598289+lukemmtt@users.noreply.github.com> Co-authored-by: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Co-authored-by: Mairramer <50643541+Mairramer@users.noreply.github.com> Co-authored-by: Florin Malita <fmalita@gmail.com> Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com> Co-authored-by: Salem Iranloye <127918074+salemiranloye@users.noreply.github.com> Co-authored-by: Kevin Moore <kevmoo@google.com> Co-authored-by: Sydney Bao <sydneybao@google.com> Co-authored-by: Wdestroier <Wdestroier@gmail.com> Co-authored-by: Matt Boetger <matt.boetger@gmail.com> Co-authored-by: Reid Baker <reidbaker@google.com> Co-authored-by: Victor Sanni <victorsanniay@gmail.com> Co-authored-by: Jessy Yameogo <jessy.yameogo@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: romain.gyh <11901536+romaingyh@users.noreply.github.com> Co-authored-by: Robert Ancell <robert.ancell@canonical.com> Co-authored-by: TheLastFlame <131446187+TheLastFlame@users.noreply.github.com> Co-authored-by: masato <returnymgstokh@icloud.com> Co-authored-by: Albin PK <56157868+albinpk@users.noreply.github.com> Co-authored-by: Huy <huy@nevercode.io> Co-authored-by: Matan Lurey <matanlurey@users.noreply.github.com> Co-authored-by: Azat Chorekliyev <azat24680@gmail.com> Co-authored-by: EdwynZN <edwinzn9@gmail.com> Co-authored-by: Bruno Leroux <bruno.leroux@gmail.com> Co-authored-by: Dev TtangKong <ttankkeo112@gmail.com> Co-authored-by: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Co-authored-by: Houssem Eddine Fadhli <houssemeddinefadhli81@gmail.com>
334 lines
10 KiB
Dart
334 lines
10 KiB
Dart
// 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.
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:meta/meta.dart';
|
|
import 'package:vm_service/vm_service.dart';
|
|
import 'package:vm_service/vm_service_io.dart';
|
|
|
|
import 'devices.dart';
|
|
import 'metrics_result_writer.dart';
|
|
import 'task_result.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(
|
|
List<String> taskNames, {
|
|
bool exitOnFirstTestFailure = false,
|
|
// terminateStrayDartProcesses defaults to false so that tests don't have to specify it.
|
|
// It is set based on the --terminate-stray-dart-processes command line argument in
|
|
// normal execution, and that flag defaults to true.
|
|
bool terminateStrayDartProcesses = false,
|
|
bool silent = false,
|
|
String? deviceId,
|
|
String? gitBranch,
|
|
String? localEngine,
|
|
String? localEngineHost,
|
|
String? localEngineSrcPath,
|
|
String? luciBuilder,
|
|
String? resultsPath,
|
|
List<String>? taskArgs,
|
|
bool useEmulator = false,
|
|
@visibleForTesting Map<String, String>? isolateParams,
|
|
@visibleForTesting void Function(String) print = print,
|
|
@visibleForTesting List<String>? logs,
|
|
}) async {
|
|
for (final String taskName in taskNames) {
|
|
TaskResult result = TaskResult.success(null);
|
|
int failureCount = 0;
|
|
while (failureCount <= MetricsResultWriter.retryNumber) {
|
|
result = await rerunTask(
|
|
taskName,
|
|
deviceId: deviceId,
|
|
localEngine: localEngine,
|
|
localEngineHost: localEngineHost,
|
|
localEngineSrcPath: localEngineSrcPath,
|
|
terminateStrayDartProcesses: terminateStrayDartProcesses,
|
|
silent: silent,
|
|
taskArgs: taskArgs,
|
|
resultsPath: resultsPath,
|
|
gitBranch: gitBranch,
|
|
luciBuilder: luciBuilder,
|
|
isolateParams: isolateParams,
|
|
useEmulator: useEmulator,
|
|
);
|
|
|
|
if (!result.succeeded) {
|
|
failureCount += 1;
|
|
if (exitOnFirstTestFailure) {
|
|
break;
|
|
}
|
|
} else {
|
|
section('Flaky status for "$taskName"');
|
|
if (failureCount > 0) {
|
|
print(
|
|
'Total ${failureCount + 1} executions: $failureCount failures and 1 false positive.',
|
|
);
|
|
print('flaky: true');
|
|
// TODO(ianh): stop ignoring this failure. We should set exitCode=1, and quit
|
|
// if exitOnFirstTestFailure is true.
|
|
} else {
|
|
print('Test passed on first attempt.');
|
|
print('flaky: false');
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!result.succeeded) {
|
|
section('Flaky status for "$taskName"');
|
|
print('Consistently failed across all $failureCount executions.');
|
|
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? localEngineHost,
|
|
String? localEngineSrcPath,
|
|
bool terminateStrayDartProcesses = false,
|
|
bool silent = false,
|
|
List<String>? taskArgs,
|
|
String? resultsPath,
|
|
String? gitBranch,
|
|
String? luciBuilder,
|
|
bool useEmulator = false,
|
|
@visibleForTesting Map<String, String>? isolateParams,
|
|
}) async {
|
|
section('Running task "$taskName"');
|
|
final TaskResult result = await runTask(
|
|
taskName,
|
|
deviceId: deviceId,
|
|
localEngine: localEngine,
|
|
localEngineHost: localEngineHost,
|
|
localEngineSrcPath: localEngineSrcPath,
|
|
terminateStrayDartProcesses: terminateStrayDartProcesses,
|
|
silent: silent,
|
|
taskArgs: taskArgs,
|
|
isolateParams: isolateParams,
|
|
useEmulator: useEmulator,
|
|
);
|
|
|
|
print('Task result:');
|
|
print(const JsonEncoder.withIndent(' ').convert(result));
|
|
section('Finished task "$taskName"');
|
|
|
|
if (resultsPath != null) {
|
|
final MetricsResultWriter cocoon = MetricsResultWriter();
|
|
await cocoon.writeTaskResultToFile(
|
|
builderName: luciBuilder,
|
|
gitBranch: gitBranch,
|
|
result: result,
|
|
resultsPath: resultsPath,
|
|
);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Runs a task in a separate Dart VM and collects the result using the VM
|
|
/// service protocol.
|
|
///
|
|
/// [taskName] is the name of the task. The corresponding task executable is
|
|
/// expected to be found under `bin/tasks`.
|
|
///
|
|
/// Running the task in [silent] mode will suppress standard output from task
|
|
/// processes and only print standard errors.
|
|
///
|
|
/// [taskArgs] are passed to the task executable for additional configuration.
|
|
Future<TaskResult> runTask(
|
|
String taskName, {
|
|
bool terminateStrayDartProcesses = false,
|
|
bool silent = false,
|
|
String? localEngine,
|
|
String? localEngineHost,
|
|
String? localWebSdk,
|
|
String? localEngineSrcPath,
|
|
String? deviceId,
|
|
List<String>? taskArgs,
|
|
bool useEmulator = false,
|
|
@visibleForTesting Map<String, String>? isolateParams,
|
|
}) async {
|
|
final String taskExecutable = 'bin/tasks/$taskName.dart';
|
|
|
|
if (!file(taskExecutable).existsSync()) {
|
|
print('Executable Dart file not found: $taskExecutable');
|
|
exit(1);
|
|
}
|
|
|
|
if (useEmulator) {
|
|
taskArgs ??= <String>[];
|
|
taskArgs
|
|
..add('--android-emulator')
|
|
..add('--browser-name=android-chrome');
|
|
}
|
|
|
|
stdout.writeln('Starting process for task: [$taskName]');
|
|
|
|
final Process runner = await startProcess(
|
|
dartBin,
|
|
<String>[
|
|
'--enable-vm-service=0', // zero causes the system to choose a free port
|
|
'--no-pause-isolates-on-exit',
|
|
if (localEngine != null) '-DlocalEngine=$localEngine',
|
|
if (localEngineHost != null) '-DlocalEngineHost=$localEngineHost',
|
|
if (localWebSdk != null) '-DlocalWebSdk=$localWebSdk',
|
|
if (localEngineSrcPath != null) '-DlocalEngineSrcPath=$localEngineSrcPath',
|
|
taskExecutable,
|
|
...?taskArgs,
|
|
],
|
|
environment: <String, String>{DeviceIdEnvName: ?deviceId},
|
|
);
|
|
|
|
bool runnerFinished = false;
|
|
|
|
unawaited(
|
|
runner.exitCode.whenComplete(() {
|
|
runnerFinished = true;
|
|
}),
|
|
);
|
|
|
|
final Completer<Uri> uri = Completer<Uri>();
|
|
|
|
final StreamSubscription<String> stdoutSub = runner.stdout
|
|
.transform<String>(const Utf8Decoder())
|
|
.transform<String>(const LineSplitter())
|
|
.listen((String line) {
|
|
if (!uri.isCompleted) {
|
|
final Uri? serviceUri = parseServiceUri(
|
|
line,
|
|
prefix: RegExp('The Dart VM service is listening on '),
|
|
);
|
|
if (serviceUri != null) {
|
|
uri.complete(serviceUri);
|
|
}
|
|
}
|
|
if (!silent) {
|
|
stdout.writeln('[${DateTime.now()}] [STDOUT] $line');
|
|
}
|
|
});
|
|
|
|
final StreamSubscription<String> stderrSub = runner.stderr
|
|
.transform<String>(const Utf8Decoder())
|
|
.transform<String>(const LineSplitter())
|
|
.listen((String line) {
|
|
stderr.writeln('[${DateTime.now()}] [STDERR] $line');
|
|
});
|
|
|
|
try {
|
|
final ConnectionResult result = await _connectToRunnerIsolate(await uri.future);
|
|
print('[$taskName] Connected to VM server.');
|
|
isolateParams = isolateParams == null
|
|
? <String, String>{}
|
|
: Map<String, String>.of(isolateParams);
|
|
isolateParams['runProcessCleanup'] = terminateStrayDartProcesses.toString();
|
|
final VmService service = result.vmService;
|
|
final String isolateId = result.isolate.id!;
|
|
final Map<String, dynamic> taskResultJson = (await service.callServiceExtension(
|
|
'ext.cocoonRunTask',
|
|
args: isolateParams,
|
|
isolateId: isolateId,
|
|
)).json!;
|
|
// Notify the task process that the task result has been received and it
|
|
// can proceed to shutdown.
|
|
await _acknowledgeTaskResultReceived(service: service, isolateId: isolateId);
|
|
final TaskResult taskResult = TaskResult.fromJson(taskResultJson);
|
|
final int exitCode = await runner.exitCode;
|
|
print('[$taskName] Process terminated with exit code $exitCode.');
|
|
return taskResult;
|
|
} catch (error, stack) {
|
|
print('[$taskName] Task runner system failed with exception!\n$error\n$stack');
|
|
rethrow;
|
|
} finally {
|
|
if (!runnerFinished) {
|
|
print('[$taskName] Terminating process...');
|
|
runner.kill(ProcessSignal.sigkill);
|
|
}
|
|
await stdoutSub.cancel();
|
|
await stderrSub.cancel();
|
|
}
|
|
}
|
|
|
|
Future<ConnectionResult> _connectToRunnerIsolate(Uri vmServiceUri) async {
|
|
final List<String> pathSegments = <String>[
|
|
// Add authentication code.
|
|
if (vmServiceUri.pathSegments.isNotEmpty) vmServiceUri.pathSegments[0],
|
|
'ws',
|
|
];
|
|
final String url = vmServiceUri.replace(scheme: 'ws', pathSegments: pathSegments).toString();
|
|
final Stopwatch stopwatch = Stopwatch()..start();
|
|
|
|
while (true) {
|
|
try {
|
|
// Look up the isolate.
|
|
final VmService client = await vmServiceConnectUri(url);
|
|
VM vm = await client.getVM();
|
|
while (vm.isolates!.isEmpty) {
|
|
await Future<void>.delayed(const Duration(seconds: 1));
|
|
vm = await client.getVM();
|
|
}
|
|
final IsolateRef isolate = vm.isolates!.first;
|
|
// Sanity check to ensure we're talking with the main isolate.
|
|
final Response response = await client.callServiceExtension(
|
|
'ext.cocoonRunnerReady',
|
|
isolateId: isolate.id,
|
|
);
|
|
if (response.json!['result'] != 'success') {
|
|
throw 'not ready yet';
|
|
}
|
|
return ConnectionResult(client, isolate);
|
|
} catch (error) {
|
|
if (stopwatch.elapsed > const Duration(seconds: 10)) {
|
|
print(
|
|
'VM service still not ready. It is possible the target has failed.\n'
|
|
'Latest connection error:\n'
|
|
' $error\n'
|
|
'Continuing to retry...\n',
|
|
);
|
|
stopwatch.reset();
|
|
}
|
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _acknowledgeTaskResultReceived({
|
|
required VmService service,
|
|
required String isolateId,
|
|
}) async {
|
|
try {
|
|
await service.callServiceExtension('ext.cocoonTaskResultReceived', isolateId: isolateId);
|
|
} on RPCError {
|
|
// The target VM may shutdown before the response is received.
|
|
}
|
|
}
|
|
|
|
class ConnectionResult {
|
|
ConnectionResult(this.vmService, this.isolate);
|
|
|
|
final VmService vmService;
|
|
final IsolateRef isolate;
|
|
}
|