195 lines
6.8 KiB
Dart
195 lines
6.8 KiB
Dart
// Copyright 2015 The Chromium Authors. 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 p;
|
|
import 'package:sky_tools/src/test/json_socket.dart';
|
|
import 'package:sky_tools/src/test/remote_test.dart';
|
|
import 'package:stack_trace/stack_trace.dart';
|
|
import 'package:test/src/backend/group.dart';
|
|
import 'package:test/src/backend/metadata.dart';
|
|
import 'package:test/src/backend/test_platform.dart';
|
|
import 'package:test/src/runner/configuration.dart';
|
|
import 'package:test/src/runner/hack_load_vm_file_hook.dart' as hack;
|
|
import 'package:test/src/runner/load_exception.dart';
|
|
import 'package:test/src/runner/runner_suite.dart';
|
|
import 'package:test/src/runner/vm/environment.dart';
|
|
import 'package:test/src/util/io.dart';
|
|
import 'package:test/src/util/remote_exception.dart';
|
|
|
|
void installHook() {
|
|
hack.loadVMFileHook = _loadVMFile;
|
|
}
|
|
|
|
final String _kSkyShell = Platform.environment['SKY_SHELL'];
|
|
const String _kHost = '127.0.0.1';
|
|
const String _kPath = '/runner';
|
|
|
|
String shellPath;
|
|
|
|
// Right now a bunch of our tests crash or assert after the tests have finished running.
|
|
// Mostly this is just because the test puts the framework in an inconsistent state with
|
|
// a scheduled microtask that verifies that state. Eventually we should fix all these
|
|
// problems but for now we'll just paper over them.
|
|
const bool kExpectAllTestsToCloseCleanly = false;
|
|
|
|
class _ServerInfo {
|
|
final String url;
|
|
final Future<WebSocket> socket;
|
|
final HttpServer server;
|
|
|
|
_ServerInfo(this.server, this.url, this.socket);
|
|
}
|
|
|
|
Future<_ServerInfo> _createServer() async {
|
|
HttpServer server = await HttpServer.bind(_kHost, 0);
|
|
Completer<WebSocket> socket = new Completer<WebSocket>();
|
|
server.listen((HttpRequest request) {
|
|
if (request.uri.path == _kPath)
|
|
socket.complete(WebSocketTransformer.upgrade(request));
|
|
});
|
|
return new _ServerInfo(server, 'ws://$_kHost:${server.port}$_kPath', socket.future);
|
|
}
|
|
|
|
Future<Process> _startProcess(String path, { String packageRoot }) {
|
|
assert(shellPath != null || _kSkyShell != null); // Please provide the path to the shell in the SKY_SHELL environment variable.
|
|
return Process.start(shellPath ?? _kSkyShell, [
|
|
'--enable-checked-mode',
|
|
'--non-interactive',
|
|
'--package-root=$packageRoot',
|
|
path,
|
|
]);
|
|
}
|
|
|
|
Future<RunnerSuite> _loadVMFile(String path,
|
|
Metadata metadata,
|
|
Configuration config) async {
|
|
String encodedMetadata = Uri.encodeComponent(JSON.encode(
|
|
metadata.serialize()));
|
|
_ServerInfo info = await _createServer();
|
|
Directory tempDir = await Directory.systemTemp.createTemp(
|
|
'dart_test_listener');
|
|
File listenerFile = new File('${tempDir.path}/listener.dart');
|
|
await listenerFile.create();
|
|
await listenerFile.writeAsString('''
|
|
import 'dart:convert';
|
|
|
|
import 'package:test/src/backend/metadata.dart';
|
|
import 'package:sky_tools/src/test/remote_listener.dart';
|
|
|
|
import '${p.toUri(p.absolute(path))}' as test;
|
|
|
|
void main() {
|
|
String server = Uri.decodeComponent('${Uri.encodeComponent(info.url)}');
|
|
Metadata metadata = new Metadata.deserialize(
|
|
JSON.decode(Uri.decodeComponent('$encodedMetadata')));
|
|
RemoteListener.start(server, metadata, () => test.main);
|
|
}
|
|
''');
|
|
|
|
Completer<Iterable<RemoteTest>> completer = new Completer<Iterable<RemoteTest>>();
|
|
Completer<String> deathCompleter = new Completer();
|
|
|
|
Process process = await _startProcess(
|
|
listenerFile.path,
|
|
packageRoot: p.absolute(config.packageRoot)
|
|
);
|
|
|
|
Future cleanupTempDirectory() async {
|
|
if (tempDir == null)
|
|
return;
|
|
Directory dirToDelete = tempDir;
|
|
tempDir = null;
|
|
await dirToDelete.delete(recursive: true);
|
|
}
|
|
|
|
process.exitCode.then((int exitCode) async {
|
|
try {
|
|
info.server.close(force: true);
|
|
await cleanupTempDirectory();
|
|
String output = '';
|
|
if (exitCode < 0) {
|
|
// Abnormal termination (high bit of signed 8-bit exitCode is set)
|
|
switch (exitCode) {
|
|
case -0x0f: // ProcessSignal.SIGTERM
|
|
break; // we probably killed it ourselves
|
|
case -0x0b: // ProcessSignal.SIGSEGV
|
|
output += 'Segmentation fault in subprocess for: $path\n';
|
|
break;
|
|
case -0x06: // ProcessSignal.SIGABRT
|
|
output += 'Aborted while running: $path\n';
|
|
break;
|
|
default:
|
|
output += 'Unexpected exit code $exitCode from subprocess for: $path\n';
|
|
}
|
|
}
|
|
String stdout = await process.stdout.transform(UTF8.decoder).join('\n');
|
|
String stderr = await process.stderr.transform(UTF8.decoder).join('\n');
|
|
if (stdout != '')
|
|
output += '\nstdout:\n$stdout';
|
|
if (stderr != '')
|
|
output += '\nstderr:\n$stderr';
|
|
if (!completer.isCompleted) {
|
|
if (output == '')
|
|
output = 'No output.';
|
|
completer.completeError(
|
|
new LoadException(path, output),
|
|
new Trace.current()
|
|
);
|
|
} else {
|
|
if (kExpectAllTestsToCloseCleanly && output != '')
|
|
print('Unexpected failure after test claimed to pass:\n$output');
|
|
}
|
|
deathCompleter.complete(output);
|
|
} catch (e) {
|
|
// Throwing inside this block causes all kinds of hard-to-debug issues
|
|
// like stack overflows and hangs. So catch everything just in case.
|
|
print("exception while handling subprocess termination: $e");
|
|
}
|
|
});
|
|
|
|
JSONSocket socket = new JSONSocket(await info.socket, deathCompleter.future);
|
|
|
|
await cleanupTempDirectory();
|
|
|
|
StreamSubscription subscription;
|
|
subscription = socket.stream.listen((response) {
|
|
if (response["type"] == "print") {
|
|
print(response["line"]);
|
|
} else if (response["type"] == "loadException") {
|
|
process.kill(ProcessSignal.SIGTERM);
|
|
completer.completeError(
|
|
new LoadException(path, response["message"]),
|
|
new Trace.current());
|
|
} else if (response["type"] == "error") {
|
|
process.kill(ProcessSignal.SIGTERM);
|
|
AsyncError asyncError = RemoteException.deserialize(response["error"]);
|
|
completer.completeError(
|
|
new LoadException(path, asyncError.error),
|
|
asyncError.stackTrace);
|
|
} else {
|
|
assert(response["type"] == "success");
|
|
subscription.cancel();
|
|
completer.complete(response["tests"].map((test) {
|
|
var testMetadata = new Metadata.deserialize(test['metadata']);
|
|
return new RemoteTest(test['name'], testMetadata, socket, test['index']);
|
|
}));
|
|
}
|
|
});
|
|
|
|
Iterable<RemoteTest> entries = await completer.future;
|
|
|
|
return new RunnerSuite(
|
|
const VMEnvironment(),
|
|
new Group.root(entries, metadata: metadata),
|
|
path: path,
|
|
platform: TestPlatform.vm,
|
|
os: currentOS,
|
|
onClose: () { process.kill(ProcessSignal.SIGTERM); }
|
|
);
|
|
}
|