blob: d3b4eddb20552de6ad4b6326dc984395ff7e9a2b [file] [log] [blame]
// Copyright (c) 2015, 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 'package:stack_trace/stack_trace.dart';
import 'package:stream_channel/stream_channel.dart';
import '../backend/group.dart';
import '../backend/live_test.dart';
import '../backend/live_test_controller.dart';
import '../backend/message.dart';
import '../backend/metadata.dart';
import '../backend/operating_system.dart';
import '../backend/state.dart';
import '../backend/suite.dart';
import '../backend/test.dart';
import '../backend/test_platform.dart';
import '../util/remote_exception.dart';
import '../utils.dart';
import 'spawn_hybrid.dart';
/// A test running remotely, controlled by a stream channel.
class RunnerTest extends Test {
final String name;
final Metadata metadata;
final Trace trace;
/// The channel used to communicate with the test's [IframeListener].
final MultiChannel _channel;
RunnerTest(this.name, this.metadata, this.trace, this._channel);
RunnerTest._(this.name, this.metadata, this.trace, this._channel);
LiveTest load(Suite suite, {Iterable<Group> groups}) {
var controller;
var testChannel;
controller = new LiveTestController(suite, this, () {
controller.setState(const State(Status.running, Result.success));
testChannel = _channel.virtualChannel();
_channel.sink.add({'command': 'run', 'channel': testChannel.id});
testChannel.stream.listen((message) {
switch (message['type']) {
case 'error':
var asyncError = RemoteException.deserialize(message['error']);
var stackTrace = asyncError.stackTrace;
controller.addError(asyncError.error, stackTrace);
break;
case 'state-change':
controller.setState(new State(new Status.parse(message['status']),
new Result.parse(message['result'])));
break;
case 'message':
controller.message(new Message(
new MessageType.parse(message['message-type']),
message['text']));
break;
case 'complete':
controller.completer.complete();
break;
case 'spawn-hybrid-uri':
// When we kill the isolate that the test lives in, that will close
// this virtual channel and cause the spawned isolate to close as
// well.
spawnHybridUri(message['url'], message['message'])
.pipe(testChannel.virtualChannel(message['channel']));
break;
}
}, onDone: () {
// When the test channel closes—presumably becuase the browser
// closed—mark the test as complete no matter what.
if (controller.completer.isCompleted) return;
controller.completer.complete();
});
}, () {
// If the test has finished running, just disconnect the channel.
if (controller.completer.isCompleted) {
testChannel.sink.close();
return;
}
invoke(() async {
// If the test is still running, send it a message telling it to shut
// down ASAP. This causes the [Invoker] to eagerly throw exceptions
// whenever the test touches it.
testChannel.sink.add({'command': 'close'});
await controller.completer.future;
testChannel.sink.close();
});
}, groups: groups);
return controller.liveTest;
}
Test forPlatform(TestPlatform platform, {OperatingSystem os}) {
if (!metadata.testOn.evaluate(platform, os: os)) return null;
return new RunnerTest._(
name, metadata.forPlatform(platform, os: os), trace, _channel);
}
}