| 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 |