OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 import 'dart:async'; | |
6 import 'dart:developer'; | |
kevmoo
2016/02/11 19:57:50
unused import
nweiz
2016/02/11 21:29:18
Done.
| |
7 | |
8 import 'package:async/async.dart'; | |
kevmoo
2016/02/11 19:57:50
unused import
nweiz
2016/02/11 21:29:18
Done.
| |
9 import 'package:stream_channel/stream_channel.dart'; | |
10 | |
11 import '../backend/declarer.dart'; | |
12 import '../backend/group.dart'; | |
13 import '../backend/live_test.dart'; | |
14 import '../backend/metadata.dart'; | |
15 import '../backend/operating_system.dart'; | |
16 import '../backend/suite.dart'; | |
17 import '../backend/test.dart'; | |
18 import '../backend/test_platform.dart'; | |
19 import '../util/remote_exception.dart'; | |
20 import '../utils.dart'; | |
21 | |
22 class RemoteListener { | |
23 /// The test suite to run. | |
24 final Suite _suite; | |
25 | |
26 /// The zone to forward prints to, or `null` if prints shouldn't be forwarded. | |
27 final Zone _printZone; | |
28 | |
29 /// Extracts metadata about all the tests in the function returned by | |
30 /// [getMain] and returns a channel that will send information about them. | |
31 /// | |
32 /// The main function is wrapped in a closure so that we can handle it being | |
33 /// undefined here rather than in the generated code. | |
34 /// | |
35 /// Once that's done, this starts listening for commands about which tests to | |
36 /// run. | |
37 /// | |
38 /// If [hidePrints] is `true` (the default), calls to `print()` within this | |
39 /// suite will not be forwarded to the parent zone's print handler. However, | |
40 /// the caller may want them to be forwarded in (for example) a browser | |
41 /// context where they'll be visible in the development console. | |
42 static StreamChannel start(AsyncFunction getMain(), {bool hidePrints: true}) { | |
43 // This has to be synchronous to work around sdk#25745. Otherwise, there'll | |
44 // be an asynchronous pause before a syntax error notification is sent, | |
45 // which will cause the send to fail entirely. | |
46 var controller = new StreamChannelController( | |
47 allowForeignErrors: false, sync: true); | |
48 var channel = new MultiChannel(controller.local); | |
49 | |
50 var printZone = hidePrints ? null : Zone.current; | |
51 runZoned(() async { | |
52 var main; | |
53 try { | |
54 main = getMain(); | |
55 } on NoSuchMethodError catch (_) { | |
56 _sendLoadException(channel, "No top-level main() function defined."); | |
57 return; | |
58 } catch (error, stackTrace) { | |
59 _sendError(channel, error, stackTrace); | |
60 return; | |
61 } | |
62 | |
63 if (main is! Function) { | |
64 _sendLoadException(channel, "Top-level main getter is not a function."); | |
65 return; | |
66 } else if (main is! AsyncFunction) { | |
67 _sendLoadException( | |
68 channel, "Top-level main() function takes arguments."); | |
69 return; | |
70 } | |
71 | |
72 var message = await channel.stream.first; | |
73 var metadata = new Metadata.deserialize(message['metadata']); | |
74 var declarer = new Declarer(metadata); | |
75 await declarer.declare(main); | |
76 | |
77 var os = message['os'] == null | |
78 ? null | |
79 : OperatingSystem.find(message['os']); | |
80 var platform = TestPlatform.find(message['platform']); | |
81 var suite = new Suite(declarer.build(), platform: platform, os: os); | |
82 new RemoteListener._(suite, printZone)._listen(channel); | |
83 }, onError: (error, stackTrace) { | |
84 _sendError(channel, error, stackTrace); | |
85 }, zoneSpecification: new ZoneSpecification(print: (_, __, ___, line) { | |
86 if (printZone != null) printZone.print(line); | |
87 channel.sink.add({"type": "print", "line": line}); | |
88 })); | |
89 | |
90 return controller.foreign; | |
91 } | |
92 | |
93 | |
94 /// Sends a message over [channel] indicating that the tests failed to load. | |
95 /// | |
96 /// [message] should describe the failure. | |
97 static void _sendLoadException(StreamChannel channel, String message) { | |
98 channel.sink.add({"type": "loadException", "message": message}); | |
99 } | |
100 | |
101 /// Sends a message over [channel] indicating an error from user code. | |
102 static void _sendError(StreamChannel channel, error, StackTrace stackTrace) { | |
103 channel.sink.add({ | |
104 "type": "error", | |
105 "error": RemoteException.serialize(error, stackTrace) | |
106 }); | |
107 } | |
108 | |
109 RemoteListener._(this._suite, this._printZone); | |
110 | |
111 /// Send information about [_suite] across [channel] and start listening for | |
112 /// commands to run the tests. | |
113 void _listen(MultiChannel channel) { | |
114 channel.sink.add({ | |
115 "type": "success", | |
116 "root": _serializeGroup(channel, _suite.group, []) | |
117 }); | |
118 } | |
119 | |
120 /// Serializes [group] into a JSON-safe map. | |
121 /// | |
122 /// [parents] lists the groups that contain [group]. | |
123 Map _serializeGroup(MultiChannel channel, Group group, | |
124 Iterable<Group> parents) { | |
125 parents = parents.toList()..add(group); | |
126 return { | |
127 "type": "group", | |
128 "name": group.name, | |
129 "metadata": group.metadata.serialize(), | |
130 "setUpAll": _serializeTest(channel, group.setUpAll, parents), | |
131 "tearDownAll": _serializeTest(channel, group.tearDownAll, parents), | |
132 "entries": group.entries.map((entry) { | |
133 return entry is Group | |
134 ? _serializeGroup(channel, entry, parents) | |
135 : _serializeTest(channel, entry, parents); | |
136 }).toList() | |
137 }; | |
138 } | |
139 | |
140 /// Serializes [test] into a JSON-safe map. | |
141 /// | |
142 /// [groups] lists the groups that contain [test]. Returns `null` if [test] | |
143 /// is `null`. | |
144 Map _serializeTest(MultiChannel channel, Test test, Iterable<Group> groups) { | |
145 if (test == null) return null; | |
146 | |
147 var testChannel = channel.virtualChannel(); | |
148 testChannel.stream.listen((message) { | |
149 assert(message['command'] == 'run'); | |
150 _runLiveTest( | |
151 test.load(_suite, groups: groups), | |
152 channel.virtualChannel(message['channel'])); | |
153 }); | |
154 | |
155 return { | |
156 "type": "test", | |
157 "name": test.name, | |
158 "metadata": test.metadata.serialize(), | |
159 "channel": testChannel.id | |
160 }; | |
161 } | |
162 | |
163 /// Runs [liveTest] and sends the results across [channel]. | |
164 void _runLiveTest(LiveTest liveTest, MultiChannel channel) { | |
165 channel.stream.listen((message) { | |
166 assert(message['command'] == 'close'); | |
167 liveTest.close(); | |
168 }); | |
169 | |
170 liveTest.onStateChange.listen((state) { | |
171 channel.sink.add({ | |
172 "type": "state-change", | |
173 "status": state.status.name, | |
174 "result": state.result.name | |
175 }); | |
176 }); | |
177 | |
178 liveTest.onError.listen((asyncError) { | |
179 channel.sink.add({ | |
180 "type": "error", | |
181 "error": RemoteException.serialize( | |
182 asyncError.error, asyncError.stackTrace) | |
183 }); | |
184 }); | |
185 | |
186 liveTest.onPrint.listen((line) { | |
187 if (_printZone != null) _printZone.print(line); | |
188 channel.sink.add({"type": "print", "line": line}); | |
189 }); | |
190 | |
191 liveTest.run().then((_) => channel.sink.add({"type": "complete"})); | |
192 } | |
193 } | |
OLD | NEW |