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>>(); | |
|
nweiz
2013/06/25 22:37:56
I really don't like this [_buildResults]/[_buildEx
Bob Nystrom
2013/06/26 20:44:45
Maybe the results stream shouldn't be broadcast by
nweiz
2013/06/27 00:19:25
You're being fuzzy about what "in the background"
Bob Nystrom
2013/06/27 17:55:14
That's correct. It always pumps the event loop. Ac
nweiz
2013/06/27 21:08:46
I don't understand why they're blocked on one anot
Bob Nystrom
2013/06/27 22:23:07
Done!
| |
| 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. | |
|
nweiz
2013/06/25 22:37:56
Mention that all operations on the graph are sched
Bob Nystrom
2013/06/26 20:44:45
Done.
| |
| 46 AssetGraph initGraph([assets, Iterable<Iterable<Transformer>> transformers]) { | |
|
nweiz
2013/06/25 22:37:56
Since [assets] isn't type-annotated, the documenta
Bob Nystrom
2013/06/26 20:44:45
Done.
| |
| 47 if (assets == null) assets = []; | |
| 48 if (transformers == null) transformers = []; | |
| 49 | |
| 50 _provider = new MockProvider(assets); | |
| 51 _graph = new AssetGraph(_provider, transformers); | |
| 52 | |
| 53 _graph.results.listen(wrapAsync((result) { | |
| 54 if (_buildExpectations.isEmpty) { | |
| 55 // We aren't waiting for a result yet, so just queue it up for later. | |
| 56 _buildResults.add(result); | |
| 57 } else { | |
| 58 // See if it meets the expectation. | |
| 59 _buildExpectations.removeFirst().complete(result); | |
| 60 } | |
| 61 })); | |
| 62 | |
| 63 // Discard any previous builds from earlier tests. It's OK for a test to not | |
| 64 // care about some build results. | |
| 65 _buildResults.clear(); | |
|
nweiz
2013/06/25 22:37:56
Earlier tests should clean up their own mess. This
Bob Nystrom
2013/06/26 20:44:45
Done.
| |
| 66 | |
| 67 // This library should ensure that you can't move to the next test until all | |
| 68 // build expectations are processed. | |
| 69 assert(_buildExpectations.isEmpty); | |
| 70 | |
| 71 // TODO(bob): Temp! | |
|
nweiz
2013/06/25 22:37:56
*cough cough*
Bob Nystrom
2013/06/26 20:44:45
Oops! Done.
| |
| 72 return _graph; | |
| 73 } | |
| 74 | |
| 75 /// Updates [assets] in the current [AssetProvider]. Each item in the list may | |
| 76 /// either be an [AssetId] or a string that can be parsed as one. | |
|
nweiz
2013/06/25 22:37:56
Style nit: the second sentence here should be in a
Bob Nystrom
2013/06/26 20:44:45
Done.
| |
| 77 void updateSources(List assets) { | |
| 78 // Allow strings as asset IDs. | |
| 79 assets = assets.map((asset) { | |
| 80 if (asset is String) return new AssetId.parse(asset); | |
| 81 return asset; | |
| 82 }); | |
| 83 | |
| 84 _graph.updateSources(assets); | |
| 85 } | |
| 86 | |
| 87 /// Removes [assets] from the current [AssetProvider]. Each item in the list | |
| 88 /// may either be an [AssetId] or a string that can be parsed as one. | |
| 89 void removeSources(assets) { | |
| 90 // Allow strings as asset IDs. | |
| 91 assets = assets.map((asset) { | |
| 92 if (asset is String) return new AssetId.parse(asset); | |
| 93 return asset; | |
| 94 }); | |
| 95 | |
| 96 _graph.removeSources(assets); | |
| 97 } | |
| 98 | |
| 99 /// Changes the contents of an asset identified by [name] to [contents]. Does | |
| 100 /// not update it in the graph. | |
| 101 void modifyAsset(String name, String contents) { | |
| 102 _provider._modifyAsset(name, contents); | |
|
nweiz
2013/06/25 22:37:56
This seems like it should be scheduled.
Bob Nystrom
2013/06/26 20:44:45
Done.
| |
| 103 } | |
| 104 | |
| 105 /// Pauses the internally created [AssetProvider]. All asset requests that come | |
| 106 /// in after this will wait until the provider is unpaused. | |
|
nweiz
2013/06/25 22:37:56
"that come in" -> "that the [AssetGraph] makes to
Bob Nystrom
2013/06/26 20:44:45
Done.
| |
| 107 void pauseProvider() { | |
| 108 schedule(() { | |
|
nweiz
2013/06/25 22:37:56
Style nit: =>. Also below.
All [schedule] calls i
Bob Nystrom
2013/06/26 20:44:45
Done.
| |
| 109 _provider._wait(); | |
|
nweiz
2013/06/25 22:37:56
It's weird that the public methods are named "paus
Bob Nystrom
2013/06/26 20:44:45
Done.
| |
| 110 }); | |
| 111 } | |
| 112 | |
| 113 /// Unpauses the provider and allows all pending asset loads to finish. | |
|
nweiz
2013/06/25 22:37:56
This should refer explicitly to [pauseProvider].
Bob Nystrom
2013/06/26 20:44:45
Done.
| |
| 114 void resumeProvider() { | |
| 115 schedule(() { | |
| 116 _provider._complete(); | |
| 117 }); | |
| 118 } | |
| 119 | |
| 120 /// Waits for the next [BuildResult] to be emitted and asserts that it is a | |
| 121 /// build success. | |
| 122 Future buildShouldSucceed() { | |
|
nweiz
2013/06/25 22:37:56
This doesn't return a Future.
Bob Nystrom
2013/06/26 20:44:45
Done.
| |
| 123 schedule(() { | |
| 124 return _getNextBuildResult().then((result) { | |
| 125 expect(result.succeeded, isTrue); | |
| 126 }); | |
| 127 }); | |
| 128 } | |
| 129 | |
| 130 /// Waits for the next [BuildResult] to be emitted and asserts that it is a | |
| 131 /// build failure. Invokes [callback] with the error (not the result) so that | |
| 132 /// it can provide more precise expectations. | |
| 133 Future buildShouldFail(void callback(error)) { | |
|
nweiz
2013/06/25 22:37:56
This doesn't return a Future either, although it's
Bob Nystrom
2013/06/26 20:44:45
Done.
| |
| 134 schedule(() { | |
| 135 return _getNextBuildResult().then((result) { | |
| 136 expect(result.succeeded, isFalse); | |
| 137 callback(result.error); | |
| 138 }); | |
| 139 }); | |
| 140 } | |
| 141 | |
| 142 Future<BuildResult> _getNextBuildResult() { | |
| 143 if (_buildResults.isNotEmpty) { | |
| 144 return new Future.value(_buildResults.removeFirst()); | |
| 145 } | |
| 146 | |
| 147 // We don't have any results yet, so enqueue the expectation. | |
| 148 var completer = new Completer<BuildResult>(); | |
| 149 _buildExpectations.add(completer); | |
| 150 return completer.future; | |
| 151 } | |
| 152 | |
| 153 /// Expects that the graph will return an asset matching [name] and [contents]. | |
|
nweiz
2013/06/25 22:37:56
This comment is confusing. "return" implies that s
Bob Nystrom
2013/06/26 20:44:45
Changed to "deliver".
| |
| 154 void expectAsset(String name, [String contents]) { | |
| 27 var id = new AssetId.parse(name); | 155 var id = new AssetId.parse(name); |
| 28 | 156 |
| 29 if (contents == null) { | 157 if (contents == null) { |
| 30 contents = pathos.basenameWithoutExtension(id.path); | 158 contents = pathos.basenameWithoutExtension(id.path); |
| 31 } | 159 } |
| 32 | 160 |
| 33 schedule(() { | 161 schedule(() { |
| 34 return graph.getAssetById(id).then((asset) { | 162 return _graph.getAssetById(id).then((asset) { |
| 35 // TODO(rnystrom): Make an actual Matcher class for this. | 163 // TODO(rnystrom): Make an actual Matcher class for this. |
| 36 expect(asset, new isInstanceOf<MockAsset>()); | 164 expect(asset, new isInstanceOf<MockAsset>()); |
| 37 expect(asset._id.package, equals(id.package)); | 165 expect(asset._id.package, equals(id.package)); |
| 38 expect(asset._id.path, equals(id.path)); | 166 expect(asset._id.path, equals(id.path)); |
| 39 expect(asset._contents, equals(contents)); | 167 expect(asset._contents, equals(contents)); |
| 40 }); | 168 }); |
| 41 }, "get asset $name"); | 169 }, "get asset $name"); |
| 42 } | 170 } |
| 43 | 171 |
| 44 /// Expects that [graph] will not find an asset matching [name]. | 172 /// Expects that the graph will not find an asset matching [name]. |
| 45 void expectNoAsset(AssetGraph graph, String name) { | 173 void expectNoAsset(String name) { |
| 46 var id = new AssetId.parse(name); | 174 var id = new AssetId.parse(name); |
| 47 | 175 |
| 48 // Make sure the future gets the error. | 176 // Make sure the future gets the error. |
| 49 schedule(() { | 177 schedule(() { |
| 50 return graph.getAssetById(id).then((asset) { | 178 return _graph.getAssetById(id).then((asset) { |
| 51 fail("Should have thrown error but got $asset."); | 179 fail("Should have thrown error but got $asset."); |
|
nweiz
2013/06/25 22:37:56
Manually failing feels unnecessary when we have li
Bob Nystrom
2013/06/26 20:44:45
The body of that predicate gets kind of nasty. Thi
nweiz
2013/06/27 00:19:25
I thought you could just use [expect] in [predicat
| |
| 52 }).catchError((error) { | 180 }).catchError((error) { |
| 53 expect(error, new isInstanceOf<AssetNotFoundException>()); | 181 expect(error, new isInstanceOf<AssetNotFoundException>()); |
| 54 expect(error.id, equals(id)); | 182 expect(error.id, equals(id)); |
| 55 }); | 183 }); |
| 56 }, "get asset $name"); | 184 }, "get asset $name"); |
| 57 } | 185 } |
| 58 | 186 |
| 59 /// Expects that [graph] will have an output file collision error on an asset | 187 /// Expects that the graph will have an output file collision error on an asset |
| 60 /// matching [name]. | 188 /// matching [name]. |
| 61 Future expectCollision(AssetGraph graph, String name) { | 189 Future expectCollision(String name) { |
| 62 var id = new AssetId.parse(name); | 190 var id = new AssetId.parse(name); |
| 63 return schedule(() { | 191 return schedule(() { |
| 64 return graph.results.first.then((result) { | 192 return _graph.results.first.then((result) { |
| 65 expect(result.error, new isInstanceOf<AssetCollisionException>()); | 193 expect(result.error, new isInstanceOf<AssetCollisionException>()); |
| 66 expect(result.error.id, equals(id)); | 194 expect(result.error.id, equals(id)); |
| 67 }); | 195 }); |
| 68 }, "get collision on $name"); | 196 }, "get collision on $name"); |
| 69 } | 197 } |
| 70 | 198 |
| 71 /// Expects that [graph] will have an error on an asset matching [name] for | 199 /// Expects that [graph] will have an error on an asset matching [name] for |
| 72 /// missing [input]. | 200 /// missing [input]. |
| 73 Future expectMissingInput(AssetGraph graph, String name, String input) { | 201 Future expectMissingInput(AssetGraph graph, String name, String input) { |
| 74 var missing = new AssetId.parse(input); | 202 var missing = new AssetId.parse(input); |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 86 | 214 |
| 87 /// An [AssetProvider] that provides the given set of assets. | 215 /// An [AssetProvider] that provides the given set of assets. |
| 88 class MockProvider implements AssetProvider { | 216 class MockProvider implements AssetProvider { |
| 89 Iterable<String> get packages => _packages.keys; | 217 Iterable<String> get packages => _packages.keys; |
| 90 | 218 |
| 91 final _packages = new Map<String, List<MockAsset>>(); | 219 final _packages = new Map<String, List<MockAsset>>(); |
| 92 | 220 |
| 93 /// The completer that [getAsset()] is waiting on to complete. | 221 /// The completer that [getAsset()] is waiting on to complete. |
| 94 /// | 222 /// |
| 95 /// If `null` it will return the asset immediately. | 223 /// If `null` it will return the asset immediately. |
| 96 Completer _wait; | 224 Completer _waitCompleter; |
| 97 | 225 |
| 98 /// Tells the provider to wait during [getAsset] until [complete()] | 226 /// Tells the provider to wait during [getAsset] until [complete()] |
| 99 /// is called. | 227 /// is called. |
| 100 /// | 228 /// |
| 101 /// Lets you test the asynchronous behavior of loading. | 229 /// Lets you test the asynchronous behavior of loading. |
| 102 void wait() { | 230 void _wait() { |
| 103 _wait = new Completer(); | 231 _waitCompleter = new Completer(); |
| 104 } | 232 } |
| 105 | 233 |
| 106 void complete() { | 234 void _complete() { |
| 107 _wait.complete(); | 235 _waitCompleter.complete(); |
| 108 _wait = null; | 236 _waitCompleter = null; |
| 109 } | 237 } |
| 110 | 238 |
| 111 MockProvider(assets) { | 239 MockProvider(assets) { |
| 112 if (assets is Map) { | 240 if (assets is Map) { |
| 113 assets.forEach((asset, contents) { | 241 assets.forEach((asset, contents) { |
| 114 var id = new AssetId.parse(asset); | 242 var id = new AssetId.parse(asset); |
| 115 var package = _packages.putIfAbsent(id.package, () => []); | 243 var package = _packages.putIfAbsent(id.package, () => []); |
| 116 package.add(new MockAsset(id, contents)); | 244 package.add(new MockAsset(id, contents)); |
| 117 }); | 245 }); |
| 118 } else if (assets is Iterable) { | 246 } else if (assets is Iterable) { |
| 119 for (var asset in assets) { | 247 for (var asset in assets) { |
| 120 var id = new AssetId.parse(asset); | 248 var id = new AssetId.parse(asset); |
| 121 var package = _packages.putIfAbsent(id.package, () => []); | 249 var package = _packages.putIfAbsent(id.package, () => []); |
| 122 var contents = pathos.basenameWithoutExtension(id.path); | 250 var contents = pathos.basenameWithoutExtension(id.path); |
| 123 package.add(new MockAsset(id, contents)); | 251 package.add(new MockAsset(id, contents)); |
| 124 } | 252 } |
| 125 } | 253 } |
| 126 } | 254 } |
| 127 | 255 |
| 128 void modifyAsset(String name, String contents) { | 256 void _modifyAsset(String name, String contents) { |
| 129 var id = new AssetId.parse(name); | 257 var id = new AssetId.parse(name); |
| 130 var asset = _packages[id.package].firstWhere((a) => a._id == id); | 258 var asset = _packages[id.package].firstWhere((a) => a._id == id); |
| 131 asset._contents = contents; | 259 asset._contents = contents; |
| 132 } | 260 } |
| 133 | 261 |
| 134 List<AssetId> listAssets(String package, {String within}) { | 262 List<AssetId> listAssets(String package, {String within}) { |
| 135 if (within != null) { | 263 if (within != null) { |
| 136 throw new UnimplementedError("Doesn't handle 'within' yet."); | 264 throw new UnimplementedError("Doesn't handle 'within' yet."); |
| 137 } | 265 } |
| 138 | 266 |
| 139 return _packages[package].map((asset) => asset.id); | 267 return _packages[package].map((asset) => asset.id); |
| 140 } | 268 } |
| 141 | 269 |
| 142 Future<Asset> getAsset(AssetId id) { | 270 Future<Asset> getAsset(AssetId id) { |
| 143 var future; | 271 var future; |
| 144 if (_wait != null) { | 272 if (_waitCompleter != null) { |
| 145 future = _wait.future; | 273 future = _waitCompleter.future; |
| 146 } else { | 274 } else { |
| 147 future = new Future.value(); | 275 future = new Future.value(); |
| 148 } | 276 } |
| 149 | 277 |
| 150 return future.then((_) { | 278 return future.then((_) { |
| 151 var package = _packages[id.package]; | 279 var package = _packages[id.package]; |
| 152 if (package == null) throw new AssetNotFoundException(id); | 280 if (package == null) throw new AssetNotFoundException(id); |
| 153 | 281 |
| 154 return package.firstWhere((asset) => asset._id == id, | 282 return package.firstWhere((asset) => asset._id == id, |
| 155 orElse: () => throw new AssetNotFoundException(id)); | 283 orElse: () => throw new AssetNotFoundException(id)); |
| (...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 333 | 461 |
| 334 MockAsset(this._id, this._contents); | 462 MockAsset(this._id, this._contents); |
| 335 | 463 |
| 336 String readAsString() => _contents; | 464 String readAsString() => _contents; |
| 337 Stream<List<int>> read() => throw new UnimplementedError(); | 465 Stream<List<int>> read() => throw new UnimplementedError(); |
| 338 | 466 |
| 339 serialize() => throw new UnimplementedError(); | 467 serialize() => throw new UnimplementedError(); |
| 340 | 468 |
| 341 String toString() => "MockAsset $_id $_contents"; | 469 String toString() => "MockAsset $_id $_contents"; |
| 342 } | 470 } |
| OLD | NEW |