Commit 297b90e2 authored by Hixie's avatar Hixie

Try to fix the test framework better than before

The previous attempt to port the 'test' framework to the new framework
wasn't super-successful. This does a better job, hopefully.
parent 4c99319f
...@@ -11,7 +11,6 @@ import 'package:sky_tools/src/test/json_socket.dart'; ...@@ -11,7 +11,6 @@ import 'package:sky_tools/src/test/json_socket.dart';
import 'package:sky_tools/src/test/remote_test.dart'; import 'package:sky_tools/src/test/remote_test.dart';
import 'package:stack_trace/stack_trace.dart'; import 'package:stack_trace/stack_trace.dart';
import 'package:test/src/backend/group.dart'; import 'package:test/src/backend/group.dart';
import 'package:test/src/backend/group_entry.dart';
import 'package:test/src/backend/metadata.dart'; import 'package:test/src/backend/metadata.dart';
import 'package:test/src/backend/test_platform.dart'; import 'package:test/src/backend/test_platform.dart';
import 'package:test/src/runner/configuration.dart'; import 'package:test/src/runner/configuration.dart';
...@@ -30,6 +29,12 @@ final String _kSkyShell = Platform.environment['SKY_SHELL']; ...@@ -30,6 +29,12 @@ final String _kSkyShell = Platform.environment['SKY_SHELL'];
const String _kHost = '127.0.0.1'; const String _kHost = '127.0.0.1';
const String _kPath = '/runner'; const String _kPath = '/runner';
// 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 { class _ServerInfo {
final String url; final String url;
final Future<WebSocket> socket; final Future<WebSocket> socket;
...@@ -84,10 +89,12 @@ void main() { ...@@ -84,10 +89,12 @@ void main() {
} }
'''); ''');
Completer<Iterable<GroupEntry>> completer = new Completer<Iterable<GroupEntry>>(); Completer<Iterable<RemoteTest>> completer = new Completer<Iterable<RemoteTest>>();
Process process = await _startProcess(listenerFile.path, Process process = await _startProcess(
packageRoot: p.absolute(config.packageRoot)); listenerFile.path,
packageRoot: p.absolute(config.packageRoot)
);
Future cleanupTempDirectory() async { Future cleanupTempDirectory() async {
if (tempDir == null) if (tempDir == null)
...@@ -98,12 +105,43 @@ void main() { ...@@ -98,12 +105,43 @@ void main() {
} }
process.exitCode.then((int exitCode) async { process.exitCode.then((int exitCode) async {
info.server.close(force: true); try {
await cleanupTempDirectory(); info.server.close(force: true);
if (!completer.isCompleted) { await cleanupTempDirectory();
String error = await process.stderr.transform(UTF8.decoder).first; String output = '';
completer.completeError( if (exitCode < 0) {
new LoadException(path, error), new Trace.current()); // 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;
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');
}
} 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");
} }
}); });
...@@ -116,12 +154,12 @@ void main() { ...@@ -116,12 +154,12 @@ void main() {
if (response["type"] == "print") { if (response["type"] == "print") {
print(response["line"]); print(response["line"]);
} else if (response["type"] == "loadException") { } else if (response["type"] == "loadException") {
process.kill(); process.kill(ProcessSignal.SIGTERM);
completer.completeError( completer.completeError(
new LoadException(path, response["message"]), new LoadException(path, response["message"]),
new Trace.current()); new Trace.current());
} else if (response["type"] == "error") { } else if (response["type"] == "error") {
process.kill(); process.kill(ProcessSignal.SIGTERM);
AsyncError asyncError = RemoteException.deserialize(response["error"]); AsyncError asyncError = RemoteException.deserialize(response["error"]);
completer.completeError( completer.completeError(
new LoadException(path, asyncError.error), new LoadException(path, asyncError.error),
...@@ -136,7 +174,7 @@ void main() { ...@@ -136,7 +174,7 @@ void main() {
} }
}); });
Iterable<GroupEntry> entries = await completer.future; Iterable<RemoteTest> entries = await completer.future;
return new RunnerSuite( return new RunnerSuite(
const VMEnvironment(), const VMEnvironment(),
...@@ -144,6 +182,6 @@ void main() { ...@@ -144,6 +182,6 @@ void main() {
path: path, path: path,
platform: TestPlatform.vm, platform: TestPlatform.vm,
os: currentOS, os: currentOS,
onClose: process.kill onClose: () { process.kill(ProcessSignal.SIGTERM); }
); );
} }
...@@ -28,9 +28,11 @@ final OperatingSystem currentOS = (() { ...@@ -28,9 +28,11 @@ final OperatingSystem currentOS = (() {
typedef AsyncFunction(); typedef AsyncFunction();
class RemoteListener { class RemoteListener {
RemoteListener._(this._suite, this._socket);
final Suite _suite; final Suite _suite;
final WebSocket _socket; final WebSocket _socket;
LiveTest _liveTest; final Set<LiveTest> _liveTests = new Set<LiveTest>();
static Future start(String server, Metadata metadata, Function getMain()) async { static Future start(String server, Metadata metadata, Function getMain()) async {
WebSocket socket = await WebSocket.connect(server); WebSocket socket = await WebSocket.connect(server);
...@@ -93,8 +95,6 @@ class RemoteListener { ...@@ -93,8 +95,6 @@ class RemoteListener {
socket.add(JSON.encode({"type": "loadException", "message": message})); socket.add(JSON.encode({"type": "loadException", "message": message}));
} }
RemoteListener._(this._suite, this._socket);
void _send(data) { void _send(data) {
_socket.add(JSON.encode(data)); _socket.add(JSON.encode(data));
} }
...@@ -119,13 +119,13 @@ class RemoteListener { ...@@ -119,13 +119,13 @@ class RemoteListener {
void _handleCommand(String data) { void _handleCommand(String data) {
var message = JSON.decode(data); var message = JSON.decode(data);
if (message['command'] == 'run') { if (message['command'] == 'run') {
assert(_liveTest == null);
// TODO(ianh): entries[] might return a Group instead of a Test. We don't // TODO(ianh): entries[] might return a Group instead of a Test. We don't
// currently support nested groups. // currently support nested groups.
Test test = _suite.group.entries[message['index']]; Test test = _suite.group.entries[message['index']];
_liveTest = test.load(_suite); LiveTest liveTest = test.load(_suite);
_liveTests.add(liveTest);
_liveTest.onStateChange.listen((state) { liveTest.onStateChange.listen((state) {
_send({ _send({
"type": "state-change", "type": "state-change",
"status": state.status.name, "status": state.status.name,
...@@ -133,25 +133,30 @@ class RemoteListener { ...@@ -133,25 +133,30 @@ class RemoteListener {
}); });
}); });
_liveTest.onError.listen((asyncError) { liveTest.onError.listen((asyncError) {
_send({ _send({
"type": "error", "type": "error",
"error": RemoteException.serialize( "error": RemoteException.serialize(
asyncError.error, asyncError.stackTrace) asyncError.error,
asyncError.stackTrace
)
}); });
}); });
_liveTest.onPrint.listen((line) { liveTest.onPrint.listen((line) {
_send({"type": "print", "line": line}); _send({"type": "print", "line": line});
}); });
_liveTest.run().then((_) { liveTest.run().then((_) {
_send({"type": "complete"}); _send({"type": "complete"});
_liveTest = null; _liveTests.remove(liveTest);
}); });
} else if (message['command'] == 'close') { } else if (message['command'] == 'close') {
_liveTest.close(); if (_liveTests.isNotEmpty)
_liveTest = null; print('closing with ${_liveTests.length} live tests');
for (LiveTest liveTest in _liveTests)
liveTest.close();
_liveTests.clear();
} else { } else {
print('remote_listener.dart: ignoring command "${message["command"]}" from test harness'); print('remote_listener.dart: ignoring command "${message["command"]}" from test harness');
} }
......
...@@ -17,14 +17,13 @@ import 'package:test/src/util/remote_exception.dart'; ...@@ -17,14 +17,13 @@ import 'package:test/src/util/remote_exception.dart';
import 'package:sky_tools/src/test/json_socket.dart'; import 'package:sky_tools/src/test/json_socket.dart';
class RemoteTest extends Test { class RemoteTest extends Test {
RemoteTest(this.name, this.metadata, this._socket, this._index);
final String name; final String name;
final Metadata metadata; final Metadata metadata;
final JSONSocket _socket; final JSONSocket _socket;
final int _index; final int _index;
RemoteTest(this.name, this.metadata, this._socket, this._index);
LiveTest load(Suite suite) { LiveTest load(Suite suite) {
LiveTestController controller; LiveTestController controller;
StreamSubscription subscription; StreamSubscription subscription;
...@@ -71,7 +70,13 @@ class RemoteTest extends Test { ...@@ -71,7 +70,13 @@ class RemoteTest extends Test {
// TODO(ianh): Implement this if we need it. // TODO(ianh): Implement this if we need it.
Test forPlatform(TestPlatform platform, {OperatingSystem os}) { Test forPlatform(TestPlatform platform, {OperatingSystem os}) {
assert(false); if (!metadata.testOn.evaluate(platform, os: os))
return this; return null;
} return new RemoteTest(
name,
metadata.forPlatform(platform, os: os),
_socket,
_index
);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment