mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
8a883fa54d
Leaves some in parser test: pkg/front_end/parser_testcases/error_recovery/keyword_named_formal_parameter_prime.dart TEST=Refactoring, covered by existing tests. Change-Id: I7a83ef95df3cbd283878b3685b5c747bd89a1b16 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/256125 Reviewed-by: Johnni Winther <johnniwinther@google.com> Commit-Queue: Lasse Nielsen <lrn@google.com>
1906 lines
63 KiB
Dart
1906 lines
63 KiB
Dart
// Copyright (c) 2018, 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:collection/collection.dart' show IterableExtension;
|
|
import 'package:front_end/src/api_unstable/vm.dart'
|
|
show
|
|
CompilerOptions,
|
|
DiagnosticMessage,
|
|
ExperimentalFlag,
|
|
IncrementalCompilerResult,
|
|
computePlatformBinariesLocation;
|
|
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
|
|
import 'package:kernel/binary/ast_to_binary.dart';
|
|
import 'package:kernel/kernel.dart';
|
|
import 'package:kernel/target/targets.dart';
|
|
import 'package:kernel/text/ast_to_text.dart';
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:test/test.dart';
|
|
import 'package:web_socket_channel/io.dart';
|
|
|
|
import 'package:vm/incremental_compiler.dart';
|
|
import 'package:vm/target/vm.dart';
|
|
|
|
import 'common_test_utils.dart';
|
|
|
|
main() {
|
|
final platformKernel =
|
|
computePlatformBinariesLocation().resolve('vm_platform_strong.dill');
|
|
final sdkRoot = computePlatformBinariesLocation();
|
|
|
|
CompilerOptions getFreshOptions() {
|
|
return new CompilerOptions()
|
|
..sdkRoot = sdkRoot
|
|
..target = new VmTarget(new TargetFlags())
|
|
..additionalDills = <Uri>[platformKernel]
|
|
..onDiagnostic = (DiagnosticMessage message) {
|
|
fail("Compilation error: ${message.plainTextFormatted.join('\n')}");
|
|
}
|
|
..environmentDefines = const {};
|
|
}
|
|
|
|
final options = getFreshOptions();
|
|
|
|
group('basic', () {
|
|
late Directory mytest;
|
|
late File main;
|
|
|
|
setUpAll(() {
|
|
mytest = Directory.systemTemp.createTempSync('incremental');
|
|
main = new File('${mytest.path}/main.dart')..createSync();
|
|
main.writeAsStringSync("main() {}\n");
|
|
});
|
|
|
|
tearDownAll(() {
|
|
try {
|
|
mytest.deleteSync(recursive: true);
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
});
|
|
|
|
test('compile', () async {
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(options, [main.uri]);
|
|
IncrementalCompilerResult compilerResult = await compiler.compile();
|
|
Component component = compilerResult.component;
|
|
|
|
final StringBuffer buffer = new StringBuffer();
|
|
new Printer(buffer, showMetadata: true)
|
|
.writeLibraryFile(component.mainMethod!.enclosingLibrary);
|
|
expect(
|
|
buffer.toString(),
|
|
equals('library /*isNonNullableByDefault*/;\n'
|
|
'import self as self;\n'
|
|
'\n'
|
|
'static method main() → dynamic {}\n'));
|
|
});
|
|
|
|
test('compile exclude sources', () async {
|
|
CompilerOptions optionsExcludeSources = getFreshOptions()
|
|
..embedSourceText = false;
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(optionsExcludeSources, [main.uri]);
|
|
IncrementalCompilerResult compilerResult = await compiler.compile();
|
|
Component component = compilerResult.component;
|
|
|
|
for (Source source in component.uriToSource.values) {
|
|
expect(source.source.length, equals(0));
|
|
}
|
|
|
|
final StringBuffer buffer = new StringBuffer();
|
|
new Printer(buffer, showMetadata: true)
|
|
.writeLibraryFile(component.mainMethod!.enclosingLibrary);
|
|
expect(
|
|
buffer.toString(),
|
|
equals('library /*isNonNullableByDefault*/;\n'
|
|
'import self as self;\n'
|
|
'\n'
|
|
'static method main() → dynamic {}\n'));
|
|
});
|
|
|
|
test('compile expressions errors are not re-reported', () async {
|
|
var errorsReported = 0;
|
|
CompilerOptions optionsAcceptErrors = getFreshOptions()
|
|
..onDiagnostic = (DiagnosticMessage message) {
|
|
errorsReported++;
|
|
message.plainTextFormatted.forEach(print);
|
|
};
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(optionsAcceptErrors, [main.uri]);
|
|
await compiler.compile();
|
|
compiler.accept();
|
|
{
|
|
Procedure? procedure = await compiler.compileExpression(
|
|
'main',
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
main.uri.toString(),
|
|
null,
|
|
null,
|
|
true);
|
|
expect(procedure, isNotNull);
|
|
expect(errorsReported, equals(0));
|
|
}
|
|
{
|
|
Procedure? procedure = await compiler.compileExpression(
|
|
'main1',
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
main.uri.toString(),
|
|
null,
|
|
null,
|
|
true);
|
|
expect(procedure, isNotNull);
|
|
expect(errorsReported, equals(1));
|
|
errorsReported = 0;
|
|
}
|
|
await compiler.compile();
|
|
expect(errorsReported, equals(0));
|
|
});
|
|
});
|
|
|
|
/// Collects coverage for "main.dart", "lib.dart", "lib1.dart" and "lib2.dart"
|
|
/// checks that all tokens can be translated to line and column,
|
|
/// return the hit positions for "lib1.dart".
|
|
/// If [getAllSources] is false it will ask specifically for report
|
|
/// (and thus hits) for "lib1.dart" only.
|
|
Future<Set<int>> collectAndCheckCoverageData(int port, bool getAllSources,
|
|
{bool resume = true,
|
|
bool onGetAllVerifyCount = true,
|
|
Set<int>? coverageForLines}) async {
|
|
RemoteVm remoteVm = new RemoteVm(port);
|
|
|
|
// Wait for the script to have finished.
|
|
while (true) {
|
|
Map isolate = await remoteVm.getIsolate();
|
|
Map pauseEvent = isolate["pauseEvent"];
|
|
if (pauseEvent["kind"] == "PauseExit") break;
|
|
}
|
|
|
|
// Collect coverage for the two user scripts.
|
|
List<Map> sourceReports = <Map>[];
|
|
if (getAllSources) {
|
|
Map sourceReport = await remoteVm.getSourceReport();
|
|
sourceReports.add(sourceReport);
|
|
} else {
|
|
Map scriptsMap = await remoteVm.getScripts();
|
|
List scripts = scriptsMap["scripts"];
|
|
Set<String> scriptIds = new Set<String>();
|
|
for (int i = 0; i < scripts.length; i++) {
|
|
Map script = scripts[i];
|
|
String scriptUri = script["uri"];
|
|
if (scriptUri.contains("lib1.dart")) {
|
|
scriptIds.add(script["id"]);
|
|
}
|
|
}
|
|
|
|
for (String scriptId in scriptIds) {
|
|
Map sourceReport = await remoteVm.getSourceReport(scriptId);
|
|
sourceReports.add(sourceReport);
|
|
}
|
|
}
|
|
|
|
List<String> errorMessages = <String>[];
|
|
Set<int> hits = new Set<int>();
|
|
|
|
// Ensure that we can get a line and column number for all reported
|
|
// positions in the scripts we care about.
|
|
for (Map sourceReport in sourceReports) {
|
|
List<Map> scripts = sourceReport["scripts"].cast<Map>();
|
|
Map<String, int> scriptIdToIndex = new Map<String, int>();
|
|
Set<int> lib1scriptIndices = new Set<int>();
|
|
int i = 0;
|
|
for (Map script in scripts) {
|
|
if (script["uri"].toString().endsWith("main.dart") ||
|
|
script["uri"].toString().endsWith("lib.dart") ||
|
|
script["uri"].toString().endsWith("lib1.dart") ||
|
|
script["uri"].toString().endsWith("lib2.dart")) {
|
|
scriptIdToIndex[script["id"]] = i;
|
|
if (script["uri"].toString().endsWith("lib1.dart")) {
|
|
lib1scriptIndices.add(i);
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
if (getAllSources && onGetAllVerifyCount) {
|
|
expect(scriptIdToIndex.length >= 2, isTrue);
|
|
}
|
|
|
|
// Ensure the scripts all have a non-null 'tokenPosTable' entry.
|
|
Map<int, Map> scriptIndexToScript = new Map<int, Map>();
|
|
for (String scriptId in scriptIdToIndex.keys) {
|
|
Map script = await remoteVm.getObject(scriptId);
|
|
int scriptIdx = scriptIdToIndex[scriptId]!;
|
|
scriptIndexToScript[scriptIdx] = script;
|
|
List? tokenPosTable = script["tokenPosTable"];
|
|
if (tokenPosTable == null) {
|
|
errorMessages.add("Script with uri ${script['uri']} "
|
|
"and id ${script['id']} "
|
|
"has null tokenPosTable.");
|
|
} else if (tokenPosTable.isEmpty) {
|
|
errorMessages.add("Script with uri ${script['uri']} "
|
|
"and id ${script['id']} "
|
|
"has empty tokenPosTable.");
|
|
}
|
|
}
|
|
|
|
List<Map> ranges = sourceReport["ranges"].cast<Map>();
|
|
Set<int> scriptIndexesSet = new Set<int>.from(scriptIndexToScript.keys);
|
|
for (Map range in ranges) {
|
|
if (scriptIndexesSet.contains(range["scriptIndex"])) {
|
|
Set<int> positions = new Set<int>();
|
|
positions.add(range["startPos"]);
|
|
positions.add(range["endPos"]);
|
|
Map coverage = range["coverage"];
|
|
for (int pos in coverage["hits"]) {
|
|
positions.add(pos);
|
|
if (lib1scriptIndices.contains(range["scriptIndex"])) {
|
|
hits.add(pos);
|
|
}
|
|
}
|
|
for (int pos in coverage["misses"]) {
|
|
positions.add(pos);
|
|
}
|
|
if (range["possibleBreakpoints"] != null) {
|
|
for (int pos in range["possibleBreakpoints"]) {
|
|
positions.add(pos);
|
|
}
|
|
}
|
|
Map script = scriptIndexToScript[range["scriptIndex"]]!;
|
|
Set<int> knownPositions = new Set<int>();
|
|
Map<int, int> tokenPosToLine = {};
|
|
if (script["tokenPosTable"] != null) {
|
|
for (List tokenPosTableLine in script["tokenPosTable"]) {
|
|
for (int i = 1; i < tokenPosTableLine.length; i += 2) {
|
|
tokenPosToLine[tokenPosTableLine[i]] = tokenPosTableLine[0];
|
|
knownPositions.add(tokenPosTableLine[i]);
|
|
}
|
|
}
|
|
}
|
|
for (int pos in positions) {
|
|
if (!knownPositions.contains(pos)) {
|
|
errorMessages.add("Script with uri ${script['uri']} "
|
|
"and id ${script['id']} "
|
|
"references position $pos which cannot be translated to "
|
|
"line and column.");
|
|
}
|
|
}
|
|
|
|
if (coverageForLines != null) {
|
|
for (int pos in coverage["hits"]) {
|
|
if (lib1scriptIndices.contains(range["scriptIndex"])) {
|
|
coverageForLines.add(tokenPosToLine[pos]!);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
expect(errorMessages, isEmpty);
|
|
if (resume) {
|
|
remoteVm.resume();
|
|
}
|
|
return hits;
|
|
}
|
|
|
|
group('multiple kernels', () {
|
|
late Directory mytest;
|
|
late File main;
|
|
late File lib;
|
|
late Process vm;
|
|
setUpAll(() {
|
|
mytest = Directory.systemTemp.createTempSync('incremental');
|
|
main = new File('${mytest.path}/main.dart')..createSync();
|
|
main.writeAsStringSync("""
|
|
import 'lib.dart';
|
|
main() => print(foo());
|
|
class C1 extends Object with C3, C2 {
|
|
c1method() {
|
|
print("c1");
|
|
}
|
|
}
|
|
class C3 {
|
|
c3method() {
|
|
print("c3");
|
|
}
|
|
}
|
|
""");
|
|
lib = new File('${mytest.path}/lib.dart')..createSync();
|
|
lib.writeAsStringSync("""
|
|
import 'main.dart';
|
|
foo() => 'foo';
|
|
main() => print('bar');
|
|
mixin C2 on C3 {
|
|
c2method() {
|
|
print("c2");
|
|
}
|
|
}
|
|
""");
|
|
});
|
|
|
|
tearDownAll(() {
|
|
try {
|
|
mytest.deleteSync(recursive: true);
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
try {
|
|
vm.kill();
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
});
|
|
|
|
compileAndSerialize(
|
|
File mainDill, File libDill, IncrementalCompiler compiler) async {
|
|
IncrementalCompilerResult compilerResult = await compiler.compile();
|
|
Component component = compilerResult.component;
|
|
new BinaryPrinter(new DevNullSink<List<int>>())
|
|
.writeComponentFile(component);
|
|
IOSink sink = mainDill.openWrite();
|
|
BinaryPrinter printer = new BinaryPrinter(sink,
|
|
libraryFilter: (lib) => lib.fileUri.path.endsWith("main.dart"));
|
|
printer.writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
sink = libDill.openWrite();
|
|
printer = new BinaryPrinter(sink,
|
|
libraryFilter: (lib) => lib.fileUri.path.endsWith("lib.dart"));
|
|
printer.writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
}
|
|
|
|
test('main first, lib second', () async {
|
|
Directory dir = mytest.createTempSync();
|
|
File mainDill = File(p.join(dir.path, p.basename(main.path + ".dill")));
|
|
File libDill = File(p.join(dir.path, p.basename(lib.path + ".dill")));
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(options, [main.uri]);
|
|
await compileAndSerialize(mainDill, libDill, compiler);
|
|
|
|
var list = new File(p.join(dir.path, 'myMain.dilllist'))..createSync();
|
|
list.writeAsStringSync("#@dill\n${mainDill.path}\n${libDill.path}\n");
|
|
vm =
|
|
await Process.start(Platform.resolvedExecutable, <String>[list.path]);
|
|
|
|
final splitter = new LineSplitter();
|
|
Completer<String> portLineCompleter = new Completer<String>();
|
|
vm.stdout.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stdout: $s");
|
|
if (!portLineCompleter.isCompleted) {
|
|
portLineCompleter.complete(s);
|
|
}
|
|
});
|
|
vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stderr: $s");
|
|
});
|
|
expect(await portLineCompleter.future, equals('foo'));
|
|
print("Compiler terminated with ${await vm.exitCode} exit code");
|
|
});
|
|
|
|
test('main second, lib first', () async {
|
|
Directory dir = mytest.createTempSync();
|
|
File mainDill = File(p.join(dir.path, p.basename(main.path + ".dill")));
|
|
File libDill = File(p.join(dir.path, p.basename(lib.path + ".dill")));
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(options, [lib.uri]);
|
|
await compileAndSerialize(mainDill, libDill, compiler);
|
|
|
|
var list = new File(p.join(dir.path, 'myMain.dilllist'))..createSync();
|
|
list.writeAsStringSync("#@dill\n${libDill.path}\n${mainDill.path}\n");
|
|
vm =
|
|
await Process.start(Platform.resolvedExecutable, <String>[list.path]);
|
|
|
|
final splitter = new LineSplitter();
|
|
|
|
Completer<String> portLineCompleter = new Completer<String>();
|
|
vm.stdout.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stdout: $s");
|
|
if (!portLineCompleter.isCompleted) {
|
|
portLineCompleter.complete(s);
|
|
}
|
|
});
|
|
vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stderr: $s");
|
|
});
|
|
expect(await portLineCompleter.future, equals('bar'));
|
|
print("Compiler terminated with ${await vm.exitCode} exit code");
|
|
});
|
|
|
|
test('empty list', () async {
|
|
var list = new File(p.join(mytest.path, 'myMain.dilllist'))..createSync();
|
|
list.writeAsStringSync("#@dill\n");
|
|
vm =
|
|
await Process.start(Platform.resolvedExecutable, <String>[list.path]);
|
|
|
|
Completer<int> exitCodeCompleter = new Completer<int>();
|
|
vm.exitCode.then((exitCode) {
|
|
print("Compiler terminated with $exitCode exit code");
|
|
exitCodeCompleter.complete(exitCode);
|
|
});
|
|
expect(await exitCodeCompleter.future, equals(254));
|
|
});
|
|
|
|
test('fallback to source compilation if fail to load', () async {
|
|
var list = new File('${mytest.path}/myMain.dilllist')..createSync();
|
|
list.writeAsStringSync("main() => print('baz');\n");
|
|
vm =
|
|
await Process.start(Platform.resolvedExecutable, <String>[list.path]);
|
|
|
|
final splitter = new LineSplitter();
|
|
|
|
Completer<String> portLineCompleter = new Completer<String>();
|
|
vm.stdout.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stdout: $s");
|
|
if (!portLineCompleter.isCompleted) {
|
|
portLineCompleter.complete(s);
|
|
}
|
|
});
|
|
vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stderr: $s");
|
|
});
|
|
expect(await portLineCompleter.future, equals('baz'));
|
|
print("Compiler terminated with ${await vm.exitCode} exit code");
|
|
});
|
|
|
|
test('relative paths', () async {
|
|
Directory dir = mytest.createTempSync();
|
|
File mainDill = File(p.join(dir.path, p.basename(main.path + ".dill")));
|
|
File libDill = File(p.join(dir.path, p.basename(lib.path + ".dill")));
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(options, [main.uri]);
|
|
await compileAndSerialize(mainDill, libDill, compiler);
|
|
|
|
var list = new File(p.join(dir.path, 'myMain.dilllist'))..createSync();
|
|
list.writeAsStringSync("#@dill\nmain.dart.dill\nlib.dart.dill\n");
|
|
Directory runFrom = new Directory(dir.path + "/runFrom")..createSync();
|
|
vm = await Process.start(Platform.resolvedExecutable, <String>[list.path],
|
|
workingDirectory: runFrom.path);
|
|
|
|
final splitter = new LineSplitter();
|
|
Completer<String> portLineCompleter = new Completer<String>();
|
|
vm.stdout.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stdout: $s");
|
|
if (!portLineCompleter.isCompleted) {
|
|
portLineCompleter.complete(s);
|
|
}
|
|
});
|
|
vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stderr: $s");
|
|
});
|
|
expect(await portLineCompleter.future, equals('foo'));
|
|
print("Compiler terminated with ${await vm.exitCode} exit code");
|
|
});
|
|
|
|
test('collect coverage', () async {
|
|
Directory dir = mytest.createTempSync();
|
|
File mainDill = File(p.join(dir.path, p.basename(main.path + ".dill")));
|
|
File libDill = File(p.join(dir.path, p.basename(lib.path + ".dill")));
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(options, [main.uri]);
|
|
await compileAndSerialize(mainDill, libDill, compiler);
|
|
|
|
var list = new File(p.join(dir.path, 'myMain.dilllist'))..createSync();
|
|
list.writeAsStringSync("#@dill\n${mainDill.path}\n${libDill.path}\n");
|
|
vm = await Process.start(Platform.resolvedExecutable, <String>[
|
|
"--pause-isolates-on-exit",
|
|
"--enable-vm-service:0",
|
|
"--disable-service-auth-codes",
|
|
"--disable-dart-dev",
|
|
list.path
|
|
]);
|
|
|
|
const kDartVMServiceListening = 'The Dart VM service is listening on ';
|
|
final RegExp dartVMServicePortRegExp = new RegExp(
|
|
"The Dart VM service is listening on http://127.0.0.1:\([0-9]*\)");
|
|
int port;
|
|
final splitter = new LineSplitter();
|
|
Completer<String> portLineCompleter = new Completer<String>();
|
|
vm.stdout
|
|
.transform(utf8.decoder)
|
|
.transform(splitter)
|
|
.listen((String s) async {
|
|
if (s.startsWith(kDartVMServiceListening)) {
|
|
expect(dartVMServicePortRegExp.hasMatch(s), isTrue);
|
|
final match = dartVMServicePortRegExp.firstMatch(s)!;
|
|
port = int.parse(match.group(1)!);
|
|
await collectAndCheckCoverageData(port, true);
|
|
if (!portLineCompleter.isCompleted) {
|
|
portLineCompleter.complete("done");
|
|
}
|
|
}
|
|
print("vm stdout: $s");
|
|
});
|
|
vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stderr: $s");
|
|
});
|
|
await portLineCompleter.future;
|
|
print("Compiler terminated with ${await vm.exitCode} exit code");
|
|
});
|
|
});
|
|
|
|
group('multiple kernels constant coverage', () {
|
|
late Directory mytest;
|
|
late File main;
|
|
late File lib1;
|
|
late int lineForUnnamedConstructor;
|
|
late int lineForNamedConstructor;
|
|
late Process vm;
|
|
setUpAll(() {
|
|
mytest = Directory.systemTemp.createTempSync('incremental');
|
|
main = new File('${mytest.path}/main.dart')..createSync();
|
|
main.writeAsStringSync("""
|
|
// This file - combined with the lib - should have coverage for both
|
|
// constructors of Foo.
|
|
import 'lib1.dart' as lib1;
|
|
|
|
void testFunction() {
|
|
const foo = lib1.Foo.named();
|
|
const foo2 = lib1.Foo.named();
|
|
if (!identical(foo, foo2)) throw "what?";
|
|
}
|
|
|
|
main() {
|
|
lib1.testFunction();
|
|
testFunction();
|
|
print("main");
|
|
}
|
|
""");
|
|
lib1 = new File('${mytest.path}/lib1.dart')..createSync();
|
|
lib1.writeAsStringSync("""
|
|
// Compiling this file should mark the default constructor - but not the
|
|
// named constructor - as having coverage.
|
|
class Foo {
|
|
final int x;
|
|
const Foo([int? x]) : this.x = x ?? 42;
|
|
const Foo.named([int? x]) : this.x = x ?? 42;
|
|
}
|
|
|
|
void testFunction() {
|
|
const foo = Foo();
|
|
const foo2 = Foo();
|
|
if (!identical(foo, foo2)) throw "what?";
|
|
}
|
|
|
|
main() {
|
|
testFunction();
|
|
print("lib1");
|
|
}
|
|
""");
|
|
lineForUnnamedConstructor = 5;
|
|
lineForNamedConstructor = 6;
|
|
});
|
|
|
|
tearDownAll(() {
|
|
try {
|
|
mytest.deleteSync(recursive: true);
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
try {
|
|
vm.kill();
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
});
|
|
|
|
Future<Set<int>> runAndGetLineCoverage(
|
|
File list, String expectStdoutContains) async {
|
|
vm = await Process.start(Platform.resolvedExecutable, <String>[
|
|
"--pause-isolates-on-exit",
|
|
"--enable-vm-service:0",
|
|
"--disable-service-auth-codes",
|
|
"--disable-dart-dev",
|
|
list.path
|
|
]);
|
|
|
|
const kDartVMServiceListening = 'The Dart VM service is listening on ';
|
|
final RegExp dartVMServicePortRegExp = new RegExp(
|
|
"The Dart VM service is listening on http://127.0.0.1:\([0-9]*\)");
|
|
int port;
|
|
final splitter = new LineSplitter();
|
|
Completer<String> portLineCompleter = new Completer<String>();
|
|
Set<int> coverageLines = {};
|
|
bool foundExpectedString = false;
|
|
vm.stdout
|
|
.transform(utf8.decoder)
|
|
.transform(splitter)
|
|
.listen((String s) async {
|
|
if (s == expectStdoutContains) {
|
|
foundExpectedString = true;
|
|
}
|
|
if (s.startsWith(kDartVMServiceListening)) {
|
|
expect(dartVMServicePortRegExp.hasMatch(s), isTrue);
|
|
final match = dartVMServicePortRegExp.firstMatch(s)!;
|
|
port = int.parse(match.group(1)!);
|
|
await collectAndCheckCoverageData(port, true,
|
|
onGetAllVerifyCount: false, coverageForLines: coverageLines);
|
|
if (!portLineCompleter.isCompleted) {
|
|
portLineCompleter.complete("done");
|
|
}
|
|
}
|
|
print("vm stdout: $s");
|
|
});
|
|
vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stderr: $s");
|
|
});
|
|
await portLineCompleter.future;
|
|
print("Compiler terminated with ${await vm.exitCode} exit code");
|
|
expect(foundExpectedString, isTrue);
|
|
return coverageLines;
|
|
}
|
|
|
|
test('compile separately, check coverage', () async {
|
|
Directory dir = mytest.createTempSync();
|
|
|
|
// First compile lib, run and verify coverage (un-named constructor
|
|
// covered, but not the named constructor).
|
|
// Note that it's called 'lib1' to match with expectations from coverage
|
|
// collector helper in this file.
|
|
File libDill = File(p.join(dir.path, p.basename(lib1.path + ".dill")));
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(options, [lib1.uri]);
|
|
IncrementalCompilerResult compilerResult = await compiler.compile();
|
|
Component component = compilerResult.component;
|
|
expect(component.libraries.length, equals(1));
|
|
expect(component.libraries.single.fileUri, equals(lib1.uri));
|
|
IOSink sink = libDill.openWrite();
|
|
BinaryPrinter printer = new BinaryPrinter(sink);
|
|
printer.writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
File list = new File(p.join(dir.path, 'dill.list'))..createSync();
|
|
list.writeAsStringSync("#@dill\n${libDill.path}\n");
|
|
Set<int> lineCoverage = await runAndGetLineCoverage(list, "lib1");
|
|
// Expect coverage for unnamed constructor but not for the named one.
|
|
expect(
|
|
lineCoverage.intersection(
|
|
{lineForUnnamedConstructor, lineForNamedConstructor}),
|
|
equals({lineForUnnamedConstructor}));
|
|
|
|
try {
|
|
vm.kill();
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
// Accept the compile to not include the lib again.
|
|
compiler.accept();
|
|
|
|
// Then compile lib, run and verify coverage (un-named constructor
|
|
// covered, and the named constructor covered too).
|
|
File mainDill = File(p.join(dir.path, p.basename(main.path + ".dill")));
|
|
compilerResult = await compiler.compile(entryPoints: [main.uri]);
|
|
component = compilerResult.component;
|
|
expect(component.libraries.length, equals(1));
|
|
expect(component.libraries.single.fileUri, equals(main.uri));
|
|
sink = mainDill.openWrite();
|
|
printer = new BinaryPrinter(sink);
|
|
printer.writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
list.writeAsStringSync("#@dill\n${mainDill.path}\n${libDill.path}\n");
|
|
lineCoverage = await runAndGetLineCoverage(list, "main");
|
|
|
|
// Expect coverage for both unnamed constructor and for the named one.
|
|
expect(
|
|
lineCoverage.intersection(
|
|
{lineForUnnamedConstructor, lineForNamedConstructor}),
|
|
equals({lineForUnnamedConstructor, lineForNamedConstructor}));
|
|
|
|
try {
|
|
vm.kill();
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
// Accept the compile to not include the lib again.
|
|
compiler.accept();
|
|
|
|
// Finally, change lib to shift the constructors so the old line numbers
|
|
// doesn't match. Compile lib by itself, compile lib, run with the old
|
|
// main and verify coverage is still correct (both un-named constructor
|
|
// and named constructor (at new line numbers) are covered, and the old
|
|
// line numbers are not coverage.
|
|
|
|
lib1.writeAsStringSync("""
|
|
//
|
|
// Shift lines down by five
|
|
// lines so the original
|
|
// lines can't be covered
|
|
//
|
|
class Foo {
|
|
final int x;
|
|
const Foo([int? x]) : this.x = x ?? 42;
|
|
const Foo.named([int? x]) : this.x = x ?? 42;
|
|
}
|
|
|
|
void testFunction() {
|
|
const foo = Foo();
|
|
const foo2 = Foo();
|
|
if (!identical(foo, foo2)) throw "what?";
|
|
}
|
|
|
|
main() {
|
|
testFunction();
|
|
print("lib1");
|
|
}
|
|
""");
|
|
int newLineForUnnamedConstructor = 8;
|
|
int newLineForNamedConstructor = 9;
|
|
compiler.invalidate(lib1.uri);
|
|
compilerResult = await compiler.compile(entryPoints: [lib1.uri]);
|
|
component = compilerResult.component;
|
|
expect(component.libraries.length, equals(1));
|
|
expect(component.libraries.single.fileUri, equals(lib1.uri));
|
|
sink = libDill.openWrite();
|
|
printer = new BinaryPrinter(sink);
|
|
printer.writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
list.writeAsStringSync("#@dill\n${mainDill.path}\n${libDill.path}\n");
|
|
lineCoverage = await runAndGetLineCoverage(list, "main");
|
|
|
|
// Expect coverage for both unnamed constructor and for the named one on
|
|
// the new positions, but no coverage on the old positions.
|
|
expect(
|
|
lineCoverage.intersection({
|
|
lineForUnnamedConstructor,
|
|
lineForNamedConstructor,
|
|
newLineForUnnamedConstructor,
|
|
newLineForNamedConstructor
|
|
}),
|
|
equals({newLineForUnnamedConstructor, newLineForNamedConstructor}));
|
|
|
|
try {
|
|
vm.kill();
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
// Accept the compile to not include the lib again.
|
|
compiler.accept();
|
|
});
|
|
});
|
|
|
|
group('multiple kernels 2', () {
|
|
late Directory mytest;
|
|
late File main;
|
|
late File lib1;
|
|
late File lib2;
|
|
late Process vm;
|
|
setUpAll(() {
|
|
mytest = Directory.systemTemp.createTempSync('incremental');
|
|
main = new File('${mytest.path}/main.dart')..createSync();
|
|
main.writeAsStringSync("""
|
|
import 'lib1.dart';
|
|
import 'lib2.dart';
|
|
|
|
void main() {
|
|
TestA().foo();
|
|
bar();
|
|
}
|
|
|
|
class TestA with A {}
|
|
""");
|
|
lib1 = new File('${mytest.path}/lib1.dart')..createSync();
|
|
lib1.writeAsStringSync("""
|
|
mixin A {
|
|
void foo() {
|
|
print('foo');
|
|
}
|
|
void bar() {
|
|
print('bar');
|
|
}
|
|
}
|
|
""");
|
|
lib2 = new File('${mytest.path}/lib2.dart')..createSync();
|
|
lib2.writeAsStringSync("""
|
|
import 'lib1.dart';
|
|
void bar() {
|
|
TestB().bar();
|
|
}
|
|
class TestB with A {}
|
|
""");
|
|
});
|
|
|
|
tearDownAll(() {
|
|
try {
|
|
mytest.deleteSync(recursive: true);
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
try {
|
|
vm.kill();
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
});
|
|
|
|
compileAndSerialize(File mainDill, File lib1Dill, File lib2Dill,
|
|
IncrementalCompiler compiler) async {
|
|
IncrementalCompilerResult compilerResult = await compiler.compile();
|
|
Component component = compilerResult.component;
|
|
new BinaryPrinter(new DevNullSink<List<int>>())
|
|
.writeComponentFile(component);
|
|
IOSink sink = mainDill.openWrite();
|
|
BinaryPrinter printer = new BinaryPrinter(sink,
|
|
libraryFilter: (lib) => lib.fileUri.path.endsWith("main.dart"));
|
|
printer.writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
sink = lib1Dill.openWrite();
|
|
printer = new BinaryPrinter(sink,
|
|
libraryFilter: (lib) => lib.fileUri.path.endsWith("lib1.dart"));
|
|
printer.writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
sink = lib2Dill.openWrite();
|
|
printer = new BinaryPrinter(sink,
|
|
libraryFilter: (lib) => lib.fileUri.path.endsWith("lib2.dart"));
|
|
printer.writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
}
|
|
|
|
test('collect coverage hits', () async {
|
|
Directory dir = mytest.createTempSync();
|
|
File mainDill = File(p.join(dir.path, p.basename(main.path + ".dill")));
|
|
File lib1Dill = File(p.join(dir.path, p.basename(lib1.path + ".dill")));
|
|
File lib2Dill = File(p.join(dir.path, p.basename(lib2.path + ".dill")));
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(options, [main.uri]);
|
|
await compileAndSerialize(mainDill, lib1Dill, lib2Dill, compiler);
|
|
|
|
var list = new File(p.join(dir.path, 'myMain.dilllist'))..createSync();
|
|
list.writeAsStringSync(
|
|
"#@dill\n${mainDill.path}\n${lib1Dill.path}\n${lib2Dill.path}\n");
|
|
vm = await Process.start(Platform.resolvedExecutable, <String>[
|
|
"--pause-isolates-on-exit",
|
|
"--enable-vm-service:0",
|
|
"--disable-service-auth-codes",
|
|
"--disable-dart-dev",
|
|
list.path
|
|
]);
|
|
|
|
const kDartVMServiceListening = 'The Dart VM service is listening on ';
|
|
final RegExp dartVMServicePortRegExp = new RegExp(
|
|
"The Dart VM service is listening on http://127.0.0.1:\([0-9]*\)");
|
|
int port;
|
|
final splitter = new LineSplitter();
|
|
Completer<String> portLineCompleter = new Completer<String>();
|
|
vm.stdout
|
|
.transform(utf8.decoder)
|
|
.transform(splitter)
|
|
.listen((String s) async {
|
|
if (s.startsWith(kDartVMServiceListening)) {
|
|
expect(dartVMServicePortRegExp.hasMatch(s), isTrue);
|
|
final match = dartVMServicePortRegExp.firstMatch(s)!;
|
|
port = int.parse(match.group(1)!);
|
|
Set<int> hits1 =
|
|
await collectAndCheckCoverageData(port, true, resume: false);
|
|
Set<int> hits2 =
|
|
await collectAndCheckCoverageData(port, false, resume: true);
|
|
expect(hits1.toList()..sort(), equals(hits2.toList()..sort()));
|
|
if (!portLineCompleter.isCompleted) {
|
|
portLineCompleter.complete("done");
|
|
}
|
|
}
|
|
print("vm stdout: $s");
|
|
});
|
|
vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stderr: $s");
|
|
});
|
|
await portLineCompleter.future;
|
|
print("Compiler terminated with ${await vm.exitCode} exit code");
|
|
});
|
|
});
|
|
|
|
group('reload', () {
|
|
late Directory mytest;
|
|
|
|
setUpAll(() {
|
|
mytest = Directory.systemTemp.createTempSync('incremental');
|
|
});
|
|
|
|
tearDownAll(() {
|
|
try {
|
|
mytest.deleteSync(recursive: true);
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
});
|
|
|
|
test('picks up after rejected delta', () async {
|
|
var file = new File('${mytest.path}/foo.dart')..createSync();
|
|
file.writeAsStringSync("import 'bar.dart';\n"
|
|
"import 'baz.dart';\n"
|
|
"main() {\n"
|
|
" new A();\n"
|
|
" openReceivePortSoWeWontDie();"
|
|
"}\n");
|
|
|
|
var fileBar = new File('${mytest.path}/bar.dart')..createSync();
|
|
fileBar.writeAsStringSync("class A<T> { int _a = 0; }\n");
|
|
|
|
var fileBaz = new File('${mytest.path}/baz.dart')..createSync();
|
|
fileBaz.writeAsStringSync("import 'dart:isolate';\n"
|
|
"openReceivePortSoWeWontDie() { new RawReceivePort(); }\n");
|
|
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(options, [file.uri]);
|
|
IncrementalCompilerResult compilerResult = await compiler.compile();
|
|
Component component = compilerResult.component;
|
|
|
|
File outputFile = new File('${mytest.path}/foo.dart.dill');
|
|
await _writeProgramToFile(component, outputFile);
|
|
|
|
final List<String> vmArgs = [
|
|
'--trace_reload',
|
|
'--trace_reload_verbose',
|
|
'--enable-vm-service=0', // Note: use 0 to avoid port collisions.
|
|
'--pause_isolates_on_start',
|
|
'--disable-service-auth-codes',
|
|
'--disable-dart-dev',
|
|
outputFile.path
|
|
];
|
|
final vm = await Process.start(Platform.resolvedExecutable, vmArgs);
|
|
|
|
final splitter = new LineSplitter();
|
|
|
|
vm.exitCode.then((exitCode) {
|
|
print("Compiler terminated with $exitCode exit code");
|
|
});
|
|
|
|
Completer<String> portLineCompleter = new Completer<String>();
|
|
vm.stdout.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stdout: $s");
|
|
if (!portLineCompleter.isCompleted) {
|
|
portLineCompleter.complete(s);
|
|
}
|
|
});
|
|
|
|
vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stderr: $s");
|
|
});
|
|
|
|
String portLine = await portLineCompleter.future;
|
|
|
|
final RegExp dartVMServicePortRegExp = new RegExp(
|
|
"The Dart VM service is listening on http://127.0.0.1:\([0-9]*\)");
|
|
expect(dartVMServicePortRegExp.hasMatch(portLine), isTrue);
|
|
final match = dartVMServicePortRegExp.firstMatch(portLine)!;
|
|
final port = int.parse(match.group(1)!);
|
|
|
|
var remoteVm = new RemoteVm(port);
|
|
await remoteVm.resume();
|
|
compiler.accept();
|
|
|
|
// Confirm that without changes VM reloads nothing.
|
|
compilerResult = await compiler.compile();
|
|
component = compilerResult.component;
|
|
await _writeProgramToFile(component, outputFile);
|
|
var reloadResult = await remoteVm.reload(new Uri.file(outputFile.path));
|
|
expect(reloadResult['success'], isTrue);
|
|
expect(reloadResult['details']['loadedLibraryCount'], equals(0));
|
|
|
|
// Introduce a change that force VM to reject the change.
|
|
fileBar.writeAsStringSync("class A<T,U> { int _a = 0; }\n");
|
|
compiler.invalidate(fileBar.uri);
|
|
compilerResult = await compiler.compile();
|
|
component = compilerResult.component;
|
|
await _writeProgramToFile(component, outputFile);
|
|
reloadResult = await remoteVm.reload(new Uri.file(outputFile.path));
|
|
expect(reloadResult['success'], isFalse);
|
|
|
|
// Fix a change so VM is happy to accept the change.
|
|
fileBar.writeAsStringSync("class A<T> { int _a = 0; hi() => _a; }\n");
|
|
compiler.invalidate(fileBar.uri);
|
|
compilerResult = await compiler.compile();
|
|
component = compilerResult.component;
|
|
await _writeProgramToFile(component, outputFile);
|
|
reloadResult = await remoteVm.reload(new Uri.file(outputFile.path));
|
|
expect(reloadResult['success'], isTrue);
|
|
expect(reloadResult['details']['loadedLibraryCount'], equals(2));
|
|
compiler.accept();
|
|
|
|
vm.kill();
|
|
});
|
|
});
|
|
|
|
group('reject', () {
|
|
late Directory mytest;
|
|
setUpAll(() {
|
|
mytest = Directory.systemTemp.createTempSync('incremental_reject');
|
|
});
|
|
|
|
tearDownAll(() {
|
|
try {
|
|
mytest.deleteSync(recursive: true);
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
});
|
|
|
|
test('compile, reject, compile again', () async {
|
|
new Directory(mytest.path + "/.dart_tool").createSync();
|
|
var packageUri =
|
|
Uri.file('${mytest.path}/.dart_tool/package_config.json');
|
|
new File(packageUri.toFilePath()).writeAsStringSync(jsonEncode({
|
|
"configVersion": 2,
|
|
"packages": [
|
|
{
|
|
"name": "foo",
|
|
"rootUri": "..",
|
|
"packageUri": "lib",
|
|
"languageVersion": "2.7",
|
|
},
|
|
],
|
|
}));
|
|
new Directory(mytest.path + "/lib").createSync();
|
|
var fooUri = Uri.file('${mytest.path}/lib/foo.dart');
|
|
new File(fooUri.toFilePath())
|
|
.writeAsStringSync("import 'package:foo/bar.dart';\n"
|
|
"import 'package:foo/baz.dart';\n"
|
|
"main() {\n"
|
|
" new A();\n"
|
|
" openReceivePortSoWeWontDie();"
|
|
"}\n");
|
|
|
|
var barUri = Uri.file('${mytest.path}/lib/bar.dart');
|
|
new File(barUri.toFilePath())
|
|
.writeAsStringSync("class A { static int a; }\n");
|
|
|
|
var bazUri = Uri.file('${mytest.path}/lib/baz.dart');
|
|
new File(bazUri.toFilePath()).writeAsStringSync("import 'dart:isolate';\n"
|
|
"openReceivePortSoWeWontDie() { new RawReceivePort(); }\n");
|
|
|
|
Uri packageEntry = Uri.parse('package:foo/foo.dart');
|
|
|
|
CompilerOptions optionsModified = getFreshOptions()
|
|
..packagesFileUri = packageUri;
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(optionsModified, [packageEntry]);
|
|
{
|
|
IncrementalCompilerResult compilerResult =
|
|
await compiler.compile(entryPoints: [packageEntry]);
|
|
Component component = compilerResult.component;
|
|
File outputFile = new File('${mytest.path}/foo.dart.dill');
|
|
await _writeProgramToFile(component, outputFile);
|
|
}
|
|
compiler.accept();
|
|
{
|
|
Procedure? procedure = await compiler.compileExpression(
|
|
'a',
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
'package:foo/bar.dart',
|
|
'A',
|
|
null,
|
|
true);
|
|
expect(procedure, isNotNull);
|
|
}
|
|
|
|
new File(barUri.toFilePath())
|
|
.writeAsStringSync("class A { static int b; }\n");
|
|
compiler.invalidate(barUri);
|
|
{
|
|
IncrementalCompilerResult compilerResult =
|
|
await compiler.compile(entryPoints: [packageEntry]);
|
|
Component component = compilerResult.component;
|
|
File outputFile = new File('${mytest.path}/foo1.dart.dill');
|
|
await _writeProgramToFile(component, outputFile);
|
|
}
|
|
await compiler.reject();
|
|
{
|
|
Procedure? procedure = await compiler.compileExpression(
|
|
'a',
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
'package:foo/bar.dart',
|
|
'A',
|
|
null,
|
|
true);
|
|
expect(procedure, isNotNull);
|
|
}
|
|
});
|
|
|
|
/// This test basically verifies that components `relink` method is
|
|
/// correctly called when rejecting (i.e. logically going back in time to
|
|
/// before a rejected compilation).
|
|
test('check links after reject', () async {
|
|
final Uri fooUri = Uri.file('${mytest.path}/foo.dart');
|
|
new File.fromUri(fooUri).writeAsStringSync("""
|
|
import 'bar.dart';
|
|
main() {
|
|
A a = new A();
|
|
print(a.b());
|
|
print(A.a);
|
|
}
|
|
""");
|
|
|
|
final Uri barUri = Uri.file('${mytest.path}/bar.dart');
|
|
new File.fromUri(barUri).writeAsStringSync("""
|
|
class A {
|
|
static int a;
|
|
int b() { return 42; }
|
|
}
|
|
""");
|
|
|
|
final CompilerOptions optionsModified = getFreshOptions();
|
|
optionsModified.explicitExperimentalFlags[
|
|
ExperimentalFlag.alternativeInvalidationStrategy] = true;
|
|
|
|
final IncrementalCompiler compiler =
|
|
new IncrementalCompiler(optionsModified, [fooUri]);
|
|
Library fooLib;
|
|
Library barLib;
|
|
{
|
|
final IncrementalCompilerResult compilerResult =
|
|
await compiler.compile(entryPoints: [fooUri]);
|
|
final Component component = compilerResult.component;
|
|
expect(component.libraries.length, equals(2));
|
|
fooLib = component.libraries.firstWhere((lib) => lib.fileUri == fooUri);
|
|
barLib = component.libraries.firstWhere((lib) => lib.fileUri == barUri);
|
|
// Verify that foo only has links to this bar.
|
|
final LibraryReferenceCollector lrc = new LibraryReferenceCollector();
|
|
fooLib.accept(lrc);
|
|
expect(lrc.librariesReferenced, equals(<Library>{barLib}));
|
|
}
|
|
compiler.accept();
|
|
{
|
|
final Procedure procedure = (await compiler.compileExpression(
|
|
'a',
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
barUri.toString(),
|
|
'A',
|
|
null,
|
|
true))!;
|
|
// Verify that the expression only has links to the only bar we know
|
|
// about.
|
|
final LibraryReferenceCollector lrc = new LibraryReferenceCollector();
|
|
procedure.accept(lrc);
|
|
expect(lrc.librariesReferenced, equals(<Library>{barLib}));
|
|
}
|
|
|
|
new File.fromUri(barUri).writeAsStringSync("""
|
|
class A {
|
|
static int a;
|
|
int b() { return 84; }
|
|
}
|
|
""");
|
|
compiler.invalidate(barUri);
|
|
{
|
|
final IncrementalCompilerResult compilerResult =
|
|
await compiler.compile(entryPoints: [fooUri]);
|
|
final Component component = compilerResult.component;
|
|
final Library? fooLib2 = component.libraries
|
|
.firstWhereOrNull((lib) => lib.fileUri == fooUri);
|
|
expect(fooLib2, isNull);
|
|
final Library barLib2 =
|
|
component.libraries.firstWhere((lib) => lib.fileUri == barUri);
|
|
// Verify that the fooLib (we only have the original one) only has
|
|
// links to the newly compiled bar.
|
|
final LibraryReferenceCollector lrc = new LibraryReferenceCollector();
|
|
fooLib.accept(lrc);
|
|
expect(lrc.librariesReferenced, equals(<Library>{barLib2}));
|
|
}
|
|
await compiler.reject();
|
|
{
|
|
// Verify that the original foo library only has links to the original
|
|
// compiled bar.
|
|
final LibraryReferenceCollector lrc = new LibraryReferenceCollector();
|
|
fooLib.accept(lrc);
|
|
expect(lrc.librariesReferenced, equals(<Library>{barLib}));
|
|
}
|
|
{
|
|
// Verify that the saved "last known good" component only contains links
|
|
// to the original 'foo' and 'bar' libraries.
|
|
final LibraryReferenceCollector lrc = new LibraryReferenceCollector();
|
|
compiler.lastKnownGoodResult!.component.accept(lrc);
|
|
expect(lrc.librariesReferenced, equals(<Library>{fooLib, barLib}));
|
|
}
|
|
{
|
|
final Procedure procedure = (await compiler.compileExpression(
|
|
'a',
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
<String>[],
|
|
barUri.toString(),
|
|
'A',
|
|
null,
|
|
true))!;
|
|
// Verify that the expression only has links to the original bar.
|
|
final LibraryReferenceCollector lrc = new LibraryReferenceCollector();
|
|
procedure.accept(lrc);
|
|
expect(lrc.librariesReferenced, equals(<Library>{barLib}));
|
|
}
|
|
});
|
|
});
|
|
|
|
group('expression evaluation', () {
|
|
late Directory mytest;
|
|
late Process vm;
|
|
|
|
setUpAll(() {
|
|
mytest = Directory.systemTemp.createTempSync('expression_evaluation');
|
|
});
|
|
|
|
tearDownAll(() {
|
|
try {
|
|
mytest.deleteSync(recursive: true);
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
try {
|
|
vm.kill();
|
|
} catch (_) {
|
|
// Ignore errors;
|
|
}
|
|
});
|
|
|
|
launchBreakAndEvaluate(File scriptOrDill, String scriptUriToBreakIn,
|
|
int lineToBreakAt, List<String> expressionsAndExpectedResults,
|
|
{Future Function(RemoteVm remoteVm)? callback}) async {
|
|
vm = await Process.start(Platform.resolvedExecutable, <String>[
|
|
"--pause-isolates-on-start",
|
|
"--enable-vm-service:0",
|
|
"--disable-service-auth-codes",
|
|
"--disable-dart-dev",
|
|
scriptOrDill.path
|
|
]);
|
|
|
|
const kDartVMServiceListening = 'The Dart VM service is listening on ';
|
|
final RegExp dartVMServicePortRegExp = new RegExp(
|
|
"The Dart VM service is listening on http://127.0.0.1:\([0-9]*\)");
|
|
int port;
|
|
final splitter = new LineSplitter();
|
|
Completer<String> portLineCompleter = new Completer<String>();
|
|
vm.stdout
|
|
.transform(utf8.decoder)
|
|
.transform(splitter)
|
|
.listen((String s) async {
|
|
print("vm stdout: $s");
|
|
if (s.startsWith(kDartVMServiceListening)) {
|
|
expect(dartVMServicePortRegExp.hasMatch(s), isTrue);
|
|
final match = dartVMServicePortRegExp.firstMatch(s)!;
|
|
port = int.parse(match.group(1)!);
|
|
RemoteVm remoteVm = new RemoteVm(port);
|
|
|
|
// Wait for the script to have loaded.
|
|
while (true) {
|
|
Map isolate = await remoteVm.getIsolate();
|
|
Map pauseEvent = isolate["pauseEvent"];
|
|
if (pauseEvent["kind"] == "PauseStart") break;
|
|
}
|
|
|
|
var breakpoint = await findScriptAndBreak(
|
|
remoteVm, scriptUriToBreakIn, lineToBreakAt);
|
|
await remoteVm.resume();
|
|
await waitForScriptToHavePaused(remoteVm);
|
|
await evaluateExpressions(expressionsAndExpectedResults, remoteVm);
|
|
await deletePossibleBreakpoint(remoteVm, breakpoint);
|
|
|
|
if (callback != null) {
|
|
await callback(remoteVm);
|
|
}
|
|
|
|
await remoteVm.resume();
|
|
|
|
if (!portLineCompleter.isCompleted) {
|
|
portLineCompleter.complete("done");
|
|
}
|
|
}
|
|
});
|
|
bool gotStdErrOutput = false;
|
|
vm.stderr.transform(utf8.decoder).transform(splitter).listen((String s) {
|
|
print("vm stderr: $s");
|
|
gotStdErrOutput = true;
|
|
});
|
|
await portLineCompleter.future;
|
|
int exitCode = await vm.exitCode;
|
|
print("Compiler terminated with ${exitCode} exit code");
|
|
expect(exitCode, equals(0));
|
|
expect(gotStdErrOutput, isFalse);
|
|
}
|
|
|
|
test('from source', () async {
|
|
Directory dir = mytest.createTempSync();
|
|
File mainFile = new File.fromUri(dir.uri.resolve("main.dart"));
|
|
mainFile.writeAsStringSync(r"""
|
|
var hello = "Hello";
|
|
main() {
|
|
var s = "$hello world!";
|
|
print(s);
|
|
}
|
|
int extra() { return 22; }
|
|
""");
|
|
|
|
await launchBreakAndEvaluate(mainFile, mainFile.uri.toString(), 4, [
|
|
// 1st expression
|
|
"s.length",
|
|
"12",
|
|
|
|
// 2nd expression
|
|
"s",
|
|
"Hello world!",
|
|
|
|
// 3rd expression
|
|
"hello",
|
|
"Hello",
|
|
|
|
// 4th expression
|
|
"extra()",
|
|
"22",
|
|
]);
|
|
});
|
|
|
|
test('from dill', () async {
|
|
Directory dir = mytest.createTempSync();
|
|
File mainFile = new File.fromUri(dir.uri.resolve("main.dart"));
|
|
mainFile.writeAsStringSync(r"""
|
|
var hello = "Hello";
|
|
main() {
|
|
var s = "$hello world!";
|
|
print(s);
|
|
}
|
|
int extra() { return 22; }
|
|
""");
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(options, [mainFile.uri]);
|
|
IncrementalCompilerResult compilerResult = await compiler.compile();
|
|
Component component = compilerResult.component;
|
|
File mainDill = new File.fromUri(mainFile.uri.resolve("main.dill"));
|
|
IOSink sink = mainDill.openWrite();
|
|
new BinaryPrinter(sink).writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
|
|
mainFile.deleteSync();
|
|
|
|
await launchBreakAndEvaluate(mainDill, mainFile.uri.toString(), 4, [
|
|
// 1st expression
|
|
"s.length",
|
|
"12",
|
|
|
|
// 2nd expression
|
|
"s",
|
|
"Hello world!",
|
|
|
|
// 3rd expression
|
|
"hello",
|
|
"Hello",
|
|
|
|
// 4th expression
|
|
"extra()",
|
|
"22",
|
|
]);
|
|
});
|
|
|
|
test('from dill with reload', () async {
|
|
Directory dir = mytest.createTempSync();
|
|
File mainFile = new File.fromUri(dir.uri.resolve("main.dart"));
|
|
mainFile.writeAsStringSync(r"""
|
|
import 'dart:async';
|
|
import 'helper.dart';
|
|
main() {
|
|
int latestReloadTime = -1;
|
|
int noChangeCount = 0;
|
|
int numChanges = 0;
|
|
new Timer.periodic(new Duration(milliseconds: 5), (timer) async {
|
|
var result = reloadTime();
|
|
if (latestReloadTime != result) {
|
|
latestReloadTime = result;
|
|
numChanges++;
|
|
helperMethod();
|
|
} else {
|
|
noChangeCount++;
|
|
}
|
|
if (latestReloadTime == 42) {
|
|
timer.cancel();
|
|
}
|
|
if (numChanges > 20) {
|
|
timer.cancel();
|
|
}
|
|
if (noChangeCount >= 400) {
|
|
// ~2 seconds with no change.
|
|
throw "Expected to be done but wasn't";
|
|
}
|
|
});
|
|
}
|
|
""");
|
|
File helperFile = new File.fromUri(dir.uri.resolve("helper.dart"));
|
|
helperFile.writeAsStringSync(r"""
|
|
int reloadTime() {
|
|
return 0;
|
|
}
|
|
void helperMethod() {
|
|
var hello = "Hello";
|
|
var s = "$hello world!";
|
|
print(s);
|
|
}
|
|
""");
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(options, [mainFile.uri]);
|
|
IncrementalCompilerResult compilerResult = await compiler.compile();
|
|
Component component = compilerResult.component;
|
|
File mainDill = new File.fromUri(mainFile.uri.resolve("main.dill"));
|
|
IOSink sink = mainDill.openWrite();
|
|
new BinaryPrinter(sink).writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
print("=> Notice main file has size ${mainDill.lengthSync()}");
|
|
|
|
helperFile.writeAsStringSync(r"""
|
|
int reloadTime() {
|
|
return 1;
|
|
}
|
|
void helperMethod() {
|
|
var hello = "Hello";
|
|
var s = "$hello world!!!";
|
|
print(s);
|
|
}
|
|
int helperMethod2() {
|
|
return 42;
|
|
}
|
|
""");
|
|
compiler.invalidate(helperFile.uri);
|
|
compilerResult = await compiler.compile();
|
|
component = compilerResult.component;
|
|
File partial1Dill =
|
|
new File.fromUri(mainFile.uri.resolve("partial1.dill"));
|
|
sink = partial1Dill.openWrite();
|
|
new BinaryPrinter(sink).writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
print("=> Notice partial file #1 has size ${partial1Dill.lengthSync()}");
|
|
|
|
helperFile.writeAsStringSync(r"""
|
|
int reloadTime() {
|
|
return 2;
|
|
}
|
|
void helperMethod() {
|
|
var hello = "Hello";
|
|
var s = "$hello world!!!!";
|
|
print(s);
|
|
}
|
|
int helperMethod2() {
|
|
return 21;
|
|
}
|
|
int helperMethod3() {
|
|
return 84;
|
|
}
|
|
""");
|
|
compiler.invalidate(helperFile.uri);
|
|
compilerResult = await compiler.compile();
|
|
component = compilerResult.component;
|
|
File partial2Dill =
|
|
new File.fromUri(mainFile.uri.resolve("partial2.dill"));
|
|
sink = partial2Dill.openWrite();
|
|
new BinaryPrinter(sink).writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
print("=> Notice partial file #2 has size ${partial2Dill.lengthSync()}");
|
|
|
|
mainFile.deleteSync();
|
|
helperFile.deleteSync();
|
|
|
|
await launchBreakAndEvaluate(mainDill, helperFile.uri.toString(), 7, [
|
|
// 1st expression
|
|
"s.length",
|
|
"12",
|
|
|
|
// 2nd expression
|
|
"s",
|
|
"Hello world!",
|
|
|
|
// 3rd expression
|
|
"hello",
|
|
"Hello",
|
|
|
|
// 4th expression
|
|
"reloadTime()",
|
|
"0",
|
|
], callback: (RemoteVm remoteVm) async {
|
|
for (int q = 0; q < 10; q++) {
|
|
Map reloadResult = await remoteVm.reload(partial1Dill.uri);
|
|
expect(reloadResult["success"], equals(true));
|
|
|
|
await remoteVm.forceGc();
|
|
|
|
var breakpoint =
|
|
await findScriptAndBreak(remoteVm, helperFile.uri.toString(), 7);
|
|
await remoteVm.resume();
|
|
await waitForScriptToHavePaused(remoteVm);
|
|
await evaluateExpressions([
|
|
// 1st expression
|
|
"s.length",
|
|
"14",
|
|
|
|
// 2nd expression
|
|
"s",
|
|
"Hello world!!!",
|
|
|
|
// 3rd expression
|
|
"hello",
|
|
"Hello",
|
|
|
|
// 4th expression
|
|
"reloadTime()",
|
|
"1",
|
|
|
|
// 5th expression
|
|
"helperMethod2()",
|
|
"42",
|
|
], remoteVm);
|
|
await deletePossibleBreakpoint(remoteVm, breakpoint);
|
|
|
|
reloadResult = await remoteVm.reload(partial2Dill.uri);
|
|
expect(reloadResult["success"], equals(true));
|
|
|
|
await remoteVm.forceGc();
|
|
|
|
breakpoint =
|
|
await findScriptAndBreak(remoteVm, helperFile.uri.toString(), 7);
|
|
await remoteVm.resume();
|
|
await waitForScriptToHavePaused(remoteVm);
|
|
await evaluateExpressions([
|
|
// 1st expression
|
|
"s.length",
|
|
"15",
|
|
|
|
// 2nd expression
|
|
"s",
|
|
"Hello world!!!!",
|
|
|
|
// 3rd expression
|
|
"hello",
|
|
"Hello",
|
|
|
|
// 4th expression
|
|
"reloadTime()",
|
|
"2",
|
|
|
|
// 5th expression
|
|
"helperMethod2()",
|
|
"21",
|
|
|
|
// 6th expression
|
|
"helperMethod3()",
|
|
"84",
|
|
], remoteVm);
|
|
await deletePossibleBreakpoint(remoteVm, breakpoint);
|
|
}
|
|
});
|
|
});
|
|
|
|
test('from dill with package uri', () async {
|
|
// 2 iterations: One where the package_config.json file is deleted, and one where
|
|
// it is not.
|
|
for (int i = 0; i < 2; i++) {
|
|
Directory dir = mytest.createTempSync();
|
|
File mainFile = new File.fromUri(dir.uri.resolve("main.dart"));
|
|
mainFile.writeAsStringSync(r"""
|
|
var hello = "Hello";
|
|
main() {
|
|
var s = "$hello world!";
|
|
print(s);
|
|
}
|
|
int extra() { return 22; }
|
|
""");
|
|
|
|
File packagesFile =
|
|
new File.fromUri(dir.uri.resolve("package_config.json"));
|
|
packagesFile.writeAsStringSync(jsonEncode({
|
|
"configVersion": 2,
|
|
"packages": [
|
|
{
|
|
"name": "foo",
|
|
"rootUri": ".",
|
|
"languageVersion": "2.7",
|
|
},
|
|
],
|
|
}));
|
|
|
|
Uri mainUri = Uri.parse("package:foo/main.dart");
|
|
|
|
CompilerOptions optionsModified = getFreshOptions()
|
|
..packagesFileUri = packagesFile.uri;
|
|
IncrementalCompiler compiler =
|
|
new IncrementalCompiler(optionsModified, [mainUri]);
|
|
|
|
IncrementalCompilerResult compilerResult = await compiler.compile();
|
|
Component component = compilerResult.component;
|
|
File mainDill = new File.fromUri(mainFile.uri.resolve("main.dill"));
|
|
IOSink sink = mainDill.openWrite();
|
|
new BinaryPrinter(sink).writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
|
|
mainFile.deleteSync();
|
|
if (i == 0) {
|
|
packagesFile.deleteSync();
|
|
}
|
|
|
|
await launchBreakAndEvaluate(mainDill, mainUri.toString(), 4, [
|
|
// 1st expression
|
|
"s.length",
|
|
"12",
|
|
|
|
// 2nd expression
|
|
"s",
|
|
"Hello world!",
|
|
|
|
// 3rd expression
|
|
"hello",
|
|
"Hello",
|
|
|
|
// 4th expression
|
|
"extra()",
|
|
"22",
|
|
]);
|
|
|
|
try {
|
|
dir.deleteSync(recursive: true);
|
|
} catch (e) {
|
|
// ignore.
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
Future evaluateExpressions(
|
|
List<String> expressionsAndExpectedResults, RemoteVm remoteVm) async {
|
|
for (int i = 0; i < expressionsAndExpectedResults.length; i += 2) {
|
|
String expression = expressionsAndExpectedResults[i];
|
|
String expectedResult = expressionsAndExpectedResults[i + 1];
|
|
|
|
print("Evaluating $expression (expecting $expectedResult)");
|
|
var result = await remoteVm.evaluateInFrame(expression);
|
|
expect(result is Map, isTrue);
|
|
expect(result["type"], equals("@Instance"));
|
|
expect(result["valueAsString"], equals(expectedResult));
|
|
}
|
|
}
|
|
|
|
Future waitForScriptToHavePaused(RemoteVm remoteVm) async {
|
|
// Wait for the script to have paused.
|
|
while (true) {
|
|
Map isolate = await remoteVm.getIsolate();
|
|
Map pauseEvent = isolate["pauseEvent"];
|
|
if (pauseEvent["kind"] == "PauseBreakpoint") break;
|
|
}
|
|
}
|
|
|
|
Future findScriptAndBreak(
|
|
RemoteVm remoteVm, String scriptUriToBreakIn, int lineToBreakAt) async {
|
|
Map scriptsMap = await remoteVm.getScripts();
|
|
List scripts = scriptsMap["scripts"];
|
|
String? scriptId;
|
|
for (int i = 0; i < scripts.length; i++) {
|
|
Map script = scripts[i];
|
|
String scriptUri = script["uri"];
|
|
if (scriptUri == scriptUriToBreakIn) {
|
|
scriptId = script["id"];
|
|
break;
|
|
}
|
|
}
|
|
expect(scriptId, isNotNull);
|
|
|
|
return await remoteVm.addBreakpoint(scriptId!, lineToBreakAt);
|
|
}
|
|
|
|
Future deletePossibleBreakpoint(
|
|
RemoteVm remoteVm, dynamic possibleBreakpoint) async {
|
|
if (possibleBreakpoint is Map && possibleBreakpoint["id"] is String) {
|
|
return await remoteVm.removeBreakpoint(possibleBreakpoint["id"]);
|
|
}
|
|
}
|
|
|
|
_writeProgramToFile(Component component, File outputFile) async {
|
|
final IOSink sink = outputFile.openWrite();
|
|
final BinaryPrinter printer = new BinaryPrinter(sink);
|
|
printer.writeComponentFile(component);
|
|
await sink.flush();
|
|
await sink.close();
|
|
}
|
|
|
|
class LibraryReferenceCollector extends RecursiveVisitor {
|
|
Set<Library> librariesReferenced = {};
|
|
|
|
void defaultMemberReference(Member node) {
|
|
Library lib = node.enclosingLibrary;
|
|
if (!lib.importUri.isScheme("dart")) {
|
|
librariesReferenced.add(lib);
|
|
}
|
|
return super.defaultMemberReference(node);
|
|
}
|
|
}
|
|
|
|
/// APIs to communicate with a remote VM via the VM's service protocol.
|
|
///
|
|
/// Only supports APIs to resume the program execution (when isolates are paused
|
|
/// at startup) and to trigger hot reloads.
|
|
class RemoteVm {
|
|
/// Port used to connect to the vm service protocol, typically 8181.
|
|
final int port;
|
|
|
|
/// An peer point used to send service protocol messages. The service
|
|
/// protocol uses JSON rpc on top of web-sockets.
|
|
json_rpc.Peer get rpc => _rpc ??= _createPeer();
|
|
json_rpc.Peer? _rpc;
|
|
|
|
/// The main isolate ID of the running VM. Needed to indicate to the VM which
|
|
/// isolate to reload.
|
|
FutureOr<String> get mainId async => _mainId ??= await _computeMainId();
|
|
String? _mainId;
|
|
|
|
RemoteVm([this.port = 8181]);
|
|
|
|
/// Establishes the JSON rpc connection.
|
|
json_rpc.Peer _createPeer() {
|
|
var socket = new IOWebSocketChannel.connect('ws://127.0.0.1:$port/ws');
|
|
var peer = new json_rpc.Peer(socket.cast<String>());
|
|
peer.listen().then((_) {
|
|
print('connection to vm-service closed');
|
|
return disconnect();
|
|
}).catchError((e) {
|
|
print('error connecting to the vm-service');
|
|
return disconnect();
|
|
});
|
|
return peer;
|
|
}
|
|
|
|
/// Retrieves the ID of the main isolate using the service protocol.
|
|
Future<String> _computeMainId() async {
|
|
var completer = new Completer<String>();
|
|
rpc.registerMethod('streamNotify', (response) {
|
|
if (response['streamId'] == 'Isolate') return;
|
|
var event = response['event'];
|
|
if (event['kind'] != 'IsolateStart') return;
|
|
var isolate = event['isolate'];
|
|
completer.complete(isolate['id']);
|
|
});
|
|
await rpc.sendRequest('streamListen', {'streamId': 'Isolate'});
|
|
var vm = await rpc.sendRequest('getVM', {});
|
|
var isolates = vm['isolates'];
|
|
for (var isolate in isolates) {
|
|
if (isolate['name'].contains(r'$main')) {
|
|
return isolate['id'];
|
|
}
|
|
}
|
|
for (var isolate in isolates) {
|
|
return isolate['id'];
|
|
}
|
|
return completer.future;
|
|
}
|
|
|
|
/// Send a request to the VM to reload sources from [entryUri].
|
|
///
|
|
/// This will establish a connection with the VM assuming it is running on the
|
|
/// local machine and listening on [port] for service protocol requests.
|
|
///
|
|
/// The result is the JSON map received from the reload request.
|
|
Future<Map> reload(Uri entryUri) async {
|
|
print("reload($entryUri)");
|
|
var id = await mainId;
|
|
print("got $id, sending reloadSources rpc request");
|
|
var result = await rpc.sendRequest('reloadSources', {
|
|
'isolateId': id,
|
|
'rootLibUri': entryUri.toString(),
|
|
});
|
|
print("got rpc result $result");
|
|
return result;
|
|
}
|
|
|
|
Future resume() async {
|
|
var id = await mainId;
|
|
await rpc.sendRequest('resume', {'isolateId': id});
|
|
}
|
|
|
|
Future<Map> getIsolate() async {
|
|
var id = await mainId;
|
|
return (await rpc.sendRequest('getIsolate', {'isolateId': id})) as Map;
|
|
}
|
|
|
|
Future<Map> getScripts() async {
|
|
var id = await mainId;
|
|
return (await rpc.sendRequest('getScripts', {
|
|
'isolateId': id,
|
|
})) as Map;
|
|
}
|
|
|
|
Future<Map> getSourceReport([String? scriptId]) async {
|
|
var id = await mainId;
|
|
return (await rpc.sendRequest('getSourceReport', {
|
|
'isolateId': id,
|
|
if (scriptId != null) 'scriptId': scriptId,
|
|
'reports': ['Coverage', 'PossibleBreakpoints'],
|
|
'forceCompile': true,
|
|
})) as Map;
|
|
}
|
|
|
|
Future<Map> getObject(String objectId) async {
|
|
var id = await mainId;
|
|
return (await rpc.sendRequest('getObject', {
|
|
'isolateId': id,
|
|
'objectId': objectId,
|
|
})) as Map;
|
|
}
|
|
|
|
Future addBreakpoint(String scriptId, int line) async {
|
|
var id = await mainId;
|
|
return await rpc.sendRequest('addBreakpoint', {
|
|
'isolateId': id,
|
|
'scriptId': scriptId,
|
|
'line': line,
|
|
});
|
|
}
|
|
|
|
Future removeBreakpoint(String breakpointId) async {
|
|
var id = await mainId;
|
|
return await rpc.sendRequest('removeBreakpoint', {
|
|
'isolateId': id,
|
|
'breakpointId': breakpointId,
|
|
});
|
|
}
|
|
|
|
Future evaluateInFrame(String expression) async {
|
|
var id = await mainId;
|
|
var frameIndex = 0;
|
|
return await rpc.sendRequest('evaluateInFrame', {
|
|
'isolateId': id,
|
|
"frameIndex": frameIndex,
|
|
'expression': expression,
|
|
});
|
|
}
|
|
|
|
Future forceGc() async {
|
|
int expectGcAfter = new DateTime.now().millisecondsSinceEpoch;
|
|
while (true) {
|
|
var id = await mainId;
|
|
Map result = (await rpc.sendRequest('getAllocationProfile', {
|
|
'isolateId': id,
|
|
"gc": true,
|
|
})) as Map;
|
|
String? lastGc = result["dateLastServiceGC"];
|
|
if (lastGc != null && int.parse(lastGc) >= expectGcAfter) return;
|
|
}
|
|
}
|
|
|
|
/// Close any connections used to communicate with the VM.
|
|
Future disconnect() async {
|
|
final rpc = this._rpc;
|
|
if (rpc == null) return null;
|
|
this._mainId = null;
|
|
if (!rpc.isClosed) {
|
|
var future = rpc.close();
|
|
_rpc = null;
|
|
return future;
|
|
}
|
|
return null;
|
|
}
|
|
}
|