blob: a85de49b658a59d13c27f17ad36aee5d44a5bafb [file] [log] [blame]
// Copyright (c) 2014, 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';
// TODO(cbracken) make generic
/// Retries the specified function with the specified interval and returns
/// the result on successful completion.
Future<dynamic> retry(Future Function() f, Duration interval,
{Duration? timeout}) async {
var keepGoing = true;
Future<dynamic> _withTimeout(Future Function() f, {Duration? duration}) {
if (duration == null) {
return f();
}
return f().timeout(duration, onTimeout: () {
keepGoing = false;
final msg = duration.inSeconds == 0
? '${duration.inMilliseconds}ms'
: '${duration.inSeconds}s';
throw StateError('Failed to complete within $msg');
});
}
return _withTimeout(() async {
while (keepGoing) {
try {
return await f();
} catch (_) {
if (keepGoing) {
await Future<dynamic>.delayed(interval);
}
}
}
}, duration: timeout);
}
/// Scrapes and returns the Dart VM service URI from a string, or null if not
/// found.
///
/// Potentially useful as a means to extract it from log statements.
Uri? extractVMServiceUri(String str) {
final listeningMessageRegExp = RegExp(
r'(?:Observatory|The Dart VM service is) listening on ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+)',
);
final match = listeningMessageRegExp.firstMatch(str);
if (match != null) {
return Uri.parse(match[1]!);
}
return null;
}
/// Returns an open port by creating a temporary Socket
Future<int> getOpenPort() async {
ServerSocket socket;
try {
socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
} catch (_) {
// try again v/ V6 only. Slight possibility that V4 is disabled
socket =
await ServerSocket.bind(InternetAddress.loopbackIPv6, 0, v6Only: true);
}
try {
return socket.port;
} finally {
await socket.close();
}
}
const muliLineIgnoreStart = '// coverage:ignore-start';
const muliLineIgnoreEnd = '// coverage:ignore-end';
const singleLineIgnore = '// coverage:ignore-line';
const ignoreFile = '// coverage:ignore-file';
/// Return list containing inclusive range of lines to be ignored by coverage.
/// If there is a error in balancing the statements it will ignore nothing,
/// unless `coverage:ignore-file` is found.
/// Return [0, lines.length] if the whole file is ignored.
///
/// ```
/// 1. final str = ''; // coverage:ignore-line
/// 2. final str = '';
/// 3. final str = ''; // coverage:ignore-start
/// 4. final str = '';
/// 5. final str = ''; // coverage:ignore-end
/// ```
///
/// Returns
/// ```
/// [
/// [1,1],
/// [3,5],
/// ]
/// ```
///
List<List<int>> getIgnoredLines(List<String>? lines) {
final ignoredLines = <List<int>>[];
if (lines == null) return ignoredLines;
final allLines = [
[0, lines.length]
];
var isError = false;
var i = 0;
while (i < lines.length) {
if (lines[i].contains(ignoreFile)) return allLines;
if (lines[i].contains(muliLineIgnoreEnd)) isError = true;
if (lines[i].contains(singleLineIgnore)) ignoredLines.add([i + 1, i + 1]);
if (lines[i].contains(muliLineIgnoreStart)) {
final start = i;
++i;
while (i < lines.length) {
if (lines[i].contains(ignoreFile)) return allLines;
if (lines[i].contains(muliLineIgnoreStart)) {
isError = true;
break;
}
if (lines[i].contains(muliLineIgnoreEnd)) {
ignoredLines.add([start + 1, i + 1]);
break;
}
++i;
}
}
++i;
}
return isError ? [] : ignoredLines;
}
extension StandardOutExtension on Stream<List<int>> {
Stream<String> lines() =>
transform(SystemEncoding().decoder).transform(const LineSplitter());
}
Future<Uri> serviceUriFromProcess(Stream<String> procStdout) {
// Capture the VM service URI.
final serviceUriCompleter = Completer<Uri>();
procStdout.listen((line) {
if (!serviceUriCompleter.isCompleted) {
final serviceUri = extractVMServiceUri(line);
if (serviceUri != null) {
serviceUriCompleter.complete(serviceUri);
}
}
});
return serviceUriCompleter.future;
}