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 | 7 |
8 import 'package:analyzer/analyzer.dart' hide Configuration; | 8 import 'package:analyzer/analyzer.dart' hide Configuration; |
9 import 'package:async/async.dart'; | 9 import 'package:async/async.dart'; |
10 import 'package:path/path.dart' as p; | 10 import 'package:path/path.dart' as p; |
11 | 11 |
12 import '../backend/group.dart'; | 12 import '../backend/group.dart'; |
13 import '../backend/metadata.dart'; | 13 import '../backend/metadata.dart'; |
14 import '../backend/test_platform.dart'; | 14 import '../backend/test_platform.dart'; |
15 import '../util/io.dart'; | 15 import '../util/io.dart'; |
16 import '../utils.dart'; | 16 import '../utils.dart'; |
17 import 'browser/server.dart'; | 17 import 'browser/platform.dart'; |
18 import 'configuration.dart'; | 18 import 'configuration.dart'; |
19 import 'load_exception.dart'; | 19 import 'load_exception.dart'; |
20 import 'load_suite.dart'; | 20 import 'load_suite.dart'; |
21 import 'parse_metadata.dart'; | 21 import 'parse_metadata.dart'; |
22 import 'plugin/environment.dart'; | 22 import 'plugin/environment.dart'; |
23 import 'plugin/platform.dart'; | 23 import 'plugin/platform.dart'; |
24 import 'runner_suite.dart'; | 24 import 'runner_suite.dart'; |
25 import 'vm/platform.dart'; | 25 import 'vm/platform.dart'; |
26 | 26 |
27 /// 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. |
28 class Loader { | 28 class Loader { |
29 /// The test runner configuration. | 29 /// The test runner configuration. |
30 final Configuration _config; | 30 final Configuration _config; |
31 | 31 |
32 /// The root directory that will be served for browser tests. | 32 /// The root directory that will be served for browser tests. |
33 final String _root; | 33 final String _root; |
34 | 34 |
35 /// All suites that have been created by the loader. | 35 /// All suites that have been created by the loader. |
36 final _suites = new Set<RunnerSuite>(); | 36 final _suites = new Set<RunnerSuite>(); |
37 | 37 |
38 /// Plugins for loading test suites for various platforms. | 38 /// Memoizers for platform plugins, indexed by the platforms they support. |
| 39 final _platformPlugins = <TestPlatform, AsyncMemoizer<PlatformPlugin>>{}; |
| 40 |
| 41 /// The functions to use to load [_platformPlugins]. |
39 /// | 42 /// |
40 /// This includes the built-in [VMPlatform] plugin. | 43 /// These are passed to the plugins' async memoizers when a plugin is needed. |
41 final _platformPlugins = <TestPlatform, PlatformPlugin>{}; | 44 final _platformCallbacks = <TestPlatform, AsyncFunction>{}; |
42 | |
43 /// The server that serves browser test pages. | |
44 /// | |
45 /// This is lazily initialized the first time it's accessed. | |
46 Future<BrowserServer> get _browserServer { | |
47 return _browserServerMemo.runOnce(() { | |
48 return BrowserServer.start(_config, root: _root); | |
49 }); | |
50 } | |
51 final _browserServerMemo = new AsyncMemoizer<BrowserServer>(); | |
52 | |
53 /// The memoizer for running [close] exactly once. | |
54 final _closeMemo = new AsyncMemoizer(); | |
55 | 45 |
56 /// Creates a new loader that loads tests on platforms defined in [_config]. | 46 /// Creates a new loader that loads tests on platforms defined in [_config]. |
57 /// | 47 /// |
58 /// [root] is the root directory that will be served for browser tests. It | 48 /// [root] is the root directory that will be served for browser tests. It |
59 /// defaults to the working directory. | 49 /// defaults to the working directory. |
60 Loader(this._config, {String root}) | 50 Loader(this._config, {String root}) |
61 : _root = root == null ? p.current : root { | 51 : _root = root == null ? p.current : root { |
62 registerPlatformPlugin(new VMPlatform(_config)); | 52 registerPlatformPlugin([TestPlatform.vm], () => new VMPlatform(_config)); |
| 53 registerPlatformPlugin([ |
| 54 TestPlatform.dartium, |
| 55 TestPlatform.contentShell, |
| 56 TestPlatform.chrome, |
| 57 TestPlatform.phantomJS, |
| 58 TestPlatform.firefox, |
| 59 TestPlatform.safari, |
| 60 TestPlatform.internetExplorer |
| 61 ], () => BrowserPlatform.start(_config, root: root)); |
63 } | 62 } |
64 | 63 |
65 /// Registers [plugin] as a plugin for the platforms it defines in | 64 /// Registers a [PlatformPlugin] for [platforms]. |
66 /// [PlatformPlugin.platforms]. | 65 /// |
| 66 /// When the runner first requests that a suite be loaded for one of the given |
| 67 /// platforms, this will call [getPlugin] to load the platform plugin. It may |
| 68 /// return either a [PlatformPlugin] or a [Future<PlatformPlugin>]. That |
| 69 /// plugin is then preserved and used to load all suites for all matching |
| 70 /// platforms. |
67 /// | 71 /// |
68 /// This overwrites previous plugins for those platforms. | 72 /// This overwrites previous plugins for those platforms. |
69 void registerPlatformPlugin(PlatformPlugin plugin) { | 73 void registerPlatformPlugin(Iterable<TestPlatform> platforms, getPlugin()) { |
70 for (var platform in plugin.platforms) { | 74 var memoizer = new AsyncMemoizer(); |
71 _platformPlugins[platform] = plugin; | 75 for (var platform in platforms) { |
| 76 _platformPlugins[platform] = memoizer; |
| 77 _platformCallbacks[platform] = getPlugin; |
72 } | 78 } |
73 } | 79 } |
74 | 80 |
75 /// Loads all test suites in [dir]. | 81 /// Loads all test suites in [dir]. |
76 /// | 82 /// |
77 /// This will load tests from files that match the configuration's filename | 83 /// This will load tests from files that match the configuration's filename |
78 /// glob. Any tests that fail to load will be emitted as [LoadException]s. | 84 /// glob. Any tests that fail to load will be emitted as [LoadException]s. |
79 /// | 85 /// |
80 /// This emits [LoadSuite]s that must then be run to emit the actual | 86 /// This emits [LoadSuite]s that must then be run to emit the actual |
81 /// [RunnerSuite]s defined in the file. | 87 /// [RunnerSuite]s defined in the file. |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
132 if (metadata.skip) { | 138 if (metadata.skip) { |
133 yield new LoadSuite.forSuite(new RunnerSuite( | 139 yield new LoadSuite.forSuite(new RunnerSuite( |
134 const PluginEnvironment(), | 140 const PluginEnvironment(), |
135 new Group.root([], metadata: metadata), | 141 new Group.root([], metadata: metadata), |
136 path: path, platform: platform)); | 142 path: path, platform: platform)); |
137 continue; | 143 continue; |
138 } | 144 } |
139 | 145 |
140 var name = (platform.isJS ? "compiling " : "loading ") + path; | 146 var name = (platform.isJS ? "compiling " : "loading ") + path; |
141 yield new LoadSuite(name, () async { | 147 yield new LoadSuite(name, () async { |
142 var plugin = _platformPlugins[platform]; | 148 var memo = _platformPlugins[platform]; |
143 | 149 |
144 if (plugin != null) { | 150 try { |
145 try { | 151 var plugin = await memo.runOnce(_platformCallbacks[platform]); |
146 return await plugin.load(path, platform, metadata); | 152 var suite = await plugin.load(path, platform, metadata); |
147 } catch (error, stackTrace) { | 153 _suites.add(suite); |
148 if (error is LoadException) rethrow; | 154 return suite; |
149 await new Future.error(new LoadException(path, error), stackTrace); | 155 } catch (error, stackTrace) { |
150 } | 156 if (error is LoadException) rethrow; |
| 157 await new Future.error(new LoadException(path, error), stackTrace); |
151 } | 158 } |
152 | |
153 assert(platform.isBrowser); | |
154 return _loadBrowserFile(path, platform, metadata); | |
155 }, path: path, platform: platform); | 159 }, path: path, platform: platform); |
156 } | 160 } |
157 } | 161 } |
158 | 162 |
159 /// Load the test suite at [path] in [platform]. | 163 Future closeEphemeral() async { |
160 /// | 164 await Future.wait(_platformPlugins.values.map((memo) async { |
161 /// [metadata] is the suite-level metadata for the test. | 165 if (!memo.hasRun) return; |
162 Future<RunnerSuite> _loadBrowserFile(String path, TestPlatform platform, | 166 await (await memo.future).closeEphemeral(); |
163 Metadata metadata) async => | 167 })); |
164 (await _browserServer).loadSuite(path, platform, metadata); | |
165 | |
166 /// Close all the browsers that the loader currently has open. | |
167 /// | |
168 /// Note that this doesn't close the loader itself. Browser tests can still be | |
169 /// loaded, they'll just spawn new browsers. | |
170 Future closeBrowsers() async { | |
171 if (!_browserServerMemo.hasRun) return; | |
172 await (await _browserServer).closeBrowsers(); | |
173 } | 168 } |
174 | 169 |
175 /// Closes the loader and releases all resources allocated by it. | 170 /// Closes the loader and releases all resources allocated by it. |
176 Future close() { | 171 Future close() => _closeMemo.runOnce(() async { |
177 return _closeMemo.runOnce(() async { | 172 await Future.wait([ |
178 await Future.wait(_suites.map((suite) => suite.close())); | 173 Future.wait(_platformPlugins.values.map((memo) async { |
179 _suites.clear(); | 174 if (!memo.hasRun) return; |
| 175 await (await memo.future).close(); |
| 176 })), |
| 177 Future.wait(_suites.map((suite) => suite.close())) |
| 178 ]); |
180 | 179 |
181 if (!_browserServerMemo.hasRun) return; | 180 _platformPlugins.clear(); |
182 await (await _browserServer).close(); | 181 _platformCallbacks.clear(); |
183 }); | 182 _suites.clear(); |
184 } | 183 }); |
| 184 final _closeMemo = new AsyncMemoizer(); |
185 } | 185 } |
OLD | NEW |