Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(76)

Side by Side Diff: pkg/barback/test/utils.dart

Issue 21226004: Refactor the barback tests somewhat. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Code review changes. Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/barback/test/transformer/rewrite.dart ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 import 'dart:collection';
9 import 'dart:io'; 9 import 'dart:io';
10 10
11 import 'package:barback/barback.dart'; 11 import 'package:barback/barback.dart';
12 import 'package:barback/src/asset_cascade.dart'; 12 import 'package:barback/src/asset_cascade.dart';
13 import 'package:barback/src/asset_set.dart';
14 import 'package:barback/src/cancelable_future.dart';
13 import 'package:barback/src/package_graph.dart'; 15 import 'package:barback/src/package_graph.dart';
14 import 'package:barback/src/utils.dart'; 16 import 'package:barback/src/utils.dart';
15 import 'package:path/path.dart' as pathos; 17 import 'package:path/path.dart' as pathos;
16 import 'package:scheduled_test/scheduled_test.dart'; 18 import 'package:scheduled_test/scheduled_test.dart';
17 import 'package:stack_trace/stack_trace.dart'; 19 import 'package:stack_trace/stack_trace.dart';
18 import 'package:unittest/compact_vm_config.dart'; 20 import 'package:unittest/compact_vm_config.dart';
19 21
22 export 'transformer/bad.dart';
23 export 'transformer/check_content.dart';
24 export 'transformer/create_asset.dart';
25 export 'transformer/many_to_one.dart';
26 export 'transformer/mock.dart';
27 export 'transformer/one_to_many.dart';
28 export 'transformer/rewrite.dart';
29
20 var _configured = false; 30 var _configured = false;
21 31
22 MockProvider _provider; 32 MockProvider _provider;
23 PackageGraph _graph; 33 PackageGraph _graph;
24 34
25 /// Calls to [buildShouldSucceed] and [buildShouldFail] set expectations on 35 /// Calls to [buildShouldSucceed] and [buildShouldFail] set expectations on
26 /// successive [BuildResult]s from [_graph]. This keeps track of how many calls 36 /// successive [BuildResult]s from [_graph]. This keeps track of how many calls
27 /// have already been made so later calls know which result to look for. 37 /// have already been made so later calls know which result to look for.
28 int _nextBuildResult; 38 int _nextBuildResult;
29 39
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
89 /// Schedules a change to the contents of an asset identified by [name] to 99 /// Schedules a change to the contents of an asset identified by [name] to
90 /// [contents]. 100 /// [contents].
91 /// 101 ///
92 /// Does not update it in the graph. 102 /// Does not update it in the graph.
93 void modifyAsset(String name, String contents) { 103 void modifyAsset(String name, String contents) {
94 schedule(() { 104 schedule(() {
95 _provider._modifyAsset(name, contents); 105 _provider._modifyAsset(name, contents);
96 }, "modify asset $name"); 106 }, "modify asset $name");
97 } 107 }
98 108
109 /// Schedules an error to be generated when loading the asset identified by
110 /// [name].
111 ///
112 /// Does not update the asset in the graph.
113 void setAssetError(String name) {
114 schedule(() {
115 _provider._setAssetError(name);
116 }, "set error for asset $name");
117 }
118
99 /// Schedules a pause of the internally created [PackageProvider]. 119 /// Schedules a pause of the internally created [PackageProvider].
100 /// 120 ///
101 /// All asset requests that the [PackageGraph] makes to the provider after this 121 /// All asset requests that the [PackageGraph] makes to the provider after this
102 /// will not complete until [resumeProvider] is called. 122 /// will not complete until [resumeProvider] is called.
103 void pauseProvider() { 123 void pauseProvider() {
104 schedule(() => _provider._pause(), "pause provider"); 124 schedule(() => _provider._pause(), "pause provider");
105 } 125 }
106 126
107 /// Schedules an unpause of the provider after a call to [pauseProvider] and 127 /// Schedules an unpause of the provider after a call to [pauseProvider] and
108 /// allows all pending asset loads to finish. 128 /// allows all pending asset loads to finish.
109 void resumeProvider() { 129 void resumeProvider() {
110 schedule(() => _provider._resume(), "resume provider"); 130 schedule(() => _provider._resume(), "resume provider");
111 } 131 }
112 132
113 /// Asserts that the current build step shouldn't have finished by this point in 133 /// Asserts that the current build step shouldn't have finished by this point in
114 /// the schedule. 134 /// the schedule.
115 /// 135 ///
116 /// This uses the same build counter as [buildShouldSucceed] and 136 /// This uses the same build counter as [buildShouldSucceed] and
117 /// [buildShouldFail], so those can be used to validate build results before and 137 /// [buildShouldFail], so those can be used to validate build results before and
118 /// after this. 138 /// after this.
119 void buildShouldNotBeDone() { 139 void buildShouldNotBeDone() {
120 var resultAllowed = false; 140 _futureShouldNotCompleteUntil(
121 var trace = new Trace.current(); 141 _graph.results.elementAt(_nextBuildResult),
122 _graph.results.elementAt(_nextBuildResult).then((result) { 142 schedule(() => pumpEventQueue(), "build should not terminate"),
123 if (resultAllowed) return; 143 "build");
124
125 currentSchedule.signalError(
126 new Exception("Expected build not to terminate "
127 "here, but it terminated with result: $result"), trace);
128 }).catchError((error) {
129 if (resultAllowed) return;
130 currentSchedule.signalError(error);
131 });
132
133 schedule(() {
134 // Pump the event queue in case the build completes out-of-band after we get
135 // here. If it does, we want to signal an error.
136 return pumpEventQueue().then((_) {
137 resultAllowed = true;
138 });
139 }, "ensuring build doesn't terminate");
140 } 144 }
141 145
142 /// Expects that the next [BuildResult] is a build success. 146 /// Expects that the next [BuildResult] is a build success.
143 void buildShouldSucceed() { 147 void buildShouldSucceed() {
144 expect(_getNextBuildResult().then((result) { 148 expect(_getNextBuildResult().then((result) {
149 result.errors.forEach(currentSchedule.signalError);
145 expect(result.succeeded, isTrue); 150 expect(result.succeeded, isTrue);
146 }), completes); 151 }), completes);
147 } 152 }
148 153
149 /// Expects that the next [BuildResult] emitted is a failure. 154 /// Expects that the next [BuildResult] emitted is a failure.
150 /// 155 ///
151 /// [matchers] is a list of matchers to match against the errors that caused the 156 /// [matchers] is a list of matchers to match against the errors that caused the
152 /// build to fail. Every matcher is expected to match an error, but the order of 157 /// build to fail. Every matcher is expected to match an error, but the order of
153 /// matchers is unimportant. 158 /// matchers is unimportant.
154 void buildShouldFail(List matchers) { 159 void buildShouldFail(List matchers) {
(...skipping 28 matching lines...) Expand all
183 void expectAsset(String name, [String contents]) { 188 void expectAsset(String name, [String contents]) {
184 var id = new AssetId.parse(name); 189 var id = new AssetId.parse(name);
185 190
186 if (contents == null) { 191 if (contents == null) {
187 contents = pathos.basenameWithoutExtension(id.path); 192 contents = pathos.basenameWithoutExtension(id.path);
188 } 193 }
189 194
190 schedule(() { 195 schedule(() {
191 return _graph.getAssetById(id).then((asset) { 196 return _graph.getAssetById(id).then((asset) {
192 // TODO(rnystrom): Make an actual Matcher class for this. 197 // TODO(rnystrom): Make an actual Matcher class for this.
193 expect(asset, new isInstanceOf<MockAsset>());
194 expect(asset.id, equals(id)); 198 expect(asset.id, equals(id));
195 expect(asset.contents, equals(contents)); 199 expect(asset.readAsString(), completion(equals(contents)));
196 }); 200 });
197 }, "get asset $name"); 201 }, "get asset $name");
198 } 202 }
199 203
200 /// Schedules an expectation that the graph will not find an asset matching 204 /// Schedules an expectation that the graph will not find an asset matching
201 /// [name]. 205 /// [name].
202 void expectNoAsset(String name) { 206 void expectNoAsset(String name) {
203 var id = new AssetId.parse(name); 207 var id = new AssetId.parse(name);
204 208
205 // Make sure the future gets the error. 209 // Make sure the future gets the error.
206 schedule(() { 210 schedule(() {
207 return _graph.getAssetById(id).then((asset) { 211 return _graph.getAssetById(id).then((asset) {
208 fail("Should have thrown error but got $asset."); 212 fail("Should have thrown error but got $asset.");
209 }).catchError((error) { 213 }).catchError((error) {
210 expect(error, new isInstanceOf<AssetNotFoundException>()); 214 expect(error, new isInstanceOf<AssetNotFoundException>());
211 expect(error.id, equals(id)); 215 expect(error.id, equals(id));
212 }); 216 });
213 }, "get asset $name"); 217 }, "get asset $name");
214 } 218 }
215 219
220 /// Schedules an expectation that a [getAssetById] call for the given asset
221 /// won't terminate at this point in the schedule.
222 void expectAssetDoesNotComplete(String name) {
223 var id = new AssetId.parse(name);
224
225 schedule(() {
226 return _futureShouldNotCompleteUntil(
227 _graph.getAssetById(id),
228 pumpEventQueue(),
229 "asset $id");
230 }, "asset $id should not complete");
231 }
232
216 /// Returns a matcher for an [AssetNotFoundException] with the given [id]. 233 /// Returns a matcher for an [AssetNotFoundException] with the given [id].
217 Matcher isAssetNotFoundException(String name) { 234 Matcher isAssetNotFoundException(String name) {
218 var id = new AssetId.parse(name); 235 var id = new AssetId.parse(name);
219 return allOf( 236 return allOf(
220 new isInstanceOf<AssetNotFoundException>(), 237 new isInstanceOf<AssetNotFoundException>(),
221 predicate((error) => error.id == id, 'id is $name')); 238 predicate((error) => error.id == id, 'id == $name'));
222 } 239 }
223 240
224 /// Returns a matcher for an [AssetCollisionException] with the given [id]. 241 /// Returns a matcher for an [AssetCollisionException] with the given [id].
225 Matcher isAssetCollisionException(String name) { 242 Matcher isAssetCollisionException(String name) {
226 var id = new AssetId.parse(name); 243 var id = new AssetId.parse(name);
227 return allOf( 244 return allOf(
228 new isInstanceOf<AssetCollisionException>(), 245 new isInstanceOf<AssetCollisionException>(),
229 predicate((error) => error.id == id, 'id is $name')); 246 predicate((error) => error.id == id, 'id == $name'));
230 } 247 }
231 248
232 /// Returns a matcher for a [MissingInputException] with the given [id]. 249 /// Returns a matcher for a [MissingInputException] with the given [id].
233 Matcher isMissingInputException(String name) { 250 Matcher isMissingInputException(String name) {
234 var id = new AssetId.parse(name); 251 var id = new AssetId.parse(name);
235 return allOf( 252 return allOf(
236 new isInstanceOf<MissingInputException>(), 253 new isInstanceOf<MissingInputException>(),
237 predicate((error) => error.id == id, 'id is $name')); 254 predicate((error) => error.id == id, 'id == $name'));
238 } 255 }
239 256
240 /// Returns a matcher for an [InvalidOutputException] with the given id and 257 /// Returns a matcher for an [InvalidOutputException] with the given id and
241 /// package name. 258 /// package name.
242 Matcher isInvalidOutputException(String package, String name) { 259 Matcher isInvalidOutputException(String package, String name) {
243 var id = new AssetId.parse(name); 260 var id = new AssetId.parse(name);
244 return allOf( 261 return allOf(
245 new isInstanceOf<InvalidOutputException>(), 262 new isInstanceOf<InvalidOutputException>(),
246 predicate((error) => error.package == package, 'package is $package'), 263 predicate((error) => error.package == package, 'package is $package'),
247 predicate((error) => error.id == id, 'id is $name')); 264 predicate((error) => error.id == id, 'id == $name'));
265 }
266
267 /// Returns a matcher for a [MockLoadException] with the given [id].
268 Matcher isMockLoadException(String name) {
269 var id = new AssetId.parse(name);
270 return allOf(
271 new isInstanceOf<MockLoadException>(),
272 predicate((error) => error.id == id, 'id == $name'));
273 }
274
275 /// Asserts that [future] shouldn't complete until after [delay] completes.
276 ///
277 /// Once [delay] completes, the output of [future] is ignored, even if it's an
278 /// error.
279 ///
280 /// [description] should describe [future].
281 Future _futureShouldNotCompleteUntil(Future future, Future delay,
282 String description) {
283 var trace = new Trace.current();
284 var cancelable = new CancelableFuture(future);
285 cancelable.then((result) {
286 currentSchedule.signalError(
287 new Exception("Expected $description not to complete here, but it "
288 "completed with result: $result"),
289 trace);
290 }).catchError((error) {
291 currentSchedule.signalError(error);
292 });
293
294 return delay.then((_) => cancelable.cancel());
248 } 295 }
249 296
250 /// An [AssetProvider] that provides the given set of assets. 297 /// An [AssetProvider] that provides the given set of assets.
251 class MockProvider implements PackageProvider { 298 class MockProvider implements PackageProvider {
252 Iterable<String> get packages => _packages.keys; 299 Iterable<String> get packages => _packages.keys;
253 300
254 Map<String, _MockPackage> _packages; 301 Map<String, _MockPackage> _packages;
255 302
303 /// The set of assets for which [MockLoadException]s should be emitted if
304 /// they're loaded.
305 final _errors = new Set<AssetId>();
306
256 /// The completer that [getAsset()] is waiting on to complete when paused. 307 /// The completer that [getAsset()] is waiting on to complete when paused.
257 /// 308 ///
258 /// If `null` it will return the asset immediately. 309 /// If `null` it will return the asset immediately.
259 Completer _pauseCompleter; 310 Completer _pauseCompleter;
260 311
261 /// Tells the provider to wait during [getAsset] until [complete()] 312 /// Tells the provider to wait during [getAsset] until [complete()]
262 /// is called. 313 /// is called.
263 /// 314 ///
264 /// Lets you test the asynchronous behavior of loading. 315 /// Lets you test the asynchronous behavior of loading.
265 void _pause() { 316 void _pause() {
266 _pauseCompleter = new Completer(); 317 _pauseCompleter = new Completer();
267 } 318 }
268 319
269 void _resume() { 320 void _resume() {
270 _pauseCompleter.complete(); 321 _pauseCompleter.complete();
271 _pauseCompleter = null; 322 _pauseCompleter = null;
272 } 323 }
273 324
274 MockProvider(assets, 325 MockProvider(assets,
275 Map<String, Iterable<Iterable<Transformer>>> transformers) { 326 Map<String, Iterable<Iterable<Transformer>>> transformers) {
276 var assetList; 327 var assetList;
277 if (assets is Map) { 328 if (assets is Map) {
278 assetList = assets.keys.map((asset) { 329 assetList = assets.keys.map((asset) {
279 var id = new AssetId.parse(asset); 330 var id = new AssetId.parse(asset);
280 return new MockAsset(id, assets[asset]); 331 return new _MockAsset(id, assets[asset]);
281 }); 332 });
282 } else if (assets is Iterable) { 333 } else if (assets is Iterable) {
283 assetList = assets.map((asset) { 334 assetList = assets.map((asset) {
284 var id = new AssetId.parse(asset); 335 var id = new AssetId.parse(asset);
285 var contents = pathos.basenameWithoutExtension(id.path); 336 var contents = pathos.basenameWithoutExtension(id.path);
286 return new MockAsset(id, contents); 337 return new _MockAsset(id, contents);
287 }); 338 });
288 } 339 }
289 340
290 _packages = mapMapValues(groupBy(assetList, (asset) => asset.id.package), 341 _packages = mapMapValues(groupBy(assetList, (asset) => asset.id.package),
291 (package, assets) { 342 (package, assets) {
292 var packageTransformers = transformers[package]; 343 var packageTransformers = transformers[package];
293 if (packageTransformers == null) packageTransformers = []; 344 if (packageTransformers == null) packageTransformers = [];
294 return new _MockPackage(assets, packageTransformers.toList()); 345 return new _MockPackage(
346 new AssetSet.from(assets), packageTransformers.toList());
295 }); 347 });
296 348
297 // If there are no assets or transformers, add a dummy package. This better 349 // If there are no assets or transformers, add a dummy package. This better
298 // simulates the real world, where there'll always be at least the 350 // simulates the real world, where there'll always be at least the
299 // entrypoint package. 351 // entrypoint package.
300 if (_packages.isEmpty) _packages = {"app": new _MockPackage([], [])}; 352 if (_packages.isEmpty) {
353 _packages = {"app": new _MockPackage(new AssetSet(), [])};
354 }
301 } 355 }
302 356
303 void _modifyAsset(String name, String contents) { 357 void _modifyAsset(String name, String contents) {
304 var id = new AssetId.parse(name); 358 var id = new AssetId.parse(name);
305 var asset = _packages[id.package].assets.firstWhere((a) => a.id == id); 359 _errors.remove(id);
306 asset.contents = contents; 360 _packages[id.package].assets[id].contents = contents;
307 } 361 }
308 362
363 void _setAssetError(String name) => _errors.add(new AssetId.parse(name));
364
309 List<AssetId> listAssets(String package, {String within}) { 365 List<AssetId> listAssets(String package, {String within}) {
310 if (within != null) { 366 if (within != null) {
311 throw new UnimplementedError("Doesn't handle 'within' yet."); 367 throw new UnimplementedError("Doesn't handle 'within' yet.");
312 } 368 }
313 369
314 return _packages[package].assets.map((asset) => asset.id); 370 return _packages[package].assets.map((asset) => asset.id);
315 } 371 }
316 372
317 Iterable<Iterable<Transformer>> getTransformers(String package) { 373 Iterable<Iterable<Transformer>> getTransformers(String package) {
318 var mockPackage = _packages[package]; 374 var mockPackage = _packages[package];
319 if (mockPackage == null) { 375 if (mockPackage == null) {
320 throw new ArgumentError("No package named $package."); 376 throw new ArgumentError("No package named $package.");
321 } 377 }
322 return mockPackage.transformers; 378 return mockPackage.transformers;
323 } 379 }
324 380
325 Future<Asset> getAsset(AssetId id) { 381 Future<Asset> getAsset(AssetId id) {
382 // Eagerly load the asset so we can test an asset's value changing between
383 // when a load starts and when it finishes.
384 var package = _packages[id.package];
385 var asset;
386 if (package != null) asset = package.assets[id];
387
388 var hasError = _errors.contains(id);
389
326 var future; 390 var future;
327 if (_pauseCompleter != null) { 391 if (_pauseCompleter != null) {
328 future = _pauseCompleter.future; 392 future = _pauseCompleter.future;
329 } else { 393 } else {
330 future = new Future.value(); 394 future = new Future.value();
331 } 395 }
332 396
333 return future.then((_) { 397 return future.then((_) {
334 var package = _packages[id.package]; 398 if (hasError) throw new MockLoadException(id);
335 if (package == null) throw new AssetNotFoundException(id); 399 if (asset == null) throw new AssetNotFoundException(id);
336 400 return asset;
337 return package.assets.firstWhere((asset) => asset.id == id,
338 orElse: () => throw new AssetNotFoundException(id));
339 }); 401 });
340 } 402 }
341 } 403 }
342 404
405 /// Error thrown for assets with [setAssetError] set.
406 class MockLoadException implements Exception {
407 final AssetId id;
408
409 MockLoadException(this.id);
410
411 String toString() => "Error loading $id.";
412 }
413
343 /// Used by [MockProvider] to keep track of which assets and transformers exist 414 /// Used by [MockProvider] to keep track of which assets and transformers exist
344 /// for each package. 415 /// for each package.
345 class _MockPackage { 416 class _MockPackage {
346 final List<MockAsset> assets; 417 final AssetSet assets;
347 final List<List<Transformer>> transformers; 418 final List<List<Transformer>> transformers;
348 419
349 _MockPackage(this.assets, Iterable<Iterable<Transformer>> transformers) 420 _MockPackage(this.assets, Iterable<Iterable<Transformer>> transformers)
350 : transformers = transformers.map((phase) => phase.toList()).toList(); 421 : transformers = transformers.map((phase) => phase.toList()).toList();
351 } 422 }
352 423
353 /// A [Transformer] that takes assets ending with one extension and generates
354 /// assets with a given extension.
355 ///
356 /// Appends the output extension to the contents of the input file.
357 class RewriteTransformer extends Transformer {
358 final String from;
359 final String to;
360
361 /// The number of times the transformer has been applied.
362 int numRuns = 0;
363
364 /// The number of currently running transforms.
365 int _runningTransforms = 0;
366
367 /// The completer that the transform is waiting on to complete.
368 ///
369 /// If `null` the transform will complete immediately.
370 Completer _wait;
371
372 /// A future that completes when the first apply of this transformer begins.
373 Future get started => _started.future;
374 final _started = new Completer();
375
376 /// Creates a transformer that rewrites assets whose extension is [from] to
377 /// one whose extension is [to].
378 ///
379 /// [to] may be a space-separated list in which case multiple outputs will be
380 /// created for each input.
381 RewriteTransformer(this.from, this.to);
382
383 /// `true` if any transforms are currently running.
384 bool get isRunning => _runningTransforms > 0;
385
386 /// Tells the transform to wait during its transformation until [complete()]
387 /// is called.
388 ///
389 /// Lets you test the asynchronous behavior of transformers.
390 void wait() {
391 _wait = new Completer();
392 }
393
394 void complete() {
395 _wait.complete();
396 _wait = null;
397 }
398
399 Future<bool> isPrimary(Asset asset) {
400 return new Future.value(asset.id.extension == ".$from");
401 }
402
403 Future apply(Transform transform) {
404 numRuns++;
405 if (!_started.isCompleted) _started.complete();
406 _runningTransforms++;
407 return transform.primaryInput.then((input) {
408 return Future.wait(to.split(" ").map((extension) {
409 var id = transform.primaryId.changeExtension(".$extension");
410 return input.readAsString().then((content) {
411 transform.addOutput(new MockAsset(id, "$content.$extension"));
412 });
413 })).then((_) {
414 if (_wait != null) return _wait.future;
415 });
416 }).whenComplete(() {
417 _runningTransforms--;
418 });
419 }
420
421 String toString() => "$from->$to";
422 }
423
424 /// A [Transformer] that takes an input asset that contains a comma-separated
425 /// list of paths and outputs a file for each path.
426 class OneToManyTransformer extends Transformer {
427 final String extension;
428
429 /// The number of times the transformer has been applied.
430 int numRuns = 0;
431
432 /// Creates a transformer that consumes assets with [extension].
433 ///
434 /// That file contains a comma-separated list of paths and it will output
435 /// files at each of those paths.
436 OneToManyTransformer(this.extension);
437
438 Future<bool> isPrimary(Asset asset) {
439 return new Future.value(asset.id.extension == ".$extension");
440 }
441
442 Future apply(Transform transform) {
443 numRuns++;
444 return transform.primaryInput.then((input) {
445 return input.readAsString().then((lines) {
446 for (var line in lines.split(",")) {
447 var id = new AssetId(transform.primaryId.package, line);
448 transform.addOutput(new MockAsset(id, "spread $extension"));
449 }
450 });
451 });
452 }
453
454 String toString() => "1->many $extension";
455 }
456
457 /// A transformer that uses the contents of a file to define the other inputs.
458 ///
459 /// Outputs a file with the same name as the primary but with an "out"
460 /// extension containing the concatenated contents of all non-primary inputs.
461 class ManyToOneTransformer extends Transformer {
462 final String extension;
463
464 /// The number of times the transformer has been applied.
465 int numRuns = 0;
466
467 /// Creates a transformer that consumes assets with [extension].
468 ///
469 /// That file contains a comma-separated list of paths and it will input
470 /// files at each of those paths.
471 ManyToOneTransformer(this.extension);
472
473 Future<bool> isPrimary(Asset asset) {
474 return new Future.value(asset.id.extension == ".$extension");
475 }
476
477 Future apply(Transform transform) {
478 numRuns++;
479 return transform.primaryInput.then((primary) {
480 return primary.readAsString().then((contents) {
481 // Get all of the included inputs.
482 var inputs = contents.split(",").map((path) {
483 var id = new AssetId(transform.primaryId.package, path);
484 return transform.getInput(id);
485 });
486
487 return Future.wait(inputs);
488 }).then((inputs) {
489 // Concatenate them to one output.
490 var output = "";
491 return Future.forEach(inputs, (input) {
492 return input.readAsString().then((contents) {
493 output += contents;
494 });
495 }).then((_) {
496 var id = transform.primaryId.changeExtension(".out");
497 transform.addOutput(new MockAsset(id, output));
498 });
499 });
500 });
501 }
502
503 String toString() => "many->1 $extension";
504 }
505
506 /// A transformer that throws an exception when run, after generating the
507 /// given outputs.
508 class BadTransformer extends Transformer {
509 /// The error it throws.
510 static const ERROR = "I am a bad transformer!";
511
512 /// The list of asset names that it should output.
513 final List<String> outputs;
514
515 BadTransformer(this.outputs);
516
517 Future<bool> isPrimary(Asset asset) => new Future.value(true);
518 Future apply(Transform transform) {
519 return newFuture(() {
520 // Create the outputs first.
521 for (var output in outputs) {
522 var id = new AssetId.parse(output);
523 transform.addOutput(new MockAsset(id, output));
524 }
525
526 // Then fail.
527 throw ERROR;
528 });
529 }
530 }
531
532 /// A transformer that outputs an asset with the given id.
533 class CreateAssetTransformer extends Transformer {
534 final String output;
535
536 CreateAssetTransformer(this.output);
537
538 Future<bool> isPrimary(Asset asset) => new Future.value(true);
539
540 Future apply(Transform transform) {
541 return newFuture(() {
542 transform.addOutput(new MockAsset(new AssetId.parse(output), output));
543 });
544 }
545 }
546
547 /// An implementation of [Asset] that never hits the file system. 424 /// An implementation of [Asset] that never hits the file system.
548 class MockAsset implements Asset { 425 class _MockAsset implements Asset {
549 final AssetId id; 426 final AssetId id;
550 String contents; 427 String contents;
551 428
552 MockAsset(this.id, this.contents); 429 _MockAsset(this.id, this.contents);
553 430
554 Future<String> readAsString({Encoding encoding}) => 431 Future<String> readAsString({Encoding encoding}) =>
555 new Future.value(contents); 432 new Future.value(contents);
556 433
557 Stream<List<int>> read() => throw new UnimplementedError(); 434 Stream<List<int>> read() => throw new UnimplementedError();
558 435
559 String toString() => "MockAsset $id $contents"; 436 String toString() => "MockAsset $id $contents";
560 } 437 }
OLDNEW
« no previous file with comments | « pkg/barback/test/transformer/rewrite.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698