| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /// A test library for testing test libraries? We must go deeper. | 5 /// A test library for testing test libraries? We must go deeper. |
| 6 /// | 6 /// |
| 7 /// Since unit testing code tends to use a lot of global state, it can be tough | 7 /// Since unit testing code tends to use a lot of global state, it can be tough |
| 8 /// to test. This library manages it by running each test case in a child | 8 /// to test. This library manages it by running each test case in a child |
| 9 /// isolate, then reporting the results back to the parent isolate. | 9 /// isolate, then reporting the results back to the parent isolate. |
| 10 library metatest; | 10 library metatest; |
| 11 | 11 |
| 12 import 'dart:async'; |
| 12 import 'dart:io'; | 13 import 'dart:io'; |
| 13 import 'dart:async'; | |
| 14 import 'dart:isolate'; | 14 import 'dart:isolate'; |
| 15 import 'dart:platform' as platform; |
| 15 | 16 |
| 16 import 'package:path/path.dart' as path; | 17 import 'package:path/path.dart' as path; |
| 17 import 'package:unittest/unittest.dart'; | 18 import 'package:unittest/unittest.dart'; |
| 18 import 'package:scheduled_test/scheduled_test.dart' as scheduled_test; | 19 import 'package:scheduled_test/scheduled_test.dart' as scheduled_test; |
| 19 | 20 |
| 20 import 'utils.dart'; | 21 import 'utils.dart'; |
| 21 | 22 |
| 22 // TODO(nweiz): get rid of this once issue 8863 is fixed. | |
| 23 /// The path to the Dart executable. This is only set in a child isolate. | |
| 24 String get dartExecutable => _executable; | |
| 25 String _executable; | |
| 26 | |
| 27 /// Declares a test with the given [description] and [body]. [body] corresponds | 23 /// Declares a test with the given [description] and [body]. [body] corresponds |
| 28 /// to the `main` method of a test file, and will be run in an isolate. By | 24 /// to the `main` method of a test file, and will be run in an isolate. By |
| 29 /// default, this expects that all tests defined in [body] pass, but if | 25 /// default, this expects that all tests defined in [body] pass, but if |
| 30 /// [passing] is passed, only tests listed there are expected to pass. | 26 /// [passing] is passed, only tests listed there are expected to pass. |
| 31 void expectTestsPass(String description, void body(), {List<String> passing}) { | 27 void expectTestsPass(String description, void body(), {List<String> passing}) { |
| 32 _setUpTest(description, body, (results) { | 28 _setUpTest(description, body, (results) { |
| 33 if (_hasError(results)) { | 29 if (_hasError(results)) { |
| 34 throw 'Expected all tests to pass, but got error(s):\n' | 30 throw 'Expected all tests to pass, but got error(s):\n' |
| 35 '${_summarizeTests(results)}'; | 31 '${_summarizeTests(results)}'; |
| 36 } else if (passing == null) { | 32 } else if (passing == null) { |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 68 } else if (results['passed'] != 0) { | 64 } else if (results['passed'] != 0) { |
| 69 throw 'Expected all tests to fail, but some passed:\n' | 65 throw 'Expected all tests to fail, but some passed:\n' |
| 70 '${_summarizeTests(results)}'; | 66 '${_summarizeTests(results)}'; |
| 71 } | 67 } |
| 72 }); | 68 }); |
| 73 } | 69 } |
| 74 | 70 |
| 75 /// Runs [setUpFn] before every metatest. Note that [setUpFn] will be | 71 /// Runs [setUpFn] before every metatest. Note that [setUpFn] will be |
| 76 /// overwritten if the test itself calls [setUp]. | 72 /// overwritten if the test itself calls [setUp]. |
| 77 void metaSetUp(void setUpFn()) { | 73 void metaSetUp(void setUpFn()) { |
| 78 _inChildIsolate.then((inIsolate) { | 74 if (_inChildIsolate) scheduled_test.setUp(setUpFn); |
| 79 if (inIsolate) scheduled_test.setUp(setUpFn); | |
| 80 }); | |
| 81 } | 75 } |
| 82 | 76 |
| 83 /// Sets up a test with the given [description] and [body]. After the test runs, | 77 /// Sets up a test with the given [description] and [body]. After the test runs, |
| 84 /// calls [validate] with the result map. | 78 /// calls [validate] with the result map. |
| 85 void _setUpTest(String description, void body(), void validate(Map)) { | 79 void _setUpTest(String description, void body(), void validate(Map)) { |
| 86 _inChildIsolate.then((inIsolate) { | 80 if (_inChildIsolate) { |
| 87 if (inIsolate) { | 81 _ensureInitialized(); |
| 88 _ensureInitialized(); | 82 if (_testToRun == description) body(); |
| 89 if (_testToRun == description) body(); | 83 } else { |
| 90 } else { | 84 test(description, () { |
| 91 test(description, () { | 85 expect(_runInIsolate(description).then(validate), completes); |
| 92 expect(_runInIsolate(description).then(validate), completes); | 86 }); |
| 93 }); | 87 } |
| 94 } | |
| 95 }); | |
| 96 } | 88 } |
| 97 | 89 |
| 98 /// The description of the test to run in the child isolate. `null` in the | 90 /// The description of the test to run in the child isolate. |
| 99 /// parent isolate. Not set until [_inChildIsolate] completes. | 91 /// |
| 92 /// `null` in the parent isolate. |
| 100 String _testToRun; | 93 String _testToRun; |
| 101 | 94 |
| 102 /// The port with which the child isolate should communicate with the parent | 95 /// The port with which the child isolate should communicate with the parent |
| 103 /// isolate. `null` in the parent isolate. Not set until [_inChildIsolate] | 96 /// isolate. |
| 104 /// completes. | 97 /// |
| 98 /// `null` in the parent isolate. |
| 105 SendPort _replyTo; | 99 SendPort _replyTo; |
| 106 | 100 |
| 107 /// The cached [Future] for [_inChildIsolate]. | 101 /// Whether or not we're running in a child isolate that's supposed to run a |
| 108 Future<bool> _inChildIsolateFuture; | 102 /// test. |
| 103 bool _inChildIsolate; |
| 109 | 104 |
| 110 /// The initial message received by the isolate. | 105 /// Initialize metatest. |
| 111 var _initialMessage; | 106 /// |
| 112 | 107 /// [message] should be the second argument to [main]. It's used to determine |
| 113 void metaTestInit(message) { | 108 /// whether this test is in the parent isolate or a child isolate. |
| 114 _initialMessage = message; | 109 void initMetatest(message) { |
| 115 } | 110 if (message == null) { |
| 116 | 111 _inChildIsolate = false; |
| 117 /// Returns whether or not we're running in a child isolate that's supposed to | |
| 118 /// run a test. | |
| 119 Future<bool> get _inChildIsolate { | |
| 120 if (_inChildIsolateFuture != null) return _inChildIsolateFuture; | |
| 121 | |
| 122 if (_initialMessage == null) { | |
| 123 _inChildIsolateFuture = new Future.value(false); | |
| 124 } else { | 112 } else { |
| 125 _testToRun = _initialMessage['testToRun']; | 113 _testToRun = message['testToRun']; |
| 126 _executable = _initialMessage['executable']; | 114 _replyTo = message['replyTo']; |
| 127 _replyTo = _initialMessage['replyTo']; | 115 _inChildIsolate = true; |
| 128 _inChildIsolateFuture = new Future.value(true); | |
| 129 } | 116 } |
| 130 return _inChildIsolateFuture; | |
| 131 } | 117 } |
| 132 | 118 |
| 133 /// Runs the test described by [description] in its own isolate. Returns a map | 119 /// Runs the test described by [description] in its own isolate. Returns a map |
| 134 /// describing the results of that test run. | 120 /// describing the results of that test run. |
| 135 Future<Map> _runInIsolate(String description) { | 121 Future<Map> _runInIsolate(String description) { |
| 136 var replyPort = new ReceivePort(); | 122 var replyPort = new ReceivePort(); |
| 137 // TODO(nweiz): Don't use path here once issue 8440 is fixed. | 123 return Isolate.spawnUri(platform.script, [], { |
| 138 return Isolate.spawnUri(Uri.parse(path.join(path.current, Platform.script)), | |
| 139 [], { | |
| 140 'testToRun': description, | 124 'testToRun': description, |
| 141 'executable': Platform.executable, | |
| 142 'replyTo': replyPort.sendPort | 125 'replyTo': replyPort.sendPort |
| 143 }).then((_) { | 126 }).then((_) { |
| 144 var future = replyPort.first; | |
| 145 | |
| 146 // TODO(nweiz): Remove this timeout once issue 8417 is fixed and we can | 127 // TODO(nweiz): Remove this timeout once issue 8417 is fixed and we can |
| 147 // capture top-level exceptions. | 128 // capture top-level exceptions. |
| 148 return timeout(future, 30 * 1000, () { | 129 return timeout(replyPort.first, 30 * 1000, () { |
| 149 throw 'Timed out waiting for test to complete.'; | 130 throw 'Timed out waiting for test to complete.'; |
| 150 }); | 131 }); |
| 151 }); | 132 }); |
| 152 } | 133 } |
| 153 | 134 |
| 154 /// Returns whether [results] (a test result map) describes a test run in which | 135 /// Returns whether [results] (a test result map) describes a test run in which |
| 155 /// an error occurred. | 136 /// an error occurred. |
| 156 bool _hasError(Map results) { | 137 bool _hasError(Map results) { |
| 157 return results['errors'] > 0 || results['uncaughtError'] != null || | 138 return results['errors'] > 0 || results['uncaughtError'] != null || |
| 158 (results['passed'] == 0 && results['failed'] == 0); | 139 (results['passed'] == 0 && results['failed'] == 0); |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 220 "uncaughtError": uncaughtError, | 201 "uncaughtError": uncaughtError, |
| 221 "results": results.map((testCase) => { | 202 "results": results.map((testCase) => { |
| 222 "description": testCase.description, | 203 "description": testCase.description, |
| 223 "message": testCase.message, | 204 "message": testCase.message, |
| 224 "result": testCase.result, | 205 "result": testCase.result, |
| 225 "stackTrace": testCase.stackTrace | 206 "stackTrace": testCase.stackTrace |
| 226 }).toList() | 207 }).toList() |
| 227 }); | 208 }); |
| 228 } | 209 } |
| 229 } | 210 } |
| OLD | NEW |