Chromium Code Reviews| 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 library barback.test.utils; | 5 library barback.test.utils; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | |
| 8 | 9 |
| 9 import 'package:barback/barback.dart'; | 10 import 'package:barback/barback.dart'; |
| 10 import 'package:barback/src/asset_graph.dart'; | 11 import 'package:barback/src/asset_graph.dart'; |
| 11 import 'package:pathos/path.dart' as pathos; | 12 import 'package:pathos/path.dart' as pathos; |
| 12 import 'package:scheduled_test/scheduled_test.dart'; | 13 import 'package:scheduled_test/scheduled_test.dart'; |
| 13 | 14 |
| 14 // TODO(rnystrom): Get rid of this or find a better path for it. | 15 // TODO(rnystrom): Get rid of this or find a better path for it. |
| 15 import '../../../sdk/lib/_internal/pub/test/command_line_config.dart'; | 16 import '../../../sdk/lib/_internal/pub/test/command_line_config.dart'; |
| 16 | 17 |
| 17 var _configured = false; | 18 var _configured = false; |
| 18 | 19 |
| 20 MockProvider _provider; | |
| 21 AssetGraph _graph; | |
| 22 | |
| 23 /// [BuildResult]s that have been output by the graph before an expectation has | |
| 24 /// consumed them. | |
| 25 /// | |
| 26 /// Since [AssetGraph] starts building immediately in the background, results | |
| 27 /// may start streaming before we've set an explicit expectation. When that | |
| 28 /// occurs, we just queue them up here. | |
| 29 final _buildResults = new Queue<BuildResult>(); | |
| 30 | |
| 31 /// The expectations we have on upcoming [BuildResult]s. | |
| 32 final _buildExpectations = new Queue<Completer<BuildResult>>(); | |
| 33 | |
| 19 void initConfig() { | 34 void initConfig() { |
| 20 if (_configured) return; | 35 if (_configured) return; |
| 21 _configured = true; | 36 _configured = true; |
| 22 unittestConfiguration = new CommandLineConfiguration(); | 37 unittestConfiguration = new CommandLineConfiguration(); |
| 23 } | 38 } |
| 24 | 39 |
| 25 /// Expects that [graph] will return an asset matching [name] and [contents]. | 40 /// Creates a new [AssetProvider] and [AssetGraph] with the given [assets] and |
| 26 void expectAsset(AssetGraph graph, String name, [String contents]) { | 41 /// [transformers]. |
| 42 /// | |
| 43 /// This graph is used internally by most of the other functions in this | |
| 44 /// library so you must call it in the test before calling any of the other | |
| 45 /// functions. These other functions are automatically scheduled, with the | |
|
nweiz
2013/06/27 00:19:25
"These other functions" is unclear, especially sin
Bob Nystrom
2013/06/27 17:55:14
Done.
| |
| 46 /// exception of [updateSources] and [removeSources]. | |
| 47 /// | |
| 48 /// [assets] may either be an [Iterable] or a [Map]. If an [Iterable], each | |
|
nweiz
2013/06/27 00:19:25
"If an" -> "If it's an"
Bob Nystrom
2013/06/27 17:55:14
Done.
| |
| 49 /// element may either be an [AssetId] or a string that can be parsed to one. | |
| 50 /// If it's a [Map], each key should be a string that can be parsed to an | |
| 51 /// [AssetId] and the value is a string defining the contents of that asset. | |
|
nweiz
2013/06/27 00:19:25
"value is" -> "value should be"
Bob Nystrom
2013/06/27 17:55:14
Done.
| |
| 52 void initGraph([assets, Iterable<Iterable<Transformer>> transformers]) { | |
| 53 if (assets == null) assets = []; | |
| 54 if (transformers == null) transformers = []; | |
| 55 | |
| 56 _provider = new MockProvider(assets); | |
| 57 _graph = new AssetGraph(_provider, transformers); | |
| 58 | |
| 59 _graph.results.listen(wrapAsync((result) { | |
| 60 if (_buildExpectations.isEmpty) { | |
| 61 // We aren't waiting for a result yet, so just queue it up for later. | |
| 62 _buildResults.add(result); | |
| 63 } else { | |
| 64 // See if it meets the expectation. | |
| 65 _buildExpectations.removeFirst().complete(result); | |
| 66 } | |
| 67 })); | |
| 68 | |
| 69 currentSchedule.onComplete.schedule(() { | |
| 70 // Discard any unused results. It's OK for a test to not care about some | |
| 71 // build results. | |
| 72 _buildResults.clear(); | |
| 73 }, "clear unused build results"); | |
| 74 | |
| 75 // This library should ensure that you can't move to the next test until all | |
| 76 // build expectations are processed. | |
| 77 assert(_buildExpectations.isEmpty); | |
| 78 } | |
| 79 | |
| 80 /// Updates [assets] in the current [AssetProvider]. | |
| 81 /// | |
| 82 /// Each item in the list may either be an [AssetId] or a string that can be | |
| 83 /// parsed as one. Note that this method is not automatically scheduled, so you | |
| 84 /// will typically wrap it in a call to [schedule] yourself. | |
|
nweiz
2013/06/27 00:19:25
"you will typically wrap it" is confusing. Most of
Bob Nystrom
2013/06/27 17:55:14
Done.
| |
| 85 /// | |
| 86 void updateSources(Iterable assets) { | |
| 87 // Allow strings as asset IDs. | |
| 88 assets = assets.map((asset) { | |
| 89 if (asset is String) return new AssetId.parse(asset); | |
| 90 return asset; | |
| 91 }); | |
| 92 | |
| 93 _graph.updateSources(assets); | |
| 94 } | |
| 95 | |
| 96 /// Removes [assets] from the current [AssetProvider]. | |
| 97 /// | |
| 98 /// Each item in the list may either be an [AssetId] or a string that can be | |
| 99 /// parsed as one. Note that this method is not automatically scheduled, so you | |
| 100 /// will typically wrap it in a call to [schedule] yourself. | |
| 101 void removeSources(Iterable assets) { | |
| 102 // Allow strings as asset IDs. | |
| 103 assets = assets.map((asset) { | |
| 104 if (asset is String) return new AssetId.parse(asset); | |
| 105 return asset; | |
| 106 }); | |
| 107 | |
| 108 _graph.removeSources(assets); | |
| 109 } | |
| 110 | |
| 111 /// Changes the contents of an asset identified by [name] to [contents]. | |
| 112 /// | |
| 113 /// Does not update it in the graph. | |
| 114 void modifyAsset(String name, String contents) { | |
| 115 schedule(() { | |
| 116 _provider._modifyAsset(name, contents); | |
| 117 }, "modify asset $name"); | |
| 118 } | |
| 119 | |
| 120 /// Pauses the internally created [AssetProvider]. All asset requests that the | |
|
nweiz
2013/06/27 00:19:25
Paragraph break
Bob Nystrom
2013/06/27 17:55:14
Done.
| |
| 121 /// [AssetGraph] makes to the provider after this will not complete until | |
| 122 /// [resumeProvider] is called. | |
| 123 void pauseProvider() { | |
| 124 schedule(() =>_provider._pause(), "resume provider"); | |
|
nweiz
2013/06/27 00:19:25
"resume" -> "pause"
Bob Nystrom
2013/06/27 17:55:14
Done.
| |
| 125 } | |
| 126 | |
| 127 /// Unpauses the provider after a call to [pauseProvider] and allows all | |
| 128 /// pending asset loads to finish. | |
| 129 void resumeProvider() { | |
| 130 schedule(() => _provider._resume(), "pause provider"); | |
|
nweiz
2013/06/27 00:19:25
"pause" -> "resume"
Bob Nystrom
2013/06/27 17:55:14
Done.
| |
| 131 } | |
| 132 | |
| 133 /// Waits for the next [BuildResult] to be emitted and asserts that it is a | |
| 134 /// build success. | |
| 135 void buildShouldSucceed() { | |
| 136 schedule(() { | |
| 137 return _getNextBuildResult().then((result) { | |
| 138 expect(result.succeeded, isTrue); | |
| 139 }); | |
| 140 }, "wait for build to succeed"); | |
| 141 } | |
| 142 | |
| 143 /// Waits for the next [BuildResult] to be emitted and asserts that it is a | |
| 144 /// build failure. Invokes [callback] with the error (not the result) so that | |
| 145 /// it can provide more precise expectations. | |
| 146 void buildShouldFail(void callback(error)) { | |
| 147 schedule(() { | |
| 148 return _getNextBuildResult().then((result) { | |
| 149 expect(result.succeeded, isFalse); | |
| 150 callback(result.error); | |
| 151 }); | |
| 152 }, "wait for build error"); | |
| 153 } | |
| 154 | |
| 155 Future<BuildResult> _getNextBuildResult() { | |
| 156 if (_buildResults.isNotEmpty) { | |
| 157 return new Future.value(_buildResults.removeFirst()); | |
| 158 } | |
| 159 | |
| 160 // We don't have any results yet, so enqueue the expectation. | |
| 161 var completer = new Completer<BuildResult>(); | |
| 162 _buildExpectations.add(completer); | |
| 163 return completer.future; | |
| 164 } | |
| 165 | |
| 166 /// Expects that the graph will deliver an asset matching [name] and [contents]. | |
| 167 /// | |
| 168 /// If [contents] is omitted, defaults to the asset's filename without an | |
| 169 /// extension (which is the same default that [initGraph] uses). | |
| 170 void expectAsset(String name, [String contents]) { | |
| 27 var id = new AssetId.parse(name); | 171 var id = new AssetId.parse(name); |
| 28 | 172 |
| 29 if (contents == null) { | 173 if (contents == null) { |
| 30 contents = pathos.basenameWithoutExtension(id.path); | 174 contents = pathos.basenameWithoutExtension(id.path); |
| 31 } | 175 } |
| 32 | 176 |
| 33 schedule(() { | 177 schedule(() { |
| 34 return graph.getAssetById(id).then((asset) { | 178 return _graph.getAssetById(id).then((asset) { |
| 35 // TODO(rnystrom): Make an actual Matcher class for this. | 179 // TODO(rnystrom): Make an actual Matcher class for this. |
| 36 expect(asset, new isInstanceOf<MockAsset>()); | 180 expect(asset, new isInstanceOf<MockAsset>()); |
| 37 expect(asset._id.package, equals(id.package)); | 181 expect(asset._id.package, equals(id.package)); |
| 38 expect(asset._id.path, equals(id.path)); | 182 expect(asset._id.path, equals(id.path)); |
| 39 expect(asset._contents, equals(contents)); | 183 expect(asset._contents, equals(contents)); |
| 40 }); | 184 }); |
| 41 }, "get asset $name"); | 185 }, "get asset $name"); |
| 42 } | 186 } |
| 43 | 187 |
| 44 /// Expects that [graph] will not find an asset matching [name]. | 188 /// Expects that the graph will not find an asset matching [name]. |
| 45 void expectNoAsset(AssetGraph graph, String name) { | 189 void expectNoAsset(String name) { |
| 46 var id = new AssetId.parse(name); | 190 var id = new AssetId.parse(name); |
| 47 | 191 |
| 48 // Make sure the future gets the error. | 192 // Make sure the future gets the error. |
| 49 schedule(() { | 193 schedule(() { |
| 50 return graph.getAssetById(id).then((asset) { | 194 return _graph.getAssetById(id).then((asset) { |
| 51 fail("Should have thrown error but got $asset."); | 195 fail("Should have thrown error but got $asset."); |
| 52 }).catchError((error) { | 196 }).catchError((error) { |
| 53 expect(error, new isInstanceOf<AssetNotFoundException>()); | 197 expect(error, new isInstanceOf<AssetNotFoundException>()); |
| 54 expect(error.id, equals(id)); | 198 expect(error.id, equals(id)); |
| 55 }); | 199 }); |
| 56 }, "get asset $name"); | 200 }, "get asset $name"); |
| 57 } | 201 } |
| 58 | 202 |
| 59 /// Expects that [graph] will have an output file collision error on an asset | 203 /// Expects that the graph will have an output file collision error on an asset |
| 60 /// matching [name]. | 204 /// matching [name]. |
| 61 Future expectCollision(AssetGraph graph, String name) { | 205 Future expectCollision(String name) { |
| 62 var id = new AssetId.parse(name); | 206 var id = new AssetId.parse(name); |
| 63 return schedule(() { | 207 return schedule(() { |
| 64 return graph.results.first.then((result) { | 208 return _graph.results.first.then((result) { |
| 65 expect(result.error, new isInstanceOf<AssetCollisionException>()); | 209 expect(result.error, new isInstanceOf<AssetCollisionException>()); |
| 66 expect(result.error.id, equals(id)); | 210 expect(result.error.id, equals(id)); |
| 67 }); | 211 }); |
| 68 }, "get collision on $name"); | 212 }, "get collision on $name"); |
| 69 } | 213 } |
| 70 | 214 |
| 71 /// Expects that [graph] will have an error on an asset matching [name] for | 215 /// Expects that [graph] will have an error on an asset matching [name] for |
| 72 /// missing [input]. | 216 /// missing [input]. |
| 73 Future expectMissingInput(AssetGraph graph, String name, String input) { | 217 Future expectMissingInput(AssetGraph graph, String name, String input) { |
| 74 var missing = new AssetId.parse(input); | 218 var missing = new AssetId.parse(input); |
| 75 | 219 |
| 76 // Make sure the future gets the error. | 220 // Make sure the future gets the error. |
| 77 schedule(() { | 221 schedule(() { |
| 78 return graph.getAssetById(new AssetId.parse(name)).then((asset) { | 222 return graph.getAssetById(new AssetId.parse(name)).then((asset) { |
| 79 fail("Should have thrown error but got $asset."); | 223 fail("Should have thrown error but got $asset."); |
| 80 }).catchError((error) { | 224 }).catchError((error) { |
| 81 expect(error, new isInstanceOf<MissingInputException>()); | 225 expect(error, new isInstanceOf<MissingInputException>()); |
| 82 expect(error.id, equals(missing)); | 226 expect(error.id, equals(missing)); |
| 83 }); | 227 }); |
| 84 }, "get missing input on $name"); | 228 }, "get missing input on $name"); |
| 85 } | 229 } |
| 86 | 230 |
| 87 /// An [AssetProvider] that provides the given set of assets. | 231 /// An [AssetProvider] that provides the given set of assets. |
| 88 class MockProvider implements AssetProvider { | 232 class MockProvider implements AssetProvider { |
| 89 Iterable<String> get packages => _packages.keys; | 233 Iterable<String> get packages => _packages.keys; |
| 90 | 234 |
| 91 final _packages = new Map<String, List<MockAsset>>(); | 235 final _packages = new Map<String, List<MockAsset>>(); |
| 92 | 236 |
| 93 /// The completer that [getAsset()] is waiting on to complete. | 237 /// The completer that [getAsset()] is waiting on to complete when paused. |
| 94 /// | 238 /// |
| 95 /// If `null` it will return the asset immediately. | 239 /// If `null` it will return the asset immediately. |
| 96 Completer _wait; | 240 Completer _pauseCompleter; |
| 97 | 241 |
| 98 /// Tells the provider to wait during [getAsset] until [complete()] | 242 /// Tells the provider to wait during [getAsset] until [complete()] |
| 99 /// is called. | 243 /// is called. |
| 100 /// | 244 /// |
| 101 /// Lets you test the asynchronous behavior of loading. | 245 /// Lets you test the asynchronous behavior of loading. |
| 102 void wait() { | 246 void _pause() { |
| 103 _wait = new Completer(); | 247 _pauseCompleter = new Completer(); |
| 104 } | 248 } |
| 105 | 249 |
| 106 void complete() { | 250 void _resume() { |
| 107 _wait.complete(); | 251 _pauseCompleter.complete(); |
| 108 _wait = null; | 252 _pauseCompleter = null; |
| 109 } | 253 } |
| 110 | 254 |
| 111 MockProvider(assets) { | 255 MockProvider(assets) { |
| 112 if (assets is Map) { | 256 if (assets is Map) { |
| 113 assets.forEach((asset, contents) { | 257 assets.forEach((asset, contents) { |
| 114 var id = new AssetId.parse(asset); | 258 var id = new AssetId.parse(asset); |
| 115 var package = _packages.putIfAbsent(id.package, () => []); | 259 var package = _packages.putIfAbsent(id.package, () => []); |
| 116 package.add(new MockAsset(id, contents)); | 260 package.add(new MockAsset(id, contents)); |
| 117 }); | 261 }); |
| 118 } else if (assets is Iterable) { | 262 } else if (assets is Iterable) { |
| 119 for (var asset in assets) { | 263 for (var asset in assets) { |
| 120 var id = new AssetId.parse(asset); | 264 var id = new AssetId.parse(asset); |
| 121 var package = _packages.putIfAbsent(id.package, () => []); | 265 var package = _packages.putIfAbsent(id.package, () => []); |
| 122 var contents = pathos.basenameWithoutExtension(id.path); | 266 var contents = pathos.basenameWithoutExtension(id.path); |
| 123 package.add(new MockAsset(id, contents)); | 267 package.add(new MockAsset(id, contents)); |
| 124 } | 268 } |
| 125 } | 269 } |
| 126 } | 270 } |
| 127 | 271 |
| 128 void modifyAsset(String name, String contents) { | 272 void _modifyAsset(String name, String contents) { |
| 129 var id = new AssetId.parse(name); | 273 var id = new AssetId.parse(name); |
| 130 var asset = _packages[id.package].firstWhere((a) => a._id == id); | 274 var asset = _packages[id.package].firstWhere((a) => a._id == id); |
| 131 asset._contents = contents; | 275 asset._contents = contents; |
| 132 } | 276 } |
| 133 | 277 |
| 134 List<AssetId> listAssets(String package, {String within}) { | 278 List<AssetId> listAssets(String package, {String within}) { |
| 135 if (within != null) { | 279 if (within != null) { |
| 136 throw new UnimplementedError("Doesn't handle 'within' yet."); | 280 throw new UnimplementedError("Doesn't handle 'within' yet."); |
| 137 } | 281 } |
| 138 | 282 |
| 139 return _packages[package].map((asset) => asset.id); | 283 return _packages[package].map((asset) => asset.id); |
| 140 } | 284 } |
| 141 | 285 |
| 142 Future<Asset> getAsset(AssetId id) { | 286 Future<Asset> getAsset(AssetId id) { |
| 143 var future; | 287 var future; |
| 144 if (_wait != null) { | 288 if (_pauseCompleter != null) { |
| 145 future = _wait.future; | 289 future = _pauseCompleter.future; |
| 146 } else { | 290 } else { |
| 147 future = new Future.value(); | 291 future = new Future.value(); |
| 148 } | 292 } |
| 149 | 293 |
| 150 return future.then((_) { | 294 return future.then((_) { |
| 151 var package = _packages[id.package]; | 295 var package = _packages[id.package]; |
| 152 if (package == null) throw new AssetNotFoundException(id); | 296 if (package == null) throw new AssetNotFoundException(id); |
| 153 | 297 |
| 154 return package.firstWhere((asset) => asset._id == id, | 298 return package.firstWhere((asset) => asset._id == id, |
| 155 orElse: () => throw new AssetNotFoundException(id)); | 299 orElse: () => throw new AssetNotFoundException(id)); |
| (...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 333 | 477 |
| 334 MockAsset(this._id, this._contents); | 478 MockAsset(this._id, this._contents); |
| 335 | 479 |
| 336 String readAsString() => _contents; | 480 String readAsString() => _contents; |
| 337 Stream<List<int>> read() => throw new UnimplementedError(); | 481 Stream<List<int>> read() => throw new UnimplementedError(); |
| 338 | 482 |
| 339 serialize() => throw new UnimplementedError(); | 483 serialize() => throw new UnimplementedError(); |
| 340 | 484 |
| 341 String toString() => "MockAsset $_id $_contents"; | 485 String toString() => "MockAsset $_id $_contents"; |
| 342 } | 486 } |
| OLD | NEW |