2da102b4e9
`connected_app_service_test` went from flaky to always failing on Windows recently (on what seems like a completely unrelated CL of mine). The cause is failure to delete this directory due to file locks, which is something we often see on Windows and usually wrap deletes with some retries instead of immediately failing. This adds the same here, Change-Id: I3f9b4f52d2327c3b3ef3e70674059366caa26093 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/461420 Commit-Queue: Ben Konyi <bkonyi@google.com> Reviewed-by: Jessy Yameogo <yjessy@google.com> Reviewed-by: Ben Konyi <bkonyi@google.com>
176 lines
4.8 KiB
Dart
176 lines
4.8 KiB
Dart
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
|
|
// for details. 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:async/async.dart';
|
|
import 'package:json_rpc_2/json_rpc_2.dart';
|
|
import 'package:path/path.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:test_process/test_process.dart';
|
|
|
|
/// Helper class for starting a Dart Tooling Daemon instance, and extracting
|
|
/// it's [trustedSecret] and [uri] from stdout.
|
|
class ToolingDaemonTestProcess {
|
|
ToolingDaemonTestProcess({this.unrestricted = false});
|
|
late final String? trustedSecret;
|
|
late final Uri uri;
|
|
late final Process? process;
|
|
final bool unrestricted;
|
|
|
|
Future<Process> start() async {
|
|
final completer = Completer<void>();
|
|
process = await Process.start(
|
|
Platform.resolvedExecutable,
|
|
[
|
|
'tooling-daemon',
|
|
'--machine',
|
|
if (unrestricted) '--unrestricted',
|
|
'--fakeAnalytics',
|
|
],
|
|
);
|
|
process!.handle(
|
|
stdoutLines: (line) {
|
|
print('DTD stdout: $line');
|
|
try {
|
|
final json = jsonDecode(line) as Map<String, Object?>;
|
|
final toolingDaemonDetails =
|
|
json['tooling_daemon_details'] as Map<String, Object?>;
|
|
trustedSecret =
|
|
toolingDaemonDetails['trusted_client_secret'] as String?;
|
|
uri = Uri.parse(toolingDaemonDetails['uri'] as String);
|
|
completer.complete();
|
|
} catch (e) {
|
|
// If we failed to decode then this line doesn't have json.
|
|
print('Json parsing error: $e');
|
|
}
|
|
},
|
|
stderrLines: (line) {
|
|
stderr.write('DTD stderr: $line');
|
|
},
|
|
);
|
|
|
|
await completer.future;
|
|
return process!;
|
|
}
|
|
|
|
void kill() {
|
|
process?.kill();
|
|
}
|
|
}
|
|
|
|
/// Helper class for starting a Dart CLI app and extracting its VM service uri
|
|
/// from stdout.
|
|
class DartCliAppProcess {
|
|
late final String vmServiceUri;
|
|
late final TestProcess? process;
|
|
|
|
Future<TestProcess> start() async {
|
|
final tmpDir = Directory.systemTemp.createTempSync();
|
|
|
|
// A simple Dart command line app that will never exit.
|
|
const fileName = 'app.dart';
|
|
File(join(tmpDir.path, fileName))
|
|
..writeAsStringSync('''
|
|
void main() async {
|
|
while (true) {
|
|
// Loop on an awaited delay to avoid pinning a CPU core.
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
}
|
|
}
|
|
''')
|
|
..createSync();
|
|
|
|
process = await TestProcess.start(
|
|
Platform.resolvedExecutable,
|
|
[
|
|
'run',
|
|
'--observe=0',
|
|
fileName,
|
|
],
|
|
workingDirectory: tmpDir.path,
|
|
);
|
|
|
|
addTearDown(() async {
|
|
await process!.kill();
|
|
await process!.exitCode;
|
|
// Delete [tmpDir] after [process] is killed and exited to reduce the
|
|
// chance of the delete failoing with "The process cannot access the file
|
|
// because it is being used by another process" and requiring retries.
|
|
await deleteDirectory(tmpDir);
|
|
});
|
|
|
|
String? uri;
|
|
final stdout = StreamQueue(process!.stdoutStream());
|
|
while (await stdout.hasNext) {
|
|
final line = await stdout.next;
|
|
if (line.contains('The Dart VM service is listening on')) {
|
|
vmServiceUri = uri =
|
|
line.substring(line.indexOf('http:')).replaceFirst('http:', 'ws:');
|
|
await stdout.cancel();
|
|
break;
|
|
}
|
|
}
|
|
if (uri == null) {
|
|
throw StateError(
|
|
'Failed to read vm service URI from the Dart run output.',
|
|
);
|
|
}
|
|
return process!;
|
|
}
|
|
|
|
void kill() {
|
|
process?.kill();
|
|
}
|
|
}
|
|
|
|
extension OutputProcessExtension on Process {
|
|
void handle({
|
|
required void Function(String) stdoutLines,
|
|
void Function(String)? stderrLines,
|
|
}) {
|
|
this
|
|
.stdout
|
|
.transform(utf8.decoder)
|
|
.transform(const LineSplitter())
|
|
.listen((line) => stdoutLines(line));
|
|
|
|
if (stderrLines != null) {
|
|
this
|
|
.stderr
|
|
.transform(utf8.decoder)
|
|
.transform(const LineSplitter())
|
|
.listen((line) => stderrLines(line));
|
|
}
|
|
}
|
|
}
|
|
|
|
Matcher throwsAnRpcError(int code) {
|
|
return throwsA(predicate((p0) => (p0 is RpcException) && (p0.code == code)));
|
|
}
|
|
|
|
/// Deletes [dir] with some retries to reduce the chances of file locking errors
|
|
/// on Windows where files may fail to delete for a short period after the
|
|
/// process that locked them terminates.
|
|
Future<void> deleteDirectory(Directory dir) async {
|
|
var deleteAttempts = 5;
|
|
while (deleteAttempts >= 0) {
|
|
deleteAttempts--;
|
|
try {
|
|
if (!dir.existsSync()) {
|
|
return;
|
|
}
|
|
dir.deleteSync(recursive: true);
|
|
} catch (e) {
|
|
if (deleteAttempts <= 0) {
|
|
rethrow;
|
|
}
|
|
await Future<void>.delayed(const Duration(milliseconds: 500));
|
|
print('Got $e while deleting $dir. Trying again...');
|
|
}
|
|
}
|
|
}
|