Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3eddc2a
use frontend server for VM test platform
jonahwilliams Nov 30, 2020
5f1c76f
Merge branch 'master' of github.com:dart-lang/test into frontend_serv…
jonahwilliams Nov 30, 2020
778dbbb
opt out of null safety
jonahwilliams Nov 30, 2020
c0879ba
switch to frontend_server_client
jonahwilliams Dec 3, 2020
6c6f959
use rootPackageLanguageVersionComment
jonahwilliams Dec 4, 2020
8bac872
fix year
jonahwilliams Dec 7, 2020
313f557
merge with master
jonahwilliams Feb 26, 2021
f932ad6
tweak tests and add comment
jonahwilliams Feb 26, 2021
c98910b
require latest frontend_server_client
jakemac53 Mar 18, 2021
000da33
Merge branch 'master' into frontend_server_compilation
jakemac53 Mar 18, 2021
6353265
fix up some null safety issues
jakemac53 Mar 18, 2021
3fb3e7a
produce a LoadException if the app fails to compile
jakemac53 Mar 18, 2021
0a5831c
remove unused import
jakemac53 Mar 18, 2021
4998cc9
add errorCount and check it
jakemac53 Mar 18, 2021
b323cab
pass the current package config to the frotnend server
jakemac53 Mar 18, 2021
bf1aa14
fix tests and expectations to match the new output
jakemac53 Mar 19, 2021
ad98660
delete output dill dir synchronously
jakemac53 Mar 19, 2021
e6ea03e
dont print incremental dependencies
jakemac53 Mar 19, 2021
164bf1b
add --use-data-isolate-strategy flag for use primarily in bazel
jakemac53 Mar 22, 2021
21a1b25
add some basic data isolate strategy tests
jakemac53 Mar 22, 2021
2a78037
fix up help output and expectations
jakemac53 Mar 22, 2021
e85f228
remove unused imports
jakemac53 Mar 22, 2021
8d96df0
code review updates
jakemac53 Mar 22, 2021
dae0aa2
update to use package:pool, move fields above constructor
jakemac53 Mar 22, 2021
2e2fac3
improve error handling of a sigkill during suite loading
jakemac53 Mar 22, 2021
08d1859
Merge branch 'master' into frontend_server_compilation
jakemac53 Mar 22, 2021
aa23f0c
Update pkgs/test_core/lib/src/runner/configuration.dart
jakemac53 Mar 22, 2021
98cc83d
Update pkgs/test/test/runner/data_isolate_strategy_test.dart
jakemac53 Mar 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 12 additions & 19 deletions pkgs/test_core/lib/src/runner/vm/platform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'package:stream_channel/stream_channel.dart';
import 'package:test_api/backend.dart'; // ignore: deprecated_member_use
import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
import 'package:test_core/src/runner/vm/test_compiler.dart';
import 'package:vm_service/vm_service.dart' hide Isolate;
import 'package:vm_service/vm_service_io.dart';

Expand All @@ -24,14 +25,14 @@ import '../../runner/platform.dart';
import '../../runner/plugin/platform_helpers.dart';
import '../../runner/runner_suite.dart';
import '../../runner/suite.dart';
import '../../util/dart.dart' as dart;
import '../package_version.dart';
import 'environment.dart';

/// A platform that loads tests in isolates spawned within this Dart process.
class VMPlatform extends PlatformPlugin {
/// The test runner configuration.
final _config = Configuration.current;
final _compiler = TestCompiler(
p.join(Directory.current.path, '.dart_tool', 'pkg_test_kernel.bin'));

VMPlatform();

Expand Down Expand Up @@ -108,6 +109,11 @@ class VMPlatform extends PlatformPlugin {
return await controller.suite;
}

@override
Future close() async {
await _compiler.dispose();
}

/// Spawns an isolate and passes it [message].
///
/// This isolate connects an [IsolateChannel] to [message] and sends the
Expand All @@ -120,27 +126,14 @@ class VMPlatform extends PlatformPlugin {
} else if (_config.pubServeUrl != null) {
return _spawnPubServeIsolate(path, message, _config.pubServeUrl!);
} else {
return _spawnDataIsolate(path, message, suiteMetadata);
final compiledDill =
await _compiler.compile(File(path).absolute.uri, suiteMetadata);
return await Isolate.spawnUri(p.toUri(compiledDill!), [], message,
checked: true);
}
}
}

Future<Isolate> _spawnDataIsolate(
String path, SendPort message, Metadata suiteMetadata) async {
return await dart.runInIsolate('''
${suiteMetadata.languageVersionComment ?? await rootPackageLanguageVersionComment}
import "dart:isolate";

import "package:test_core/src/bootstrap/vm.dart";

import "${p.toUri(p.absolute(path))}" as test;

void main(_, SendPort sendPort) {
internalBootstrapVmTest(() => test.main, sendPort);
}
''', message);
}

Future<Isolate> _spawnPrecompiledIsolate(
String testPath, SendPort message, String precompiledPath) async {
testPath = p.absolute(p.join(precompiledPath, testPath) + '.vm_test.dart');
Expand Down
273 changes: 273 additions & 0 deletions pkgs/test_core/lib/src/runner/vm/test_compiler.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
// 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 'dart:math' as math;
import 'dart:typed_data';

// ignore: import_of_legacy_library_into_null_safe
import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as path;
import 'package:test_api/backend.dart'; // ignore: deprecated_member_use

/// A request to the [TestCompiler] for recompilation.
class _CompilationRequest {
_CompilationRequest(this.mainUri, this.result, this.metadata);

Uri mainUri;
Metadata metadata;
Completer<String> result;
}

class TestCompiler {
TestCompiler(this._dillCachePath)
: _outputDillDirectory =
Directory.systemTemp.createTempSync('dart_test.') {
_outputDill = File(path.join(_outputDillDirectory.path, 'output.dill'));
_compilerController.stream.listen(_onCompilationRequest, onDone: () {
_outputDillDirectory.deleteSync(recursive: true);
});
}

final String _dillCachePath;
final Directory _outputDillDirectory;
final _compilerController = StreamController<_CompilationRequest>();
final _compilationQueue = <_CompilationRequest>[];
final _stdoutHandler = _StdoutHandler();

File? _outputDill;
Process? _compiler;
PackageConfig? _packageConfig;

Future<String?> compile(Uri mainDart, Metadata metadata) {
final completer = Completer<String>();
if (_compilerController.isClosed) {
return Future.value(null);
}
_compilerController.add(_CompilationRequest(mainDart, completer, metadata));
return completer.future;
}

Future<void> _shutdown() async {
if (_compiler != null) {
_compiler!.kill();
_compiler = null;
}
}

Future<void> dispose() async {
await _compilerController.close();
await _shutdown();
}

Future<String> _languageVersionComment(Uri testUri) async {
var localPackageConfig = _packageConfig ??= await loadPackageConfig(File(
path.join(
Directory.current.path, '.dart_tool', 'package_config.json')));
var package = localPackageConfig.packageOf(testUri) as Package?;
if (package == null) {
return '';
}
return '// @dart=${package.languageVersion.major}.${package.languageVersion.minor}';
}

Future<String> _generateEntrypoint(
Uri testUri, Metadata suiteMetadata) async {
return '''
${suiteMetadata.languageVersionComment ?? await _languageVersionComment(testUri)}
import "dart:isolate";

import "package:test_core/src/bootstrap/vm.dart";

import "$testUri" as test;

void main(_, SendPort sendPort) {
internalBootstrapVmTest(() => test.main, sendPort);
}
''';
}

// Handle a compilation request.
Future<void> _onCompilationRequest(_CompilationRequest request) async {
final isEmpty = _compilationQueue.isEmpty;
_compilationQueue.add(request);
if (!isEmpty) {
return;
}
while (_compilationQueue.isNotEmpty) {
final request = _compilationQueue.first;
var firstCompile = false;
_CompilerOutput? compilerOutput;
final contents =
await _generateEntrypoint(request.mainUri, request.metadata);
final tempFile = File(path.join(_outputDillDirectory.path, 'test.dart'))
..writeAsStringSync(contents);

if (_compiler == null) {
compilerOutput = await _createCompiler(tempFile.uri);
firstCompile = true;
} else {
compilerOutput = await _recompile(
tempFile.uri,
);
}
final outputPath = compilerOutput?.outputFilename;
if (outputPath == null || compilerOutput!.errorCount > 0) {
request.result.complete(null);
await _shutdown();
} else {
final outputFile = File(outputPath);
final kernelReadyToRun = await outputFile.copy('${tempFile.path}.dill');
final testCache = File(_dillCachePath);
if (firstCompile ||
!testCache.existsSync() ||
(testCache.lengthSync() < outputFile.lengthSync())) {
// Keep the cache file up-to-date and include as many packages as possible,
// using the kernel size as an approximation.
if (!testCache.parent.existsSync()) {
testCache.parent.createSync(recursive: true);
}
await outputFile.copy(_dillCachePath);
}
request.result.complete(kernelReadyToRun.absolute.path);
_accept();
_reset();
}
_compilationQueue.removeAt(0);
}
}

String _generateInputKey(math.Random random) {
final bytes = Uint8List(16);
for (var i = 0; i < 16; i++) {
bytes[i] = random.nextInt(25) + 65;
}
return String.fromCharCodes(bytes);
}

Future<_CompilerOutput?> _recompile(Uri mainUri) {
_stdoutHandler.reset();
final inputKey = _generateInputKey(math.Random());
_compiler!.stdin.writeln('recompile $mainUri $inputKey');
_compiler!.stdin.writeln('$mainUri');
_compiler!.stdin.writeln('$inputKey');
return _stdoutHandler.compilerOutput.future;
}

void _accept() {
_compiler!.stdin.writeln('accept');
}

void _reset() {
_compiler!.stdin.writeln('reset');
_stdoutHandler.reset(expectSources: false);
}

Future<_CompilerOutput?> _createCompiler(Uri testUri) async {
final frontendServer = path.normalize(path.join(Platform.resolvedExecutable,
'..', 'snapshots', 'frontend_server.dart.snapshot'));
final sdkRoot = Directory(
path.relative(path.join(Platform.resolvedExecutable, '..', '..')))
.uri;
final platformDill = 'lib/_internal/vm_platform_strong.dill';
final process = await Process.start(Platform.resolvedExecutable, <String>[
'--disable-dart-dev',
frontendServer,
'--incremental',
'--sdk-root=$sdkRoot',
'--no-print-incremental-dependencies',
'--target=vm',
'--output-dill=${_outputDill!.path}',
'--initialize-from-dill=$_dillCachePath',
'--platform=$platformDill',
'--packages=${path.join(Directory.current.path, '.packages')}'
]);
process.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(_stdoutHandler.handler);
process.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(print);
process.stdin.writeln('compile $testUri');
_compiler = process;
return _stdoutHandler.compilerOutput.future;
}
}

enum _StdoutState { CollectDiagnostic, CollectDependencies }

class _CompilerOutput {
const _CompilerOutput(this.outputFilename, this.errorCount, this.sources);

final String? outputFilename;
final int errorCount;
final List<Uri> sources;
}

class _StdoutHandler {
String? boundaryKey;
_StdoutState state = _StdoutState.CollectDiagnostic;
Completer<_CompilerOutput> compilerOutput = Completer<_CompilerOutput>();
final sources = <Uri>[];

bool _suppressCompilerMessages = false;
bool _expectSources = true;

void handler(String message) {
const kResultPrefix = 'result ';
if (boundaryKey == null && message.startsWith(kResultPrefix)) {
boundaryKey = message.substring(kResultPrefix.length);
return;
}
if (message.startsWith(boundaryKey!)) {
if (_expectSources) {
if (state == _StdoutState.CollectDiagnostic) {
state = _StdoutState.CollectDependencies;
return;
}
}
if (message.length <= boundaryKey!.length) {
compilerOutput.complete(null);
return;
}
final spaceDelimiter = message.lastIndexOf(' ');
compilerOutput.complete(_CompilerOutput(
message.substring(boundaryKey!.length + 1, spaceDelimiter),
int.parse(message.substring(spaceDelimiter + 1).trim()),
sources));
return;
}
if (state == _StdoutState.CollectDiagnostic) {
if (!_suppressCompilerMessages) {
print(message);
}
} else {
assert(state == _StdoutState.CollectDependencies);
switch (message[0]) {
case '+':
sources.add(Uri.parse(message.substring(1)));
break;
case '-':
sources.remove(Uri.parse(message.substring(1)));
break;
default:
}
}
}

// This is needed to get ready to process next compilation result output,
// with its own boundary key and new completer.
void reset(
{bool suppressCompilerMessages = false, bool expectSources = true}) {
boundaryKey = null;
compilerOutput = Completer<_CompilerOutput>();
_suppressCompilerMessages = suppressCompilerMessages;
_expectSources = expectSources;
state = _StdoutState.CollectDiagnostic;
}
}