Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 57f097b

Browse files
authored
Refactor devicelab bin/run.dart and other cleanup (#96320)
1 parent 1c0eade commit 57f097b

File tree

11 files changed

+466
-352
lines changed

11 files changed

+466
-352
lines changed

dev/devicelab/bin/run.dart

Lines changed: 227 additions & 219 deletions
Large diffs are not rendered by default.

dev/devicelab/lib/framework/framework.dart

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'dart:isolate';
1010

1111
import 'package:logging/logging.dart';
1212
import 'package:path/path.dart' as path;
13+
import 'package:process/process.dart';
1314
import 'package:stack_trace/stack_trace.dart';
1415

1516
import 'devices.dart';
@@ -41,35 +42,43 @@ bool _isTaskRegistered = false;
4142
///
4243
/// It is OK for a [task] to perform many things. However, only one task can be
4344
/// registered per Dart VM.
44-
Future<TaskResult> task(TaskFunction task) async {
45+
///
46+
/// If no `processManager` is provided, a default [LocalProcessManager] is created
47+
/// for the task.
48+
Future<TaskResult> task(TaskFunction task, { ProcessManager? processManager }) async {
4549
if (_isTaskRegistered)
4650
throw StateError('A task is already registered');
47-
4851
_isTaskRegistered = true;
4952

53+
processManager ??= const LocalProcessManager();
54+
5055
// TODO(ianh): allow overriding logging.
5156
Logger.root.level = Level.ALL;
5257
Logger.root.onRecord.listen((LogRecord rec) {
5358
print('${rec.level.name}: ${rec.time}: ${rec.message}');
5459
});
5560

56-
final _TaskRunner runner = _TaskRunner(task);
61+
final _TaskRunner runner = _TaskRunner(task, processManager);
5762
runner.keepVmAliveUntilTaskRunRequested();
5863
return runner.whenDone;
5964
}
6065

6166
class _TaskRunner {
62-
_TaskRunner(this.task) {
67+
_TaskRunner(this.task, this.processManager) {
6368
registerExtension('ext.cocoonRunTask',
6469
(String method, Map<String, String> parameters) async {
6570
final Duration? taskTimeout = parameters.containsKey('timeoutInMinutes')
6671
? Duration(minutes: int.parse(parameters['timeoutInMinutes']!))
6772
: null;
68-
// This is only expected to be passed in unit test runs so they do not
69-
// kill the Dart process that is running them and waste time running config.
70-
final bool runFlutterConfig = parameters['runFlutterConfig'] != 'false';
73+
final bool runFlutterConfig = parameters['runFlutterConfig'] != 'false'; // used by tests to avoid changing the configuration
7174
final bool runProcessCleanup = parameters['runProcessCleanup'] != 'false';
72-
final TaskResult result = await run(taskTimeout, runProcessCleanup: runProcessCleanup, runFlutterConfig: runFlutterConfig);
75+
final String? localEngine = parameters['localEngine'];
76+
final TaskResult result = await run(
77+
taskTimeout,
78+
runProcessCleanup: runProcessCleanup,
79+
runFlutterConfig: runFlutterConfig,
80+
localEngine: localEngine,
81+
);
7382
return ServiceExtensionResponse.result(json.encode(result.toJson()));
7483
});
7584
registerExtension('ext.cocoonRunnerReady',
@@ -79,6 +88,7 @@ class _TaskRunner {
7988
}
8089

8190
final TaskFunction task;
91+
final ProcessManager processManager;
8292

8393
Future<Device?> _getWorkingDeviceIfAvailable() async {
8494
try {
@@ -103,6 +113,7 @@ class _TaskRunner {
103113
Future<TaskResult> run(Duration? taskTimeout, {
104114
bool runFlutterConfig = true,
105115
bool runProcessCleanup = true,
116+
required String? localEngine,
106117
}) async {
107118
try {
108119
_taskStarted = true;
@@ -113,34 +124,31 @@ class _TaskRunner {
113124
section('Checking running Dart$exe processes');
114125
beforeRunningDartInstances = await getRunningProcesses(
115126
processName: 'dart$exe',
116-
).toSet();
117-
final Set<RunningProcessInfo> allProcesses = await getRunningProcesses().toSet();
127+
processManager: processManager,
128+
);
129+
final Set<RunningProcessInfo> allProcesses = await getRunningProcesses(processManager: processManager);
118130
beforeRunningDartInstances.forEach(print);
119131
for (final RunningProcessInfo info in allProcesses) {
120132
if (info.commandLine.contains('iproxy')) {
121133
print('[LEAK]: ${info.commandLine} ${info.creationDate} ${info.pid} ');
122134
}
123135
}
124-
} else {
125-
section('Skipping check running Dart$exe processes');
126136
}
127137

128138
if (runFlutterConfig) {
129-
print('enabling configs for macOS, Linux, Windows, and Web...');
139+
print('Enabling configs for macOS, Linux, Windows, and Web...');
130140
final int configResult = await exec(path.join(flutterDirectory.path, 'bin', 'flutter'), <String>[
131141
'config',
132142
'-v',
133143
'--enable-macos-desktop',
134144
'--enable-windows-desktop',
135145
'--enable-linux-desktop',
136146
'--enable-web',
137-
if (localEngine != null) ...<String>['--local-engine', localEngine!],
147+
if (localEngine != null) ...<String>['--local-engine', localEngine],
138148
], canFail: true);
139149
if (configResult != 0) {
140150
print('Failed to enable configuration, tasks may not run.');
141151
}
142-
} else {
143-
print('Skipping enabling configs for macOS, Linux, Windows, and Web');
144152
}
145153

146154
final Device? device = await _getWorkingDeviceIfAvailable();
@@ -169,26 +177,24 @@ class _TaskRunner {
169177
}
170178

171179
if (runProcessCleanup) {
172-
section('Checking running Dart$exe processes after task...');
173-
final List<RunningProcessInfo> afterRunningDartInstances = await getRunningProcesses(
180+
section('Terminating lingering Dart$exe processes after task...');
181+
final Set<RunningProcessInfo> afterRunningDartInstances = await getRunningProcesses(
174182
processName: 'dart$exe',
175-
).toList();
183+
processManager: processManager,
184+
);
176185
for (final RunningProcessInfo info in afterRunningDartInstances) {
177186
if (!beforeRunningDartInstances.contains(info)) {
178187
print('$info was leaked by this test.');
179188
if (result is TaskResultCheckProcesses) {
180189
result = TaskResult.failure('This test leaked dart processes');
181190
}
182-
final bool killed = await killProcess(info.pid);
183-
if (!killed) {
184-
print('Failed to kill process ${info.pid}.');
185-
} else {
191+
if (await info.terminate(processManager: processManager)) {
186192
print('Killed process id ${info.pid}.');
193+
} else {
194+
print('Failed to kill process ${info.pid}.');
187195
}
188196
}
189197
}
190-
} else {
191-
print('Skipping check running Dart$exe processes after task');
192198
}
193199
_completer.complete(result);
194200
return result;

dev/devicelab/lib/framework/runner.dart

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import 'dart:async';
66
import 'dart:convert';
7-
// import 'dart:core' as core;
87
import 'dart:io';
98

109
import 'package:meta/meta.dart';
@@ -29,6 +28,10 @@ import 'utils.dart';
2928
Future<void> runTasks(
3029
List<String> taskNames, {
3130
bool exitOnFirstTestFailure = false,
31+
// terminateStrayDartProcesses defaults to false so that tests don't have to specify it.
32+
// It is set based on the --terminate-stray-dart-processes command line argument in
33+
// normal execution, and that flag defaults to true.
34+
bool terminateStrayDartProcesses = false,
3235
bool silent = false,
3336
String? deviceId,
3437
String? gitBranch,
@@ -50,6 +53,7 @@ Future<void> runTasks(
5053
deviceId: deviceId,
5154
localEngine: localEngine,
5255
localEngineSrcPath: localEngineSrcPath,
56+
terminateStrayDartProcesses: terminateStrayDartProcesses,
5357
silent: silent,
5458
taskArgs: taskArgs,
5559
resultsPath: resultsPath,
@@ -58,23 +62,26 @@ Future<void> runTasks(
5862
isolateParams: isolateParams,
5963
);
6064

61-
section('Flaky status for "$taskName"');
6265
if (!result.succeeded) {
63-
retry++;
66+
retry += 1;
6467
} else {
68+
section('Flaky status for "$taskName"');
6569
if (retry > 0) {
66-
print('Total ${retry+1} executions: $retry failures and 1 success');
70+
print('Total ${retry+1} executions: $retry failures and 1 false positive.');
6771
print('flaky: true');
72+
// TODO(ianh): stop ignoring this failure. We should set exitCode=1, and quit
73+
// if exitOnFirstTestFailure is true.
6874
} else {
69-
print('Total ${retry+1} executions: 1 success');
75+
print('Test passed on first attempt.');
7076
print('flaky: false');
7177
}
7278
break;
7379
}
7480
}
7581

7682
if (!result.succeeded) {
77-
print('Total $retry executions: 0 success');
83+
section('Flaky status for "$taskName"');
84+
print('Consistently failed across all $retry executions.');
7885
print('flaky: false');
7986
exitCode = 1;
8087
if (exitOnFirstTestFailure) {
@@ -92,6 +99,7 @@ Future<TaskResult> rerunTask(
9299
String? deviceId,
93100
String? localEngine,
94101
String? localEngineSrcPath,
102+
bool terminateStrayDartProcesses = false,
95103
bool silent = false,
96104
List<String>? taskArgs,
97105
String? resultsPath,
@@ -105,6 +113,7 @@ Future<TaskResult> rerunTask(
105113
deviceId: deviceId,
106114
localEngine: localEngine,
107115
localEngineSrcPath: localEngineSrcPath,
116+
terminateStrayDartProcesses: terminateStrayDartProcesses,
108117
silent: silent,
109118
taskArgs: taskArgs,
110119
isolateParams: isolateParams,
@@ -138,11 +147,12 @@ Future<TaskResult> rerunTask(
138147
/// [taskArgs] are passed to the task executable for additional configuration.
139148
Future<TaskResult> runTask(
140149
String taskName, {
150+
bool terminateStrayDartProcesses = false,
141151
bool silent = false,
142152
String? localEngine,
143153
String? localEngineSrcPath,
144154
String? deviceId,
145-
List<String> ?taskArgs,
155+
List<String>? taskArgs,
146156
@visibleForTesting Map<String, String>? isolateParams,
147157
}) async {
148158
final String taskExecutable = 'bin/tasks/$taskName.dart';
@@ -198,17 +208,26 @@ Future<TaskResult> runTask(
198208

199209
try {
200210
final ConnectionResult result = await _connectToRunnerIsolate(await uri.future);
211+
print('[$taskName] Connected to VM server.');
212+
isolateParams = isolateParams == null ? <String, String>{} : Map<String, String>.of(isolateParams);
213+
isolateParams['runProcessCleanup'] = terminateStrayDartProcesses.toString();
201214
final Map<String, dynamic> taskResultJson = (await result.vmService.callServiceExtension(
202215
'ext.cocoonRunTask',
203216
args: isolateParams,
204217
isolateId: result.isolate.id,
205218
)).json!;
206219
final TaskResult taskResult = TaskResult.fromJson(taskResultJson);
207-
await runner.exitCode;
220+
final int exitCode = await runner.exitCode;
221+
print('[$taskName] Process terminated with exit code $exitCode.');
208222
return taskResult;
223+
} catch (error, stack) {
224+
print('[$taskName] Task runner system failed with exception!\n$error\n$stack');
225+
rethrow;
209226
} finally {
210-
if (!runnerFinished)
227+
if (!runnerFinished) {
228+
print('[$taskName] Terminating process...');
211229
runner.kill(ProcessSignal.sigkill);
230+
}
212231
await stdoutSub.cancel();
213232
await stderrSub.cancel();
214233
}

0 commit comments

Comments
 (0)