Index: lib/src/runner/browser/iframe_listener.dart |
diff --git a/lib/src/runner/browser/iframe_listener.dart b/lib/src/runner/browser/iframe_listener.dart |
deleted file mode 100644 |
index 9e49dfca4930456279920dd682bb7e17c5ff96b4..0000000000000000000000000000000000000000 |
--- a/lib/src/runner/browser/iframe_listener.dart |
+++ /dev/null |
@@ -1,213 +0,0 @@ |
-// 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 'dart:async'; |
-import 'dart:convert'; |
-import 'dart:html' hide Metadata; |
- |
-import 'package:stream_channel/stream_channel.dart'; |
- |
-import '../../backend/declarer.dart'; |
-import '../../backend/group.dart'; |
-import '../../backend/live_test.dart'; |
-import '../../backend/metadata.dart'; |
-import '../../backend/suite.dart'; |
-import '../../backend/test.dart'; |
-import '../../backend/test_platform.dart'; |
-import '../../util/remote_exception.dart'; |
-import '../../utils.dart'; |
- |
-/// A class that runs tests in a separate iframe. |
-/// |
-/// This indirectly communicates with the test server. It uses `postMessage` to |
-/// relay communication through the host page, which has a WebSocket connection |
-/// to the test server. |
-class IframeListener { |
- /// The test suite to run. |
- final Suite _suite; |
- |
- /// Extracts metadata about all the tests in the function returned by |
- /// [getMain] and sends information about them over the `postMessage` |
- /// connection. |
- /// |
- /// The main function is wrapped in a closure so that we can handle it being |
- /// undefined here rather than in the generated code. |
- /// |
- /// Once that's done, this starts listening for commands about which tests to |
- /// run. |
- static Future start(Function getMain()) async { |
- var channel = _postMessageChannel(); |
- |
- // Send periodic pings to the test runner so it can know when the suite is |
- // paused for debugging. |
- new Timer.periodic(new Duration(seconds: 1), |
- (_) => channel.sink.add({"type": "ping"})); |
- |
- var main; |
- try { |
- main = getMain(); |
- } on NoSuchMethodError catch (_) { |
- _sendLoadException(channel, "No top-level main() function defined."); |
- return; |
- } |
- |
- if (main is! Function) { |
- _sendLoadException(channel, "Top-level main getter is not a function."); |
- return; |
- } else if (main is! AsyncFunction) { |
- _sendLoadException(channel, "Top-level main() function takes arguments."); |
- return; |
- } |
- |
- var url = Uri.parse(window.location.href); |
- var message = JSON.decode(Uri.decodeFull(url.fragment)); |
- var metadata = new Metadata.deserialize(message['metadata']); |
- |
- var declarer = new Declarer(metadata); |
- try { |
- await declarer.declare(() { |
- return runZoned(() => new Future.sync(main), |
- zoneSpecification: new ZoneSpecification(print: (_, __, ___, line) { |
- channel.sink.add({"type": "print", "line": line}); |
- })); |
- }); |
- } catch (error, stackTrace) { |
- channel.sink.add({ |
- "type": "error", |
- "error": RemoteException.serialize(error, stackTrace) |
- }); |
- return; |
- } |
- |
- var browser = TestPlatform.find(message['browser']); |
- var suite = new Suite(declarer.build(), platform: browser); |
- new IframeListener._(suite)._listen(channel); |
- |
- return; |
- } |
- |
- /// Constructs a [MultiChannel] wrapping the `postMessage` communication with |
- /// the host page. |
- /// |
- /// This [MultiChannel] corresponds to a [MultiChannel] in the server's |
- /// [IframeTest] class. |
- static MultiChannel _postMessageChannel() { |
- var inputController = new StreamController(sync: true); |
- var outputController = new StreamController(sync: true); |
- |
- window.onMessage.listen((message) { |
- // A message on the Window can theoretically come from any website. It's |
- // very unlikely that a malicious site would care about hacking someone's |
- // unit tests, let alone be able to find the test server while it's |
- // running, but it's good practice to check the origin anyway. |
- if (message.origin != window.location.origin) return; |
- message.stopPropagation(); |
- inputController.add(message.data); |
- }); |
- |
- outputController.stream.listen((data) { |
- // TODO(nweiz): Stop manually adding href here once issue 22554 is |
- // fixed. |
- window.parent.postMessage({ |
- "href": window.location.href, |
- "data": data |
- }, window.location.origin); |
- }); |
- |
- return new MultiChannel(new StreamChannel( |
- inputController.stream, outputController.sink)); |
- } |
- |
- /// Sends a message over [channel] indicating that the tests failed to load. |
- /// |
- /// [message] should describe the failure. |
- static void _sendLoadException(MultiChannel channel, String message) { |
- channel.sink.add({"type": "loadException", "message": message}); |
- } |
- |
- IframeListener._(this._suite); |
- |
- /// Send information about [_suite] across [channel] and start listening for |
- /// commands to run the tests. |
- void _listen(MultiChannel channel) { |
- channel.sink.add({ |
- "type": "success", |
- "root": _serializeGroup(channel, _suite.group, []) |
- }); |
- } |
- |
- /// Serializes [group] into a JSON-safe map. |
- /// |
- /// [parents] lists the groups that contain [group]. |
- Map _serializeGroup(MultiChannel channel, Group group, |
- Iterable<Group> parents) { |
- parents = parents.toList()..add(group); |
- return { |
- "type": "group", |
- "name": group.name, |
- "metadata": group.metadata.serialize(), |
- "setUpAll": _serializeTest(channel, group.setUpAll, parents), |
- "tearDownAll": _serializeTest(channel, group.tearDownAll, parents), |
- "entries": group.entries.map((entry) { |
- return entry is Group |
- ? _serializeGroup(channel, entry, parents) |
- : _serializeTest(channel, entry, parents); |
- }).toList() |
- }; |
- } |
- |
- /// Serializes [test] into a JSON-safe map. |
- /// |
- /// [groups] lists the groups that contain [test]. Returns `null` if [test] |
- /// is `null`. |
- Map _serializeTest(MultiChannel channel, Test test, Iterable<Group> groups) { |
- if (test == null) return null; |
- |
- var testChannel = channel.virtualChannel(); |
- testChannel.stream.listen((message) { |
- assert(message['command'] == 'run'); |
- _runLiveTest( |
- test.load(_suite, groups: groups), |
- channel.virtualChannel(message['channel'])); |
- }); |
- |
- return { |
- "type": "test", |
- "name": test.name, |
- "metadata": test.metadata.serialize(), |
- "channel": testChannel.id |
- }; |
- } |
- |
- /// Runs [liveTest] and sends the results across [channel]. |
- void _runLiveTest(LiveTest liveTest, MultiChannel channel) { |
- channel.stream.listen((message) { |
- assert(message['command'] == 'close'); |
- liveTest.close(); |
- }); |
- |
- liveTest.onStateChange.listen((state) { |
- channel.sink.add({ |
- "type": "state-change", |
- "status": state.status.name, |
- "result": state.result.name |
- }); |
- }); |
- |
- liveTest.onError.listen((asyncError) { |
- channel.sink.add({ |
- "type": "error", |
- "error": RemoteException.serialize( |
- asyncError.error, asyncError.stackTrace) |
- }); |
- }); |
- |
- liveTest.onPrint.listen((line) { |
- print(line); |
- channel.sink.add({"type": "print", "line": line}); |
- }); |
- |
- liveTest.run().then((_) => channel.sink.add({"type": "complete"})); |
- } |
-} |