OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, 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 @TestOn("vm") | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:isolate'; | |
9 | |
10 import 'package:test/src/backend/group.dart'; | |
11 import 'package:test/src/backend/invoker.dart'; | |
12 import 'package:test/src/backend/live_test.dart'; | |
13 import 'package:test/src/backend/metadata.dart'; | |
14 import 'package:test/src/backend/state.dart'; | |
15 import 'package:test/src/backend/suite.dart'; | |
16 import 'package:test/src/runner/vm/isolate_listener.dart'; | |
17 import 'package:test/src/runner/vm/isolate_test.dart'; | |
18 import 'package:test/src/util/remote_exception.dart'; | |
19 import 'package:test/test.dart'; | |
20 | |
21 import '../utils.dart'; | |
22 | |
23 /// An isolate that's been spun up for the current test. | |
24 /// | |
25 /// This is tracked so that it can be killed once the test is done. | |
26 Isolate _isolate; | |
27 | |
28 /// A live test that's running for the current test. | |
29 /// | |
30 /// This is tracked so that it can be closed once the test is done. | |
31 LiveTest _liveTest; | |
32 | |
33 void main() { | |
34 tearDown(() { | |
35 if (_isolate != null) _isolate.kill(); | |
36 _isolate = null; | |
37 | |
38 if (_liveTest != null) _liveTest.close(); | |
39 _liveTest = null; | |
40 }); | |
41 | |
42 test("sends a list of available tests and groups on startup", () async { | |
43 var response = await (await _spawnIsolate(_successfulTests)).first; | |
44 expect(response, containsPair("type", "success")); | |
45 expect(response, contains("root")); | |
46 | |
47 var root = response["root"]; | |
48 expect(root, containsPair("type", "group")); | |
49 expect(root, containsPair("name", null)); | |
50 | |
51 var tests = root["entries"]; | |
52 expect(tests, hasLength(3)); | |
53 expect(tests[0], containsPair("name", "successful 1")); | |
54 expect(tests[1], containsPair("name", "successful 2")); | |
55 expect(tests[2], containsPair("type", "group")); | |
56 expect(tests[2], containsPair("name", "successful")); | |
57 expect(tests[2], contains("entries")); | |
58 expect(tests[2]["entries"][0], containsPair("name", "successful 3")); | |
59 }); | |
60 | |
61 test("waits for a returned future sending a response", () async { | |
62 var response = await (await _spawnIsolate(_asyncTests)).first; | |
63 expect(response, containsPair("type", "success")); | |
64 expect(response, contains("root")); | |
65 | |
66 var tests = response["root"]["entries"]; | |
67 expect(tests, hasLength(3)); | |
68 expect(tests[0], containsPair("name", "successful 1")); | |
69 expect(tests[1], containsPair("name", "successful 2")); | |
70 expect(tests[2], containsPair("name", "successful 3")); | |
71 }); | |
72 | |
73 test("sends an error response if loading fails", () async { | |
74 var response = await (await _spawnIsolate(_loadError)).first; | |
75 expect(response, containsPair("type", "error")); | |
76 expect(response, contains("error")); | |
77 | |
78 var error = RemoteException.deserialize(response["error"]).error; | |
79 expect(error.message, equals("oh no")); | |
80 expect(error.type, equals("String")); | |
81 }); | |
82 | |
83 test("sends an error response on a NoSuchMethodError", () async { | |
84 var response = await (await _spawnIsolate(_noSuchMethodError)).first; | |
85 expect(response, containsPair("type", "loadException")); | |
86 expect(response, | |
87 containsPair("message", "No top-level main() function defined.")); | |
88 }); | |
89 | |
90 test("sends an error response on non-function main", () async { | |
91 var response = await (await _spawnIsolate(_nonFunction)).first; | |
92 expect(response, containsPair("type", "loadException")); | |
93 expect(response, | |
94 containsPair("message", "Top-level main getter is not a function.")); | |
95 }); | |
96 | |
97 test("sends an error response on wrong-arity main", () async { | |
98 var response = await (await _spawnIsolate(_wrongArity)).first; | |
99 expect(response, containsPair("type", "loadException")); | |
100 expect( | |
101 response, | |
102 containsPair( | |
103 "message", | |
104 "Top-level main() function takes arguments.")); | |
105 }); | |
106 | |
107 group("in a successful test", () { | |
108 test("the state changes from pending to running to complete", () async { | |
109 var liveTest = await _isolateTest(_successfulTests); | |
110 liveTest.onError.listen(expectAsync((_) {}, count: 0)); | |
111 | |
112 expect(liveTest.state, | |
113 equals(const State(Status.pending, Result.success))); | |
114 | |
115 var future = liveTest.run(); | |
116 expect(liveTest.state, | |
117 equals(const State(Status.running, Result.success))); | |
118 | |
119 await future; | |
120 expect(liveTest.state, | |
121 equals(const State(Status.complete, Result.success))); | |
122 }); | |
123 | |
124 test("onStateChange fires for each state change", () async { | |
125 var liveTest = await _isolateTest(_successfulTests); | |
126 liveTest.onError.listen(expectAsync((_) {}, count: 0)); | |
127 | |
128 var first = true; | |
129 liveTest.onStateChange.listen(expectAsync((state) { | |
130 if (first) { | |
131 expect(state.status, equals(Status.running)); | |
132 first = false; | |
133 } else { | |
134 expect(state.status, equals(Status.complete)); | |
135 } | |
136 expect(state.result, equals(Result.success)); | |
137 }, count: 2, max: 2)); | |
138 | |
139 await liveTest.run(); | |
140 }); | |
141 }); | |
142 | |
143 group("in a test with failures", () { | |
144 test("a failure reported causes the test to fail", () async { | |
145 var liveTest = await _isolateTest(_failingTest); | |
146 expectSingleFailure(liveTest); | |
147 await liveTest.run(); | |
148 }); | |
149 | |
150 test("a failure reported asynchronously after the test causes it to error", | |
151 () async { | |
152 var liveTest = await _isolateTest(_failAfterSucceedTest); | |
153 expectStates(liveTest, [ | |
154 const State(Status.running, Result.success), | |
155 const State(Status.complete, Result.success), | |
156 const State(Status.complete, Result.failure), | |
157 const State(Status.complete, Result.error) | |
158 ]); | |
159 | |
160 expectErrors(liveTest, [(error) { | |
161 expect(lastState, | |
162 equals(const State(Status.complete, Result.failure))); | |
163 expect(error, isTestFailure("oh no")); | |
164 }, (error) { | |
165 expect(lastState, equals(const State(Status.complete, Result.error))); | |
166 expect(error, isRemoteException( | |
167 "This test failed after it had already completed. Make sure to " | |
168 "use [expectAsync]\n" | |
169 "or the [completes] matcher when testing async code.")); | |
170 }]); | |
171 | |
172 await liveTest.run(); | |
173 }); | |
174 | |
175 test("multiple asynchronous failures are reported", () async { | |
176 var liveTest = await _isolateTest(_multiFailTest); | |
177 expectStates(liveTest, [ | |
178 const State(Status.running, Result.success), | |
179 const State(Status.complete, Result.failure) | |
180 ]); | |
181 | |
182 expectErrors(liveTest, [(error) { | |
183 expect(lastState.status, equals(Status.complete)); | |
184 expect(error, isTestFailure("one")); | |
185 }, (error) { | |
186 expect(error, isTestFailure("two")); | |
187 }, (error) { | |
188 expect(error, isTestFailure("three")); | |
189 }, (error) { | |
190 expect(error, isTestFailure("four")); | |
191 }]); | |
192 | |
193 await liveTest.run(); | |
194 }); | |
195 }); | |
196 | |
197 group("in a test with errors", () { | |
198 test("an error reported causes the test to error", () async { | |
199 var liveTest = await _isolateTest(_errorTest); | |
200 expectStates(liveTest, [ | |
201 const State(Status.running, Result.success), | |
202 const State(Status.complete, Result.error) | |
203 ]); | |
204 | |
205 expectErrors(liveTest, [(error) { | |
206 expect(lastState.status, equals(Status.complete)); | |
207 expect(error, isRemoteException("oh no")); | |
208 }]); | |
209 | |
210 await liveTest.run(); | |
211 }); | |
212 | |
213 test("an error reported asynchronously after the test causes it to error", | |
214 () async { | |
215 var liveTest = await _isolateTest(_errorAfterSucceedTest); | |
216 expectStates(liveTest, [ | |
217 const State(Status.running, Result.success), | |
218 const State(Status.complete, Result.success), | |
219 const State(Status.complete, Result.error) | |
220 ]); | |
221 | |
222 expectErrors(liveTest, [(error) { | |
223 expect(lastState, | |
224 equals(const State(Status.complete, Result.error))); | |
225 expect(error, isRemoteException("oh no")); | |
226 }, (error) { | |
227 expect(error, isRemoteException( | |
228 "This test failed after it had already completed. Make sure to " | |
229 "use [expectAsync]\n" | |
230 "or the [completes] matcher when testing async code.")); | |
231 }]); | |
232 | |
233 await liveTest.run(); | |
234 }); | |
235 | |
236 test("multiple asynchronous errors are reported", () async { | |
237 var liveTest = await _isolateTest(_multiErrorTest); | |
238 expectStates(liveTest, [ | |
239 const State(Status.running, Result.success), | |
240 const State(Status.complete, Result.error) | |
241 ]); | |
242 | |
243 expectErrors(liveTest, [(error) { | |
244 expect(lastState.status, equals(Status.complete)); | |
245 expect(error, isRemoteException("one")); | |
246 }, (error) { | |
247 expect(error, isRemoteException("two")); | |
248 }, (error) { | |
249 expect(error, isRemoteException("three")); | |
250 }, (error) { | |
251 expect(error, isRemoteException("four")); | |
252 }]); | |
253 | |
254 await liveTest.run(); | |
255 }); | |
256 }); | |
257 | |
258 test("forwards a test's prints", () async { | |
259 var liveTest = await _isolateTest(_printTest); | |
260 expect(liveTest.onPrint.take(2).toList(), | |
261 completion(equals(["Hello,", "world!"]))); | |
262 | |
263 await liveTest.run(); | |
264 }); | |
265 } | |
266 | |
267 /// Loads the first test defined in [entryPoint] in another isolate. | |
268 /// | |
269 /// This test will be automatically closed when the test is finished. | |
270 Future<LiveTest> _isolateTest(void entryPoint(SendPort sendPort)) async { | |
271 var response = await (await _spawnIsolate(entryPoint)).first; | |
272 expect(response, containsPair("type", "success")); | |
273 | |
274 var testMap = response["root"]["entries"].first; | |
275 expect(testMap, containsPair("type", "test")); | |
276 var metadata = new Metadata.deserialize(testMap["metadata"]); | |
277 var test = new IsolateTest(testMap["name"], metadata, testMap["sendPort"]); | |
278 var suite = new Suite(new Group.root([test])); | |
279 _liveTest = test.load(suite); | |
280 return _liveTest; | |
281 } | |
282 | |
283 /// Spawns an isolate from [entryPoint], sends it a new [SendPort], and returns | |
284 /// the corresponding [ReceivePort]. | |
285 /// | |
286 /// This isolate will be automatically killed when the test is finished. | |
287 Future<ReceivePort> _spawnIsolate(void entryPoint(SendPort sendPort)) async { | |
288 var receivePort = new ReceivePort(); | |
289 var isolate = await Isolate.spawn(entryPoint, receivePort.sendPort); | |
290 _isolate = isolate; | |
291 return receivePort; | |
292 } | |
293 | |
294 /// An isolate entrypoint that throws immediately. | |
295 void _loadError(SendPort sendPort) { | |
296 IsolateListener.start(sendPort, new Metadata(), () => () => throw 'oh no'); | |
297 } | |
298 | |
299 /// An isolate entrypoint that throws a NoSuchMethodError. | |
300 void _noSuchMethodError(SendPort sendPort) { | |
301 IsolateListener.start(sendPort, new Metadata(), () => | |
302 throw new NoSuchMethodError(null, #main, [], {})); | |
303 } | |
304 | |
305 /// An isolate entrypoint that returns a non-function. | |
306 void _nonFunction(SendPort sendPort) { | |
307 IsolateListener.start(sendPort, new Metadata(), () => null); | |
308 } | |
309 | |
310 /// An isolate entrypoint that returns a function with the wrong arity. | |
311 void _wrongArity(SendPort sendPort) { | |
312 IsolateListener.start(sendPort, new Metadata(), () => (_) {}); | |
313 } | |
314 | |
315 /// An isolate entrypoint that defines three tests that succeed. | |
316 void _successfulTests(SendPort sendPort) { | |
317 IsolateListener.start(sendPort, new Metadata(), () => () { | |
318 test("successful 1", () {}); | |
319 test("successful 2", () {}); | |
320 group("successful", () => test("3", () {})); | |
321 }); | |
322 } | |
323 | |
324 /// An isolate entrypoint that defines three tests asynchronously. | |
325 void _asyncTests(SendPort sendPort) { | |
326 IsolateListener.start(sendPort, new Metadata(), () => () { | |
327 return new Future(() { | |
328 test("successful 1", () {}); | |
329 | |
330 return new Future(() { | |
331 test("successful 2", () {}); | |
332 | |
333 return new Future(() { | |
334 test("successful 3", () {}); | |
335 }); | |
336 }); | |
337 }); | |
338 }); | |
339 } | |
340 | |
341 /// An isolate entrypoint that defines a test that fails. | |
342 void _failingTest(SendPort sendPort) { | |
343 IsolateListener.start(sendPort, new Metadata(), () => () { | |
344 test("failure", () => throw new TestFailure('oh no')); | |
345 }); | |
346 } | |
347 | |
348 /// An isolate entrypoint that defines a test that fails after succeeding. | |
349 void _failAfterSucceedTest(SendPort sendPort) { | |
350 IsolateListener.start(sendPort, new Metadata(), () => () { | |
351 test("fail after succeed", () { | |
352 pumpEventQueue().then((_) { | |
353 throw new TestFailure('oh no'); | |
354 }); | |
355 }); | |
356 }); | |
357 } | |
358 | |
359 /// An isolate entrypoint that defines a test that fails multiple times. | |
360 void _multiFailTest(SendPort sendPort) { | |
361 IsolateListener.start(sendPort, new Metadata(), () => () { | |
362 test("multiple failures", () { | |
363 Invoker.current.addOutstandingCallback(); | |
364 new Future(() => throw new TestFailure("one")); | |
365 new Future(() => throw new TestFailure("two")); | |
366 new Future(() => throw new TestFailure("three")); | |
367 new Future(() => throw new TestFailure("four")); | |
368 }); | |
369 }); | |
370 } | |
371 | |
372 /// An isolate entrypoint that defines a test that errors. | |
373 void _errorTest(SendPort sendPort) { | |
374 IsolateListener.start(sendPort, new Metadata(), () => () { | |
375 test("error", () => throw 'oh no'); | |
376 }); | |
377 } | |
378 | |
379 /// An isolate entrypoint that defines a test that errors after succeeding. | |
380 void _errorAfterSucceedTest(SendPort sendPort) { | |
381 IsolateListener.start(sendPort, new Metadata(), () => () { | |
382 test("error after succeed", () { | |
383 pumpEventQueue().then((_) { | |
384 throw 'oh no'; | |
385 }); | |
386 }); | |
387 }); | |
388 } | |
389 | |
390 /// An isolate entrypoint that defines a test that errors multiple times. | |
391 void _multiErrorTest(SendPort sendPort) { | |
392 IsolateListener.start(sendPort, new Metadata(), () => () { | |
393 test("multiple errors", () { | |
394 Invoker.current.addOutstandingCallback(); | |
395 new Future(() => throw "one"); | |
396 new Future(() => throw "two"); | |
397 new Future(() => throw "three"); | |
398 new Future(() => throw "four"); | |
399 }); | |
400 }); | |
401 } | |
402 | |
403 /// An isolate entrypoint that defines a test that prints twice. | |
404 void _printTest(SendPort sendPort) { | |
405 IsolateListener.start(sendPort, new Metadata(), () => () { | |
406 test("prints", () { | |
407 print("Hello,"); | |
408 return new Future(() => print("world!")); | |
409 }); | |
410 }); | |
411 } | |
412 | |
OLD | NEW |