OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 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. |
| 4 |
| 5 /// This library contains tests for transformer behavior that relates to actions |
| 6 /// happening concurrently or other complex asynchronous timing behavior. |
| 7 library barback.test.package_graph.transform.concurrency_test; |
| 8 |
| 9 import 'package:barback/src/utils.dart'; |
| 10 import 'package:scheduled_test/scheduled_test.dart'; |
| 11 |
| 12 import '../../utils.dart'; |
| 13 |
| 14 main() { |
| 15 initConfig(); |
| 16 test("runs transforms in the same phase in parallel", () { |
| 17 var transformerA = new RewriteTransformer("txt", "a"); |
| 18 var transformerB = new RewriteTransformer("txt", "b"); |
| 19 initGraph(["app|foo.txt"], {"app": [[transformerA, transformerB]]}); |
| 20 |
| 21 transformerA.pauseApply(); |
| 22 transformerB.pauseApply(); |
| 23 |
| 24 updateSources(["app|foo.txt"]); |
| 25 |
| 26 transformerA.waitUntilStarted(); |
| 27 transformerB.waitUntilStarted(); |
| 28 |
| 29 // They should both still be running. |
| 30 expect(transformerA.isRunning, completion(isTrue)); |
| 31 expect(transformerB.isRunning, completion(isTrue)); |
| 32 |
| 33 transformerA.resumeApply(); |
| 34 transformerB.resumeApply(); |
| 35 |
| 36 expectAsset("app|foo.a", "foo.a"); |
| 37 expectAsset("app|foo.b", "foo.b"); |
| 38 buildShouldSucceed(); |
| 39 }); |
| 40 |
| 41 test("discards outputs from a transform whose primary input is removed " |
| 42 "during processing", () { |
| 43 var rewrite = new RewriteTransformer("txt", "out"); |
| 44 initGraph(["app|foo.txt"], {"app": [[rewrite]]}); |
| 45 |
| 46 rewrite.pauseApply(); |
| 47 updateSources(["app|foo.txt"]); |
| 48 rewrite.waitUntilStarted(); |
| 49 |
| 50 removeSources(["app|foo.txt"]); |
| 51 rewrite.resumeApply(); |
| 52 expectNoAsset("app|foo.out"); |
| 53 buildShouldSucceed(); |
| 54 }); |
| 55 |
| 56 test("applies the correct transform if an asset is modified during isPrimary", |
| 57 () { |
| 58 var check1 = new CheckContentTransformer("first", "#1"); |
| 59 var check2 = new CheckContentTransformer("second", "#2"); |
| 60 initGraph({ |
| 61 "app|foo.txt": "first", |
| 62 }, {"app": [[check1, check2]]}); |
| 63 |
| 64 check1.pauseIsPrimary("app|foo.txt"); |
| 65 updateSources(["app|foo.txt"]); |
| 66 // Ensure that we're waiting on check1's isPrimary. |
| 67 schedule(pumpEventQueue); |
| 68 |
| 69 modifyAsset("app|foo.txt", "second"); |
| 70 updateSources(["app|foo.txt"]); |
| 71 check1.resumeIsPrimary("app|foo.txt"); |
| 72 |
| 73 expectAsset("app|foo.txt", "second#2"); |
| 74 buildShouldSucceed(); |
| 75 }); |
| 76 |
| 77 test("applies the correct transform if an asset is removed and added during " |
| 78 "isPrimary", () { |
| 79 var check1 = new CheckContentTransformer("first", "#1"); |
| 80 var check2 = new CheckContentTransformer("second", "#2"); |
| 81 initGraph({ |
| 82 "app|foo.txt": "first", |
| 83 }, {"app": [[check1, check2]]}); |
| 84 |
| 85 check1.pauseIsPrimary("app|foo.txt"); |
| 86 updateSources(["app|foo.txt"]); |
| 87 // Ensure that we're waiting on check1's isPrimary. |
| 88 schedule(pumpEventQueue); |
| 89 |
| 90 removeSources(["app|foo.txt"]); |
| 91 modifyAsset("app|foo.txt", "second"); |
| 92 updateSources(["app|foo.txt"]); |
| 93 check1.resumeIsPrimary("app|foo.txt"); |
| 94 |
| 95 expectAsset("app|foo.txt", "second#2"); |
| 96 buildShouldSucceed(); |
| 97 }); |
| 98 |
| 99 test("restarts processing if a change occurs during processing", () { |
| 100 var transformer = new RewriteTransformer("txt", "out"); |
| 101 initGraph(["app|foo.txt"], {"app": [[transformer]]}); |
| 102 |
| 103 transformer.pauseApply(); |
| 104 |
| 105 updateSources(["app|foo.txt"]); |
| 106 transformer.waitUntilStarted(); |
| 107 |
| 108 // Now update the graph during it. |
| 109 updateSources(["app|foo.txt"]); |
| 110 transformer.resumeApply(); |
| 111 |
| 112 expectAsset("app|foo.out", "foo.out"); |
| 113 buildShouldSucceed(); |
| 114 |
| 115 expect(transformer.numRuns, completion(equals(2))); |
| 116 }); |
| 117 |
| 118 test("aborts processing if the primary input is removed during processing", |
| 119 () { |
| 120 var transformer = new RewriteTransformer("txt", "out"); |
| 121 initGraph(["app|foo.txt"], {"app": [[transformer]]}); |
| 122 |
| 123 transformer.pauseApply(); |
| 124 |
| 125 updateSources(["app|foo.txt"]); |
| 126 transformer.waitUntilStarted(); |
| 127 |
| 128 // Now remove its primary input while it's running. |
| 129 removeSources(["app|foo.txt"]); |
| 130 transformer.resumeApply(); |
| 131 |
| 132 expectNoAsset("app|foo.out"); |
| 133 buildShouldSucceed(); |
| 134 |
| 135 expect(transformer.numRuns, completion(equals(1))); |
| 136 }); |
| 137 |
| 138 test("restarts processing if a change to a new secondary input occurs during " |
| 139 "processing", () { |
| 140 var transformer = new ManyToOneTransformer("txt"); |
| 141 initGraph({ |
| 142 "app|foo.txt": "bar.inc", |
| 143 "app|bar.inc": "bar" |
| 144 }, {"app": [[transformer]]}); |
| 145 |
| 146 transformer.pauseApply(); |
| 147 |
| 148 updateSources(["app|foo.txt", "app|bar.inc"]); |
| 149 transformer.waitUntilStarted(); |
| 150 |
| 151 // Give the transform time to load bar.inc the first time. |
| 152 schedule(pumpEventQueue); |
| 153 |
| 154 // Now update the secondary input before the transform finishes. |
| 155 modifyAsset("app|bar.inc", "baz"); |
| 156 updateSources(["app|bar.inc"]); |
| 157 // Give bar.inc enough time to be loaded and marked available before the |
| 158 // transformer completes. |
| 159 schedule(pumpEventQueue); |
| 160 |
| 161 transformer.resumeApply(); |
| 162 |
| 163 expectAsset("app|foo.out", "baz"); |
| 164 buildShouldSucceed(); |
| 165 |
| 166 expect(transformer.numRuns, completion(equals(2))); |
| 167 }); |
| 168 |
| 169 test("doesn't restart processing if a change to an old secondary input " |
| 170 "occurs during processing", () { |
| 171 var transformer = new ManyToOneTransformer("txt"); |
| 172 initGraph({ |
| 173 "app|foo.txt": "bar.inc", |
| 174 "app|bar.inc": "bar", |
| 175 "app|baz.inc": "baz" |
| 176 }, {"app": [[transformer]]}); |
| 177 |
| 178 updateSources(["app|foo.txt", "app|bar.inc", "app|baz.inc"]); |
| 179 expectAsset("app|foo.out", "bar"); |
| 180 buildShouldSucceed(); |
| 181 |
| 182 transformer.pauseApply(); |
| 183 modifyAsset("app|foo.txt", "baz.inc"); |
| 184 updateSources(["app|foo.txt"]); |
| 185 transformer.waitUntilStarted(); |
| 186 |
| 187 // Now update the old secondary input before the transform finishes. |
| 188 modifyAsset("app|bar.inc", "new bar"); |
| 189 updateSources(["app|bar.inc"]); |
| 190 // Give bar.inc enough time to be loaded and marked available before the |
| 191 // transformer completes. |
| 192 schedule(pumpEventQueue); |
| 193 |
| 194 transformer.resumeApply(); |
| 195 expectAsset("app|foo.out", "baz"); |
| 196 buildShouldSucceed(); |
| 197 |
| 198 // Should have run once the first time, then again when switching to |
| 199 // baz.inc. Should not run a third time because of bar.inc being modified. |
| 200 expect(transformer.numRuns, completion(equals(2))); |
| 201 }); |
| 202 |
| 203 test("restarts before finishing later phases when a change occurs", () { |
| 204 var txtToInt = new RewriteTransformer("txt", "int"); |
| 205 var intToOut = new RewriteTransformer("int", "out"); |
| 206 initGraph(["app|foo.txt", "app|bar.txt"], |
| 207 {"app": [[txtToInt], [intToOut]]}); |
| 208 |
| 209 txtToInt.pauseApply(); |
| 210 |
| 211 updateSources(["app|foo.txt"]); |
| 212 txtToInt.waitUntilStarted(); |
| 213 |
| 214 // Now update the graph during it. |
| 215 updateSources(["app|bar.txt"]); |
| 216 txtToInt.resumeApply(); |
| 217 |
| 218 expectAsset("app|foo.out", "foo.int.out"); |
| 219 expectAsset("app|bar.out", "bar.int.out"); |
| 220 buildShouldSucceed(); |
| 221 |
| 222 // Should only have run each transform once for each primary. |
| 223 expect(txtToInt.numRuns, completion(equals(2))); |
| 224 expect(intToOut.numRuns, completion(equals(2))); |
| 225 }); |
| 226 |
| 227 test("doesn't return an asset until it's finished rebuilding", () { |
| 228 initGraph(["app|foo.in"], {"app": [ |
| 229 [new RewriteTransformer("in", "mid")], |
| 230 [new RewriteTransformer("mid", "out")] |
| 231 ]}); |
| 232 |
| 233 updateSources(["app|foo.in"]); |
| 234 expectAsset("app|foo.out", "foo.mid.out"); |
| 235 buildShouldSucceed(); |
| 236 |
| 237 pauseProvider(); |
| 238 modifyAsset("app|foo.in", "new"); |
| 239 updateSources(["app|foo.in"]); |
| 240 expectAssetDoesNotComplete("app|foo.out"); |
| 241 buildShouldNotBeDone(); |
| 242 |
| 243 resumeProvider(); |
| 244 expectAsset("app|foo.out", "new.mid.out"); |
| 245 buildShouldSucceed(); |
| 246 }); |
| 247 |
| 248 test("doesn't return an asset until its in-place transform is done", () { |
| 249 var rewrite = new RewriteTransformer("txt", "txt"); |
| 250 initGraph(["app|foo.txt"], {"app": [[rewrite]]}); |
| 251 |
| 252 rewrite.pauseApply(); |
| 253 updateSources(["app|foo.txt"]); |
| 254 expectAssetDoesNotComplete("app|foo.txt"); |
| 255 |
| 256 rewrite.resumeApply(); |
| 257 expectAsset("app|foo.txt", "foo.txt"); |
| 258 buildShouldSucceed(); |
| 259 }); |
| 260 |
| 261 test("doesn't return an asset that's removed during isPrimary", () { |
| 262 var rewrite = new RewriteTransformer("txt", "txt"); |
| 263 initGraph(["app|foo.txt"], {"app": [[rewrite]]}); |
| 264 |
| 265 rewrite.pauseIsPrimary("app|foo.txt"); |
| 266 updateSources(["app|foo.txt"]); |
| 267 // Make sure we're waiting on isPrimary. |
| 268 schedule(pumpEventQueue); |
| 269 |
| 270 removeSources(["app|foo.txt"]); |
| 271 rewrite.resumeIsPrimary("app|foo.txt"); |
| 272 expectNoAsset("app|foo.txt"); |
| 273 buildShouldSucceed(); |
| 274 }); |
| 275 |
| 276 test("doesn't transform an asset that goes from primary to non-primary " |
| 277 "during isPrimary", () { |
| 278 var check = new CheckContentTransformer(new RegExp(r"^do$"), "ne"); |
| 279 initGraph({ |
| 280 "app|foo.txt": "do" |
| 281 }, {"app": [[check]]}); |
| 282 |
| 283 check.pauseIsPrimary("app|foo.txt"); |
| 284 updateSources(["app|foo.txt"]); |
| 285 // Make sure we're waiting on isPrimary. |
| 286 schedule(pumpEventQueue); |
| 287 |
| 288 modifyAsset("app|foo.txt", "don't"); |
| 289 updateSources(["app|foo.txt"]); |
| 290 check.resumeIsPrimary("app|foo.txt"); |
| 291 |
| 292 expectAsset("app|foo.txt", "don't"); |
| 293 buildShouldSucceed(); |
| 294 }); |
| 295 |
| 296 test("transforms an asset that goes from non-primary to primary " |
| 297 "during isPrimary", () { |
| 298 var check = new CheckContentTransformer("do", "ne"); |
| 299 initGraph({ |
| 300 "app|foo.txt": "don't" |
| 301 }, {"app": [[check]]}); |
| 302 |
| 303 check.pauseIsPrimary("app|foo.txt"); |
| 304 updateSources(["app|foo.txt"]); |
| 305 // Make sure we're waiting on isPrimary. |
| 306 schedule(pumpEventQueue); |
| 307 |
| 308 modifyAsset("app|foo.txt", "do"); |
| 309 updateSources(["app|foo.txt"]); |
| 310 check.resumeIsPrimary("app|foo.txt"); |
| 311 |
| 312 expectAsset("app|foo.txt", "done"); |
| 313 buildShouldSucceed(); |
| 314 }); |
| 315 |
| 316 test("doesn't return an asset that's removed during another transformer's " |
| 317 "isPrimary", () { |
| 318 var rewrite1 = new RewriteTransformer("txt", "txt"); |
| 319 var rewrite2 = new RewriteTransformer("md", "md"); |
| 320 initGraph(["app|foo.txt", "app|foo.md"], {"app": [[rewrite1, rewrite2]]}); |
| 321 |
| 322 rewrite2.pauseIsPrimary("app|foo.md"); |
| 323 updateSources(["app|foo.txt", "app|foo.md"]); |
| 324 // Make sure we're waiting on the correct isPrimary. |
| 325 schedule(pumpEventQueue); |
| 326 |
| 327 removeSources(["app|foo.txt"]); |
| 328 rewrite2.resumeIsPrimary("app|foo.md"); |
| 329 expectNoAsset("app|foo.txt"); |
| 330 expectAsset("app|foo.md", "foo.md"); |
| 331 buildShouldSucceed(); |
| 332 }); |
| 333 |
| 334 test("doesn't transform an asset that goes from primary to non-primary " |
| 335 "during another transformer's isPrimary", () { |
| 336 var rewrite = new RewriteTransformer("md", "md"); |
| 337 var check = new CheckContentTransformer(new RegExp(r"^do$"), "ne"); |
| 338 initGraph({ |
| 339 "app|foo.txt": "do", |
| 340 "app|foo.md": "foo" |
| 341 }, {"app": [[rewrite, check]]}); |
| 342 |
| 343 rewrite.pauseIsPrimary("app|foo.md"); |
| 344 updateSources(["app|foo.txt", "app|foo.md"]); |
| 345 // Make sure we're waiting on the correct isPrimary. |
| 346 schedule(pumpEventQueue); |
| 347 |
| 348 modifyAsset("app|foo.txt", "don't"); |
| 349 updateSources(["app|foo.txt"]); |
| 350 rewrite.resumeIsPrimary("app|foo.md"); |
| 351 |
| 352 expectAsset("app|foo.txt", "don't"); |
| 353 expectAsset("app|foo.md", "foo.md"); |
| 354 buildShouldSucceed(); |
| 355 }); |
| 356 |
| 357 test("transforms an asset that goes from non-primary to primary " |
| 358 "during another transformer's isPrimary", () { |
| 359 var rewrite = new RewriteTransformer("md", "md"); |
| 360 var check = new CheckContentTransformer("do", "ne"); |
| 361 initGraph({ |
| 362 "app|foo.txt": "don't", |
| 363 "app|foo.md": "foo" |
| 364 }, {"app": [[rewrite, check]]}); |
| 365 |
| 366 rewrite.pauseIsPrimary("app|foo.md"); |
| 367 updateSources(["app|foo.txt", "app|foo.md"]); |
| 368 // Make sure we're waiting on the correct isPrimary. |
| 369 schedule(pumpEventQueue); |
| 370 |
| 371 modifyAsset("app|foo.txt", "do"); |
| 372 updateSources(["app|foo.txt"]); |
| 373 rewrite.resumeIsPrimary("app|foo.md"); |
| 374 |
| 375 expectAsset("app|foo.txt", "done"); |
| 376 expectAsset("app|foo.md", "foo.md"); |
| 377 buildShouldSucceed(); |
| 378 }); |
| 379 |
| 380 test("returns an asset even if an unrelated build is running", () { |
| 381 initGraph([ |
| 382 "app|foo.in", |
| 383 "app|bar.in", |
| 384 ], {"app": [[new RewriteTransformer("in", "out")]]}); |
| 385 |
| 386 updateSources(["app|foo.in", "app|bar.in"]); |
| 387 expectAsset("app|foo.out", "foo.out"); |
| 388 expectAsset("app|bar.out", "bar.out"); |
| 389 buildShouldSucceed(); |
| 390 |
| 391 pauseProvider(); |
| 392 modifyAsset("app|foo.in", "new"); |
| 393 updateSources(["app|foo.in"]); |
| 394 expectAssetDoesNotComplete("app|foo.out"); |
| 395 expectAsset("app|bar.out", "bar.out"); |
| 396 buildShouldNotBeDone(); |
| 397 |
| 398 resumeProvider(); |
| 399 expectAsset("app|foo.out", "new.out"); |
| 400 buildShouldSucceed(); |
| 401 }); |
| 402 |
| 403 test("doesn't report AssetNotFound until all builds are finished", () { |
| 404 initGraph([ |
| 405 "app|foo.in", |
| 406 ], {"app": [[new RewriteTransformer("in", "out")]]}); |
| 407 |
| 408 updateSources(["app|foo.in"]); |
| 409 expectAsset("app|foo.out", "foo.out"); |
| 410 buildShouldSucceed(); |
| 411 |
| 412 pauseProvider(); |
| 413 updateSources(["app|foo.in"]); |
| 414 expectAssetDoesNotComplete("app|foo.out"); |
| 415 expectAssetDoesNotComplete("app|non-existent.out"); |
| 416 buildShouldNotBeDone(); |
| 417 |
| 418 resumeProvider(); |
| 419 expectAsset("app|foo.out", "foo.out"); |
| 420 expectNoAsset("app|non-existent.out"); |
| 421 buildShouldSucceed(); |
| 422 }); |
| 423 |
| 424 test("doesn't emit a result until all builds are finished", () { |
| 425 var rewrite = new RewriteTransformer("txt", "out"); |
| 426 initGraph([ |
| 427 "pkg1|foo.txt", |
| 428 "pkg2|foo.txt" |
| 429 ], {"pkg1": [[rewrite]], "pkg2": [[rewrite]]}); |
| 430 |
| 431 // First, run both packages' transformers so both packages are successful. |
| 432 updateSources(["pkg1|foo.txt", "pkg2|foo.txt"]); |
| 433 expectAsset("pkg1|foo.out", "foo.out"); |
| 434 expectAsset("pkg2|foo.out", "foo.out"); |
| 435 buildShouldSucceed(); |
| 436 |
| 437 // pkg1 is still successful, but pkg2 is waiting on the provider, so the |
| 438 // overall build shouldn't finish. |
| 439 pauseProvider(); |
| 440 updateSources(["pkg2|foo.txt"]); |
| 441 expectAsset("pkg1|foo.out", "foo.out"); |
| 442 buildShouldNotBeDone(); |
| 443 |
| 444 // Now that the provider is unpaused, pkg2's transforms finish and the |
| 445 // overall build succeeds. |
| 446 resumeProvider(); |
| 447 buildShouldSucceed(); |
| 448 }); |
| 449 |
| 450 test("one transformer takes a long time while the other finishes, then " |
| 451 "the input is removed", () { |
| 452 var rewrite1 = new RewriteTransformer("txt", "out1"); |
| 453 var rewrite2 = new RewriteTransformer("txt", "out2"); |
| 454 initGraph(["app|foo.txt"], {"app": [[rewrite1, rewrite2]]}); |
| 455 |
| 456 rewrite1.pauseApply(); |
| 457 |
| 458 updateSources(["app|foo.txt"]); |
| 459 |
| 460 // Wait for rewrite1 to pause and rewrite2 to finish. |
| 461 schedule(pumpEventQueue); |
| 462 |
| 463 removeSources(["app|foo.txt"]); |
| 464 |
| 465 // Make sure the removal is processed completely before we restart rewrite2. |
| 466 schedule(pumpEventQueue); |
| 467 rewrite1.resumeApply(); |
| 468 |
| 469 buildShouldSucceed(); |
| 470 expectNoAsset("app|foo.out1"); |
| 471 expectNoAsset("app|foo.out2"); |
| 472 }); |
| 473 |
| 474 test("a transformer in a later phase gets a slow secondary input from an " |
| 475 "earlier phase", () { |
| 476 var rewrite = new RewriteTransformer("in", "in"); |
| 477 initGraph({ |
| 478 "app|foo.in": "foo", |
| 479 "app|bar.txt": "foo.in" |
| 480 }, {"app": [ |
| 481 [rewrite], |
| 482 [new ManyToOneTransformer("txt")] |
| 483 ]}); |
| 484 |
| 485 rewrite.pauseApply(); |
| 486 updateSources(["app|foo.in", "app|bar.txt"]); |
| 487 expectAssetDoesNotComplete("app|bar.out"); |
| 488 |
| 489 rewrite.resumeApply(); |
| 490 expectAsset("app|bar.out", "foo.in"); |
| 491 buildShouldSucceed(); |
| 492 }); |
| 493 |
| 494 test("materializes a passed-through asset that was emitted before it was " |
| 495 "available", () { |
| 496 initGraph(["app|foo.in"], {"app": [ |
| 497 [new RewriteTransformer("txt", "txt")] |
| 498 ]}); |
| 499 |
| 500 pauseProvider(); |
| 501 updateSources(["app|foo.in"]); |
| 502 expectAssetDoesNotComplete("app|foo.in"); |
| 503 |
| 504 resumeProvider(); |
| 505 expectAsset("app|foo.in", "foo"); |
| 506 buildShouldSucceed(); |
| 507 }); |
| 508 |
| 509 test("re-runs if the primary input is invalidated before accessing", () { |
| 510 var transformer1 = new RewriteTransformer("txt", "mid"); |
| 511 var transformer2 = new RewriteTransformer("mid", "out"); |
| 512 |
| 513 initGraph([ |
| 514 "app|foo.txt" |
| 515 ], {"app": [ |
| 516 [transformer1], |
| 517 [transformer2] |
| 518 ]}); |
| 519 |
| 520 transformer2.pausePrimaryInput(); |
| 521 updateSources(["app|foo.txt"]); |
| 522 |
| 523 // Wait long enough to ensure that transformer1 has completed and |
| 524 // transformer2 has started. |
| 525 schedule(pumpEventQueue); |
| 526 |
| 527 // Update the source again so that transformer1 invalidates the primary |
| 528 // input of transformer2. |
| 529 transformer1.pauseApply(); |
| 530 modifyAsset("app|foo.txt", "new foo"); |
| 531 updateSources(["app|foo.txt"]); |
| 532 |
| 533 transformer2.resumePrimaryInput(); |
| 534 transformer1.resumeApply(); |
| 535 |
| 536 expectAsset("app|foo.out", "new foo.mid.out"); |
| 537 buildShouldSucceed(); |
| 538 |
| 539 expect(transformer1.numRuns, completion(equals(2))); |
| 540 expect(transformer2.numRuns, completion(equals(2))); |
| 541 }); |
| 542 |
| 543 // Regression test for issue 19038. |
| 544 test("a secondary input that's marked dirty followed by the primary input " |
| 545 "being synchronously marked dirty re-runs a transformer", () { |
| 546 // Issue 19038 was caused by the following sequence of events: |
| 547 // |
| 548 // * Several inputs are marked dirty at once, causing dirty events to |
| 549 // propagate synchronously throughout the transform graph. |
| 550 // |
| 551 // * A transform (ManyToOneTransformer in this test case) has a secondary |
| 552 // input ("one.in") and a primary input ("foo.txt") that will both be |
| 553 // marked dirty. |
| 554 // |
| 555 // * The secondary input is marked dirty before the primary input. This |
| 556 // causes the transform to start running `apply`. Since as far as it knows |
| 557 // its primary input is still available, it passes that input to `apply`. |
| 558 // |
| 559 // * Now the primary input is marked dirty. The transform node checks to see |
| 560 // if this primary input has already been added to the transform |
| 561 // controller. This is where the bug existed: the answer to this was |
| 562 // incorrectly "no" until after some asynchronous processing occurred. |
| 563 // |
| 564 // * Since the transform thought the primary input hadn't yet been passed to |
| 565 // the transform controller, it didn't bother restarting the transform, |
| 566 // causing the old output to be preserved incorrectly. |
| 567 initGraph({ |
| 568 "app|foo.txt": "one", |
| 569 "app|one.in": "1", |
| 570 "app|two.in": "2" |
| 571 }, {"app": [ |
| 572 // We need to use CheckContentTransformer here so that |
| 573 // ManyToOneTransformer reads its primary input from memory rather than |
| 574 // from the filesystem. If it read from the filesystem, it might |
| 575 // accidentally get the correct output despite accessing the incorrect |
| 576 // asset, which would cause false positives for the test. |
| 577 [new CheckContentTransformer(new RegExp("one|two"), ".in")], |
| 578 [new ManyToOneTransformer("txt")] |
| 579 ]}); |
| 580 |
| 581 updateSources(["app|foo.txt", "app|one.in", "app|two.in"]); |
| 582 expectAsset("app|foo.out", "1"); |
| 583 buildShouldSucceed(); |
| 584 |
| 585 modifyAsset("app|foo.txt", "two"); |
| 586 |
| 587 // It's important that "one.in" come first in this list, since |
| 588 // ManyToOneTransformer needs to see its secondary input change first. |
| 589 updateSources(["app|one.in", "app|foo.txt"]); |
| 590 |
| 591 expectAsset("app|foo.out", "2"); |
| 592 buildShouldSucceed(); |
| 593 }); |
| 594 } |
OLD | NEW |