OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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 import 'dart:async'; | 5 import 'dart:async'; |
6 import 'dart:io'; | 6 import 'dart:io'; |
7 import 'dart:isolate'; | |
8 | 7 |
9 import 'package:analyzer/analyzer.dart' hide Configuration; | 8 import 'package:analyzer/analyzer.dart' hide Configuration; |
10 import 'package:async/async.dart'; | 9 import 'package:async/async.dart'; |
11 import 'package:path/path.dart' as p; | 10 import 'package:path/path.dart' as p; |
12 import 'package:stack_trace/stack_trace.dart'; | |
13 | 11 |
14 import '../backend/group.dart'; | 12 import '../backend/group.dart'; |
15 import '../backend/metadata.dart'; | 13 import '../backend/metadata.dart'; |
16 import '../backend/test.dart'; | |
17 import '../backend/test_platform.dart'; | 14 import '../backend/test_platform.dart'; |
18 import '../util/dart.dart' as dart; | |
19 import '../util/io.dart'; | 15 import '../util/io.dart'; |
20 import '../util/remote_exception.dart'; | |
21 import '../utils.dart'; | 16 import '../utils.dart'; |
22 import 'browser/server.dart'; | 17 import 'browser/server.dart'; |
23 import 'configuration.dart'; | 18 import 'configuration.dart'; |
24 import 'hack_load_vm_file_hook.dart'; | |
25 import 'load_exception.dart'; | 19 import 'load_exception.dart'; |
26 import 'load_suite.dart'; | 20 import 'load_suite.dart'; |
27 import 'parse_metadata.dart'; | 21 import 'parse_metadata.dart'; |
| 22 import 'plugin/environment.dart'; |
| 23 import 'plugin/platform.dart'; |
28 import 'runner_suite.dart'; | 24 import 'runner_suite.dart'; |
29 import 'vm/environment.dart'; | 25 import 'vm/platform.dart'; |
30 import 'vm/isolate_test.dart'; | |
31 | 26 |
32 /// A class for finding test files and loading them into a runnable form. | 27 /// A class for finding test files and loading them into a runnable form. |
33 class Loader { | 28 class Loader { |
34 /// The test runner configuration. | 29 /// The test runner configuration. |
35 final Configuration _config; | 30 final Configuration _config; |
36 | 31 |
37 /// The root directory that will be served for browser tests. | 32 /// The root directory that will be served for browser tests. |
38 final String _root; | 33 final String _root; |
39 | 34 |
40 /// All suites that have been created by the loader. | 35 /// All suites that have been created by the loader. |
41 final _suites = new Set<RunnerSuite>(); | 36 final _suites = new Set<RunnerSuite>(); |
42 | 37 |
| 38 /// Plugins for loading test suites for various platforms. |
| 39 /// |
| 40 /// This includes the built-in [VMPlatform] plugin. |
| 41 final _platformPlugins = <TestPlatform, PlatformPlugin>{}; |
| 42 |
43 /// The server that serves browser test pages. | 43 /// The server that serves browser test pages. |
44 /// | 44 /// |
45 /// This is lazily initialized the first time it's accessed. | 45 /// This is lazily initialized the first time it's accessed. |
46 Future<BrowserServer> get _browserServer { | 46 Future<BrowserServer> get _browserServer { |
47 return _browserServerMemo.runOnce(() { | 47 return _browserServerMemo.runOnce(() { |
48 return BrowserServer.start(_config, root: _root); | 48 return BrowserServer.start(_config, root: _root); |
49 }); | 49 }); |
50 } | 50 } |
51 final _browserServerMemo = new AsyncMemoizer<BrowserServer>(); | 51 final _browserServerMemo = new AsyncMemoizer<BrowserServer>(); |
52 | 52 |
53 /// The memoizer for running [close] exactly once. | 53 /// The memoizer for running [close] exactly once. |
54 final _closeMemo = new AsyncMemoizer(); | 54 final _closeMemo = new AsyncMemoizer(); |
55 | 55 |
56 /// Creates a new loader that loads tests on platforms defined in [_config]. | 56 /// Creates a new loader that loads tests on platforms defined in [_config]. |
57 /// | 57 /// |
58 /// [root] is the root directory that will be served for browser tests. It | 58 /// [root] is the root directory that will be served for browser tests. It |
59 /// defaults to the working directory. | 59 /// defaults to the working directory. |
60 Loader(this._config, {String root}) | 60 Loader(this._config, {String root}) |
61 : _root = root == null ? p.current : root; | 61 : _root = root == null ? p.current : root { |
| 62 registerPlatformPlugin(new VMPlatform(_config)); |
| 63 } |
| 64 |
| 65 /// Registers [plugin] as a plugin for the platforms it defines in |
| 66 /// [PlatformPlugin.platforms]. |
| 67 /// |
| 68 /// This overwrites previous plugins for those platforms. |
| 69 void registerPlatformPlugin(PlatformPlugin plugin) { |
| 70 for (var platform in plugin.platforms) { |
| 71 _platformPlugins[platform] = plugin; |
| 72 } |
| 73 } |
62 | 74 |
63 /// Loads all test suites in [dir]. | 75 /// Loads all test suites in [dir]. |
64 /// | 76 /// |
65 /// This will load tests from files that match the configuration's filename | 77 /// This will load tests from files that match the configuration's filename |
66 /// glob. Any tests that fail to load will be emitted as [LoadException]s. | 78 /// glob. Any tests that fail to load will be emitted as [LoadException]s. |
67 /// | 79 /// |
68 /// This emits [LoadSuite]s that must then be run to emit the actual | 80 /// This emits [LoadSuite]s that must then be run to emit the actual |
69 /// [RunnerSuite]s defined in the file. | 81 /// [RunnerSuite]s defined in the file. |
70 Stream<LoadSuite> loadDir(String dir) { | 82 Stream<LoadSuite> loadDir(String dir) { |
71 return mergeStreams(new Directory(dir).listSync(recursive: true) | 83 return mergeStreams(new Directory(dir).listSync(recursive: true) |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
112 } | 124 } |
113 | 125 |
114 for (var platform in _config.platforms) { | 126 for (var platform in _config.platforms) { |
115 if (!suiteMetadata.testOn.evaluate(platform, os: currentOS)) continue; | 127 if (!suiteMetadata.testOn.evaluate(platform, os: currentOS)) continue; |
116 | 128 |
117 var metadata = suiteMetadata.forPlatform(platform, os: currentOS); | 129 var metadata = suiteMetadata.forPlatform(platform, os: currentOS); |
118 | 130 |
119 // Don't load a skipped suite. | 131 // Don't load a skipped suite. |
120 if (metadata.skip) { | 132 if (metadata.skip) { |
121 yield new LoadSuite.forSuite(new RunnerSuite( | 133 yield new LoadSuite.forSuite(new RunnerSuite( |
122 const VMEnvironment(), | 134 const PluginEnvironment(), |
123 new Group.root([], metadata: metadata), | 135 new Group.root([], metadata: metadata), |
124 path: path, platform: platform)); | 136 path: path, platform: platform)); |
125 continue; | 137 continue; |
126 } | 138 } |
127 | 139 |
128 var name = (platform.isJS ? "compiling " : "loading ") + path; | 140 var name = (platform.isJS ? "compiling " : "loading ") + path; |
129 yield new LoadSuite(name, () { | 141 yield new LoadSuite(name, () async { |
130 return platform == TestPlatform.vm | 142 var plugin = _platformPlugins[platform]; |
131 ? _loadVmFile(path, metadata) | 143 |
132 : _loadBrowserFile(path, platform, metadata); | 144 if (plugin != null) { |
| 145 try { |
| 146 return await plugin.load(path, platform, metadata); |
| 147 } catch (error, stackTrace) { |
| 148 if (error is LoadException) rethrow; |
| 149 await new Future.error(new LoadException(path, error), stackTrace); |
| 150 } |
| 151 } |
| 152 |
| 153 assert(platform.isBrowser); |
| 154 return _loadBrowserFile(path, platform, metadata); |
133 }, path: path, platform: platform); | 155 }, path: path, platform: platform); |
134 } | 156 } |
135 } | 157 } |
136 | 158 |
137 /// Load the test suite at [path] in [platform]. | 159 /// Load the test suite at [path] in [platform]. |
138 /// | 160 /// |
139 /// [metadata] is the suite-level metadata for the test. | 161 /// [metadata] is the suite-level metadata for the test. |
140 Future<RunnerSuite> _loadBrowserFile(String path, TestPlatform platform, | 162 Future<RunnerSuite> _loadBrowserFile(String path, TestPlatform platform, |
141 Metadata metadata) async => | 163 Metadata metadata) async => |
142 (await _browserServer).loadSuite(path, platform, metadata); | 164 (await _browserServer).loadSuite(path, platform, metadata); |
143 | 165 |
144 /// Load the test suite at [path] in VM isolate. | |
145 /// | |
146 /// [metadata] is the suite-level metadata for the test. | |
147 Future<RunnerSuite> _loadVmFile(String path, Metadata metadata) async { | |
148 if (loadVMFileHook != null) { | |
149 var suite = await loadVMFileHook(path, metadata, _config); | |
150 _suites.add(suite); | |
151 return suite; | |
152 } | |
153 | |
154 var receivePort = new ReceivePort(); | |
155 | |
156 var isolate; | |
157 try { | |
158 if (_config.pubServeUrl != null) { | |
159 var url = _config.pubServeUrl.resolveUri( | |
160 p.toUri(p.relative(path, from: 'test') + '.vm_test.dart')); | |
161 | |
162 try { | |
163 isolate = await Isolate.spawnUri(url, [], { | |
164 'reply': receivePort.sendPort, | |
165 'metadata': metadata.serialize() | |
166 }, checked: true); | |
167 } on IsolateSpawnException catch (error) { | |
168 if (error.message.contains("OS Error: Connection refused") || | |
169 error.message.contains("The remote computer refused")) { | |
170 throw new LoadException(path, | |
171 "Error getting $url: Connection refused\n" | |
172 'Make sure "pub serve" is running.'); | |
173 } else if (error.message.contains("404 Not Found")) { | |
174 throw new LoadException(path, | |
175 "Error getting $url: 404 Not Found\n" | |
176 'Make sure "pub serve" is serving the test/ directory.'); | |
177 } | |
178 | |
179 throw new LoadException(path, error); | |
180 } | |
181 } else { | |
182 isolate = await dart.runInIsolate(''' | |
183 import "package:test/src/backend/metadata.dart"; | |
184 import "package:test/src/runner/vm/isolate_listener.dart"; | |
185 | |
186 import "${p.toUri(p.absolute(path))}" as test; | |
187 | |
188 void main(_, Map message) { | |
189 var sendPort = message['reply']; | |
190 var metadata = new Metadata.deserialize(message['metadata']); | |
191 IsolateListener.start(sendPort, metadata, () => test.main); | |
192 } | |
193 ''', { | |
194 'reply': receivePort.sendPort, | |
195 'metadata': metadata.serialize() | |
196 }, packageRoot: p.toUri(_config.packageRoot), checked: true); | |
197 } | |
198 } catch (error, stackTrace) { | |
199 receivePort.close(); | |
200 if (error is LoadException) rethrow; | |
201 await new Future.error(new LoadException(path, error), stackTrace); | |
202 } | |
203 | |
204 var completer = new Completer(); | |
205 | |
206 var subscription = receivePort.listen((response) { | |
207 if (response["type"] == "print") { | |
208 print(response["line"]); | |
209 } else if (response["type"] == "loadException") { | |
210 isolate.kill(); | |
211 completer.completeError( | |
212 new LoadException(path, response["message"]), | |
213 new Trace.current()); | |
214 } else if (response["type"] == "error") { | |
215 isolate.kill(); | |
216 var asyncError = RemoteException.deserialize(response["error"]); | |
217 completer.completeError( | |
218 new LoadException(path, asyncError.error), | |
219 asyncError.stackTrace); | |
220 } else { | |
221 assert(response["type"] == "success"); | |
222 completer.complete(response["root"]); | |
223 } | |
224 }); | |
225 | |
226 try { | |
227 var suite = new RunnerSuite( | |
228 const VMEnvironment(), | |
229 _deserializeGroup(await completer.future), | |
230 path: path, | |
231 platform: TestPlatform.vm, | |
232 os: currentOS, | |
233 onClose: isolate.kill); | |
234 _suites.add(suite); | |
235 return suite; | |
236 } finally { | |
237 subscription.cancel(); | |
238 } | |
239 } | |
240 | |
241 /// Deserializes [group] into a concrete [Group] class. | |
242 Group _deserializeGroup(Map group) { | |
243 var metadata = new Metadata.deserialize(group['metadata']); | |
244 return new Group(group['name'], group['entries'].map((entry) { | |
245 if (entry['type'] == 'group') return _deserializeGroup(entry); | |
246 return _deserializeTest(entry); | |
247 }), | |
248 metadata: metadata, | |
249 setUpAll: _deserializeTest(group['setUpAll']), | |
250 tearDownAll: _deserializeTest(group['tearDownAll'])); | |
251 } | |
252 | |
253 /// Deserializes [test] into a concrete [Test] class. | |
254 /// | |
255 /// Returns `null` if [test] is `null`. | |
256 Test _deserializeTest(Map test) { | |
257 if (test == null) return null; | |
258 | |
259 var metadata = new Metadata.deserialize(test['metadata']); | |
260 return new IsolateTest(test['name'], metadata, test['sendPort']); | |
261 } | |
262 | |
263 /// Close all the browsers that the loader currently has open. | 166 /// Close all the browsers that the loader currently has open. |
264 /// | 167 /// |
265 /// Note that this doesn't close the loader itself. Browser tests can still be | 168 /// Note that this doesn't close the loader itself. Browser tests can still be |
266 /// loaded, they'll just spawn new browsers. | 169 /// loaded, they'll just spawn new browsers. |
267 Future closeBrowsers() async { | 170 Future closeBrowsers() async { |
268 if (!_browserServerMemo.hasRun) return; | 171 if (!_browserServerMemo.hasRun) return; |
269 await (await _browserServer).closeBrowsers(); | 172 await (await _browserServer).closeBrowsers(); |
270 } | 173 } |
271 | 174 |
272 /// Closes the loader and releases all resources allocated by it. | 175 /// Closes the loader and releases all resources allocated by it. |
273 Future close() { | 176 Future close() { |
274 return _closeMemo.runOnce(() async { | 177 return _closeMemo.runOnce(() async { |
275 await Future.wait(_suites.map((suite) => suite.close())); | 178 await Future.wait(_suites.map((suite) => suite.close())); |
276 _suites.clear(); | 179 _suites.clear(); |
277 | 180 |
278 if (!_browserServerMemo.hasRun) return; | 181 if (!_browserServerMemo.hasRun) return; |
279 await (await _browserServer).close(); | 182 await (await _browserServer).close(); |
280 }); | 183 }); |
281 } | 184 } |
282 } | 185 } |
OLD | NEW |