- Add VariableSpec class with magnitude field validation - Add Formula class supporting multiple input/output variables - Support d4rt_code as string or object with code field - Add comprehensive tests for parsing and serialization - Fix broken test import in pruebas_d4rt_test.dart Follows README.md format requirements exactly
206 lines
6 KiB
Dart
206 lines
6 KiB
Dart
// 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';
|
|
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:vm_service/vm_service.dart';
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
// 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;
|
|
}
|
|
|
|
final multiLineIgnoreStart = RegExp(r'//\s*coverage:ignore-start[\w\d\s]*$');
|
|
final multiLineIgnoreEnd = RegExp(r'//\s*coverage:ignore-end[\w\d\s]*$');
|
|
final singleLineIgnore = RegExp(r'//\s*coverage:ignore-line[\w\d\s]*$');
|
|
final ignoreFile = RegExp(r'//\s*coverage:ignore-file[\w\d\s]*$');
|
|
|
|
/// Return list containing inclusive ranges of lines to be ignored by coverage.
|
|
/// If there is a error in balancing the statements it will throw a
|
|
/// [FormatException],
|
|
/// unless `coverage:ignore-file` is found.
|
|
/// Return null 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],
|
|
/// ]
|
|
/// ```
|
|
///
|
|
/// The returned ranges are in sorted order, and never overlap.
|
|
List<List<int>>? getIgnoredLines(String filePath, List<String>? lines) {
|
|
final ignoredLines = <List<int>>[];
|
|
if (lines == null) return ignoredLines;
|
|
|
|
FormatException? err;
|
|
var i = 0;
|
|
while (i < lines.length) {
|
|
if (lines[i].contains(ignoreFile)) return null;
|
|
|
|
if (lines[i].contains(multiLineIgnoreEnd)) {
|
|
err ??= FormatException(
|
|
'unmatched coverage:ignore-end found at $filePath:${i + 1}',
|
|
);
|
|
}
|
|
|
|
if (lines[i].contains(singleLineIgnore)) ignoredLines.add([i + 1, i + 1]);
|
|
|
|
if (lines[i].contains(multiLineIgnoreStart)) {
|
|
final start = i;
|
|
var isUnmatched = true;
|
|
++i;
|
|
while (i < lines.length) {
|
|
if (lines[i].contains(ignoreFile)) return null;
|
|
if (lines[i].contains(multiLineIgnoreStart)) {
|
|
err ??= FormatException(
|
|
'coverage:ignore-start found at $filePath:${i + 1}'
|
|
' before previous coverage:ignore-start ended',
|
|
);
|
|
break;
|
|
}
|
|
|
|
if (lines[i].contains(multiLineIgnoreEnd)) {
|
|
ignoredLines.add([start + 1, i + 1]);
|
|
isUnmatched = false;
|
|
break;
|
|
}
|
|
++i;
|
|
}
|
|
|
|
if (isUnmatched) {
|
|
err ??= FormatException(
|
|
'coverage:ignore-start found at $filePath:${start + 1}'
|
|
' has no matching coverage:ignore-end',
|
|
);
|
|
}
|
|
}
|
|
++i;
|
|
}
|
|
|
|
if (err == null) {
|
|
return ignoredLines;
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
|
|
extension IgnoredLinesContains on List<List<int>> {
|
|
/// Returns whether this list of line ranges contains the given line.
|
|
bool ignoredContains(int line) {
|
|
if (length == 0 || this[0][0] > line) return false;
|
|
|
|
// Binary search for the range with the largest start value that is <= line.
|
|
var lo = 0;
|
|
var hi = length;
|
|
while (lo < hi - 1) {
|
|
final mid = lo + (hi - lo) ~/ 2;
|
|
if (this[mid][0] <= line) {
|
|
lo = mid;
|
|
} else {
|
|
hi = mid;
|
|
}
|
|
}
|
|
return this[lo][1] >= line;
|
|
}
|
|
}
|
|
|
|
extension StandardOutExtension on Stream<List<int>> {
|
|
Stream<String> lines() =>
|
|
transform(const 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;
|
|
}
|
|
|
|
Future<List<IsolateRef>> getAllIsolates(VmService service) async =>
|
|
(await service.getVM()).isolates ?? [];
|
|
|
|
String getPubspecPath(String root) => path.join(root, 'pubspec.yaml');
|
|
|
|
List<String> getAllWorkspaceNames(String packageRoot) =>
|
|
_getAllWorkspaceNames(packageRoot, <String>[]);
|
|
|
|
List<String> _getAllWorkspaceNames(String packageRoot, List<String> results) {
|
|
final pubspec = _loadPubspec(packageRoot);
|
|
results.add(pubspec['name'] as String);
|
|
for (final workspace in pubspec['workspace'] as YamlList? ?? []) {
|
|
_getAllWorkspaceNames(path.join(packageRoot, workspace as String), results);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
YamlMap _loadPubspec(String packageRoot) {
|
|
final pubspecPath = getPubspecPath(packageRoot);
|
|
final yaml = File(pubspecPath).readAsStringSync();
|
|
return loadYaml(yaml, sourceUrl: Uri.file(pubspecPath)) as YamlMap;
|
|
}
|