197fe0c193
TEST=Fixes failures on product mode. Change-Id: I2cb00bbbcd18a7835ead301946457aa773edbf99 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/188700 Reviewed-by: Tess Strickland <sstrickl@google.com> Commit-Queue: Martin Kustermann <kustermann@google.com>
270 lines
8.0 KiB
Dart
270 lines
8.0 KiB
Dart
// Copyright (c) 2021, 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:io';
|
|
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:path/path.dart' as path;
|
|
import '../../../../../pkg/front_end/test/tool/reload.dart';
|
|
|
|
export '../snapshot_test_helper.dart' show withTempDir;
|
|
|
|
bool get currentVmSupportsReload {
|
|
final executable = Platform.executable;
|
|
return !executable.contains('Product') &&
|
|
!executable.contains('dart_precompiled_runtime');
|
|
}
|
|
|
|
final includeIn = RegExp(r'//\s+@include-in-reload-([0-9]+)([+]?)\s*$');
|
|
|
|
Future<List<String>> generateDills(String tempDir, String testDartFile) async {
|
|
// We have to compile in serial by always using the same source filename in
|
|
// order to ensure all dills will have the same root Uri.
|
|
final versions = generateReloadVersions(testDartFile);
|
|
final dills = <String>[];
|
|
int i = 0;
|
|
for (final version in versions) {
|
|
final testFile = path.join(tempDir, 'test.dart');
|
|
await File(testFile).writeAsString(version);
|
|
final dillFile = path.join(tempDir, 'test.dart.${i++}.dill');
|
|
await compile(testFile, dillFile);
|
|
dills.add(Uri.file(dillFile).toString());
|
|
}
|
|
return dills;
|
|
}
|
|
|
|
/// We generate several versions of a program by looking at annotations such as:
|
|
/// @include-in-reload-<N>
|
|
/// @include-in-reload-<N>+
|
|
/// Lines with such annotations are included in the <N>th program version and
|
|
/// possibly in all following ones (if `+` was used).
|
|
List<String> generateReloadVersions(String fileContent) {
|
|
final lines = fileContent.split('\n');
|
|
|
|
// Scan for all annotations to find out how many we reloads we need to do.
|
|
final reloadAnnotation = Uint32List(lines.length);
|
|
final reloadPlusAnnotation = List<bool>.filled(lines.length, true);
|
|
for (int i = 0; i < lines.length; ++i) {
|
|
final line = lines[i];
|
|
final m = includeIn.firstMatch(line);
|
|
if (m != null) {
|
|
final annotation = int.parse(m.group(1) as String);
|
|
reloadAnnotation[i] = annotation;
|
|
reloadPlusAnnotation[i] = m.group(2) == '+';
|
|
} else {
|
|
// No annotation means include always.
|
|
reloadPlusAnnotation[i] = true;
|
|
}
|
|
}
|
|
final reloadAnnotationSet = reloadAnnotation.toSet();
|
|
final sortedReloadAnnotations = reloadAnnotationSet.toList()..sort();
|
|
for (int i = 1; i < sortedReloadAnnotations.length; ++i) {
|
|
final int from = sortedReloadAnnotations[i - 1];
|
|
final int to = sortedReloadAnnotations[i];
|
|
if ((from + 1) != to) {
|
|
throw 'Should have strictly increasing reloads without gaps';
|
|
}
|
|
}
|
|
|
|
final versions = <String>[];
|
|
for (int i = 0; i < sortedReloadAnnotations.length; ++i) {
|
|
final int reloadIteration = sortedReloadAnnotations[i];
|
|
final sb = StringBuffer();
|
|
for (int j = 0; j < lines.length; ++j) {
|
|
final int annotation = reloadAnnotation[j];
|
|
final bool plus = reloadPlusAnnotation[j];
|
|
if (annotation == reloadIteration ||
|
|
(plus && annotation <= reloadIteration)) {
|
|
final String line = lines[j];
|
|
sb.writeln(line);
|
|
}
|
|
}
|
|
versions.add(sb.toString());
|
|
}
|
|
return versions;
|
|
}
|
|
|
|
Future compile(String from, String to) async {
|
|
final executable = Platform.executable;
|
|
final command = [
|
|
'--packages=.packages',
|
|
'--snapshot-kind=kernel',
|
|
'--snapshot=$to',
|
|
from,
|
|
];
|
|
|
|
print('Launching $executable ${command.join(' ')}');
|
|
final process = await Process.start(executable, command);
|
|
final f1 = process.stdout
|
|
.transform(utf8.decoder)
|
|
.transform(const LineSplitter())
|
|
.listen((line) => print('stdout: $line'))
|
|
.asFuture();
|
|
final f2 = process.stderr
|
|
.transform(utf8.decoder)
|
|
.transform(const LineSplitter())
|
|
.listen((line) => print('stderr: $line'))
|
|
.asFuture();
|
|
final exitCode = await process.exitCode;
|
|
if (exitCode != 0) {
|
|
await f1;
|
|
await f2;
|
|
throw 'Compilation failed';
|
|
}
|
|
}
|
|
|
|
Future<Reloader> launchOn(String file, {bool verbose: false}) async {
|
|
final command = [
|
|
if (verbose) '--trace-reload',
|
|
if (verbose) '--trace-reload-verbose',
|
|
'--enable-vm-service:0',
|
|
'--disable-dart-dev',
|
|
'--disable-service-auth-codes',
|
|
'--enable-isolate-groups',
|
|
'--experimental-enable-isolate-groups-jit',
|
|
file
|
|
];
|
|
final env = Platform.environment;
|
|
final executable = Platform.executable;
|
|
|
|
print('Launching $executable ${command.join(' ')}');
|
|
final process = await Process.start(executable, command, environment: env);
|
|
final reloader = Reloader(process);
|
|
await reloader._waitUntilService();
|
|
return reloader;
|
|
}
|
|
|
|
class Reloader {
|
|
final Process _process;
|
|
|
|
final List<String> _stdout = [];
|
|
final Set<_Filter> _stdoutFilters = {};
|
|
final List<String> _stderr = [];
|
|
final Set<_Filter> _stderrFilters = {};
|
|
|
|
RemoteVm _remoteVm;
|
|
int _reloadVersion = 0;
|
|
|
|
Reloader(this._process) {
|
|
_process.stdout
|
|
.transform(utf8.decoder)
|
|
.transform(const LineSplitter())
|
|
.listen((line) {
|
|
print('stdout: $line');
|
|
_addStdout(line);
|
|
});
|
|
_process.stderr
|
|
.transform(utf8.decoder)
|
|
.transform(const LineSplitter())
|
|
.listen((line) {
|
|
print('stderr: $line');
|
|
_addStderr(line);
|
|
});
|
|
}
|
|
|
|
Future _waitUntilService() async {
|
|
final needle = 'Observatory listening on ';
|
|
final line = await waitUntilStdoutContains(needle);
|
|
final Uri uri = Uri.parse(line.substring(needle.length));
|
|
assert(_remoteVm == null);
|
|
_remoteVm = RemoteVm(uri.port);
|
|
}
|
|
|
|
Future writeToStdin(String line) async {
|
|
_process.stdin.writeln(line);
|
|
await _process.stdin.flush();
|
|
}
|
|
|
|
Future<Map> reload(String file) async {
|
|
print('Reload $file (version: ${_reloadVersion++})');
|
|
final Map reloadResult = await _remoteVm.reload(Uri.parse(file));
|
|
print(const JsonEncoder.withIndent(' ').convert(reloadResult));
|
|
return reloadResult;
|
|
}
|
|
|
|
Future<int> close() async {
|
|
await _remoteVm.disconnect();
|
|
final exitCode = await _process.exitCode;
|
|
print('ExitCode = $exitCode');
|
|
return exitCode;
|
|
}
|
|
|
|
Future<String> waitUntilStdoutContains(String needle) {
|
|
return _waitUntilContains(_stdout, _stdoutFilters, needle, 1);
|
|
}
|
|
|
|
Future<String> waitUntilStdoutContainsN(String needle, int N) {
|
|
return _waitUntilContains(_stdout, _stdoutFilters, needle, N);
|
|
}
|
|
|
|
Future<String> waitUntilStderrContains(String needle) {
|
|
return _waitUntilContains(_stderr, _stderrFilters, needle, 1);
|
|
}
|
|
|
|
Future<String> waitUntilstderrContainsN(String needle, int N) {
|
|
return _waitUntilContains(_stderr, _stderrFilters, needle, N);
|
|
}
|
|
|
|
Future<String> _waitUntilContains(
|
|
List<String> lines, Set<_Filter> filterSet, String needle, int N) {
|
|
int count = 0;
|
|
|
|
bool handleLine(String line) {
|
|
// Sometimes the prints of the isolates get interleaved, so we allow
|
|
// multiple matches on one line.
|
|
int index = line.indexOf(needle);
|
|
while (index >= 0) {
|
|
if (++count == N) return true;
|
|
index = line.indexOf(needle, index + 1);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
for (final line in lines) {
|
|
if (handleLine(line)) return Future.value(line);
|
|
}
|
|
final c = Completer<String>();
|
|
filterSet.add(_Filter(needle, (line) {
|
|
if (handleLine(line)) {
|
|
c.complete(line);
|
|
return true;
|
|
}
|
|
return false;
|
|
}));
|
|
return c.future;
|
|
}
|
|
|
|
void _addStdout(String line) {
|
|
_stdout.add(line);
|
|
_handleNewLine(_stdoutFilters, line);
|
|
}
|
|
|
|
void _addStderr(String line) {
|
|
_stderr.add(line);
|
|
_handleNewLine(_stderrFilters, line);
|
|
}
|
|
|
|
void _handleNewLine(Set<_Filter> filters, String line) {
|
|
final toRemove = <_Filter>[];
|
|
for (final filter in filters) {
|
|
if (line.contains(filter.needle)) {
|
|
if (filter.callback(line)) {
|
|
toRemove.add(filter);
|
|
}
|
|
}
|
|
}
|
|
for (final filter in toRemove) {
|
|
filters.remove(filter);
|
|
}
|
|
}
|
|
}
|
|
|
|
class _Filter {
|
|
final String needle;
|
|
final bool Function(String line) callback;
|
|
_Filter(this.needle, this.callback);
|
|
}
|