1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.build.dependencyTracker 18 19 import java.io.File 20 import java.util.function.BiFunction 21 import org.gradle.api.Project 22 import org.gradle.api.Transformer 23 import org.gradle.api.plugins.ExtraPropertiesExtension 24 import org.gradle.api.provider.Provider 25 import org.gradle.api.specs.Spec 26 import org.gradle.testfixtures.ProjectBuilder 27 import org.hamcrest.CoreMatchers 28 import org.hamcrest.MatcherAssert 29 import org.junit.Before 30 import org.junit.Rule 31 import org.junit.Test 32 import org.junit.rules.TemporaryFolder 33 import org.junit.runner.RunWith 34 import org.junit.runners.JUnit4 35 36 @RunWith(JUnit4::class) 37 class AffectedModuleDetectorImplTest { 38 39 @Rule 40 @JvmField 41 val attachLogsRule = AttachLogsTestRule() 42 private val logger = attachLogsRule.logger 43 44 @Rule 45 @JvmField 46 val tmpFolder = TemporaryFolder() 47 48 @Rule 49 @JvmField 50 val tmpFolder2 = TemporaryFolder() 51 52 private lateinit var root: Project 53 private lateinit var p1: Project 54 private lateinit var p2: Project 55 private lateinit var p3: Project 56 private lateinit var p4: Project 57 private lateinit var p5: Project 58 private lateinit var p6: Project 59 private lateinit var p7: Project 60 private lateinit var p8: Project 61 private lateinit var p9: Project 62 private lateinit var p10: Project 63 private lateinit var p11: Project <lambda>null64 private val projectGraph by lazy { 65 ProjectGraph(root) 66 } <lambda>null67 private val dependencyTracker by lazy { 68 DependencyTracker(root, logger) 69 } 70 private val cobuiltTestPaths = setOf(setOf(":cobuilt1", ":cobuilt2")) 71 private val ignoredPaths = setOf("ignored/") 72 73 @Before initnull74 fun init() { 75 val tmpDir = tmpFolder.root 76 77 /* 78 79 Dummy project file tree: 80 81 root ----------------- 82 / | \ | | | | 83 p1 p7 p2 p8 p9 p10 p11 84 / \ 85 p3 p5 86 / \ 87 p4 p6 88 89 Dependency forest: 90 91 p1 p2 p7 p8 p9 p10 p11 92 / \ / \ 93 p3 p5 p6 94 / 95 p4 96 97 */ 98 99 root = ProjectBuilder.builder() 100 .withProjectDir(tmpDir) 101 .withName("root") 102 .build() 103 // Project Graph expects supportRootFolder. 104 (root.properties.get("ext") as ExtraPropertiesExtension).set("supportRootFolder", tmpDir) 105 p1 = ProjectBuilder.builder() 106 .withProjectDir(tmpDir.resolve("p1")) 107 .withName("p1") 108 .withParent(root) 109 .build() 110 p2 = ProjectBuilder.builder() 111 .withProjectDir(tmpDir.resolve("p2")) 112 .withName("p2") 113 .withParent(root) 114 .build() 115 p3 = ProjectBuilder.builder() 116 .withProjectDir(tmpDir.resolve("p1:p3")) 117 .withName("p3") 118 .withParent(p1) 119 .build() 120 val p3config = p3.configurations.create("p3config") 121 p3config.dependencies.add(p3.dependencies.project(mutableMapOf("path" to ":p1"))) 122 p4 = ProjectBuilder.builder() 123 .withProjectDir(tmpDir.resolve("p1:p3:p4")) 124 .withName("p4") 125 .withParent(p3) 126 .build() 127 val p4config = p4.configurations.create("p4config") 128 p4config.dependencies.add(p4.dependencies.project(mutableMapOf("path" to ":p1:p3"))) 129 p5 = ProjectBuilder.builder() 130 .withProjectDir(tmpDir.resolve("p2:p5")) 131 .withName("p5") 132 .withParent(p2) 133 .build() 134 val p5config = p5.configurations.create("p5config") 135 p5config.dependencies.add(p5.dependencies.project(mutableMapOf("path" to ":p2"))) 136 p5config.dependencies.add(p5.dependencies.project(mutableMapOf("path" to ":p1:p3"))) 137 p6 = ProjectBuilder.builder() 138 .withProjectDir(tmpDir.resolve("p1:p3:p6")) 139 .withName("p6") 140 .withParent(p3) 141 .build() 142 val p6config = p6.configurations.create("p6config") 143 p6config.dependencies.add(p6.dependencies.project(mutableMapOf("path" to ":p2"))) 144 p7 = ProjectBuilder.builder() 145 .withProjectDir(tmpDir.resolve("p7")) 146 .withName("p7") 147 .withParent(root) 148 .build() 149 p8 = ProjectBuilder.builder() 150 .withProjectDir(tmpDir.resolve("p8")) 151 .withName("cobuilt1") 152 .withParent(root) 153 .build() 154 p9 = ProjectBuilder.builder() 155 .withProjectDir(tmpDir.resolve("p9")) 156 .withName("cobuilt2") 157 .withParent(root) 158 .build() 159 p10 = ProjectBuilder.builder() 160 .withProjectDir(tmpDir.resolve("p10")) 161 .withName("benchmark") 162 .withParent(root) 163 .build() 164 p11 = ProjectBuilder.builder() 165 .withProjectDir(tmpDir.resolve("p11")) 166 .withName("placeholder-tests") 167 .withParent(root) 168 .build() 169 } 170 171 class TestProvider(private val list: List<String>) : Provider<List<String>> { getnull172 override fun get(): List<String> = list 173 override fun getOrNull(): List<String> = list 174 override fun isPresent(): Boolean = TODO("unused") 175 @Deprecated("Super method is deprecated") 176 override fun forUseAtConfigurationTime(): Provider<List<String>> = TODO("unused") 177 override fun <U : Any?, R : Any?> zip( 178 right: Provider<U>, 179 combiner: BiFunction<in List<String>, in U, out R?> 180 ): Provider<R> = TODO("unused") 181 override fun orElse(provider: Provider<out List<String>>): Provider<List<String>> { 182 TODO("unused") 183 } orElsenull184 override fun orElse(value: List<String>): Provider<List<String>> = TODO("unused") 185 override fun <S : Any?> flatMap( 186 transformer: Transformer<out Provider<out S>?, in List<String>> 187 ): Provider<S> = TODO("unused") 188 override fun filter(spec: Spec<in List<String>>): Provider<List<String>> = TODO("unused") 189 override fun <S : Any?> map( 190 transformer: Transformer<out S?, in List<String>> 191 ): Provider<S> = TODO("unused") 192 override fun getOrElse(defaultValue: List<String>): List<String> = TODO("unused") 193 } 194 195 @Test 196 fun noChangeCLs() { 197 val detector = AffectedModuleDetectorImpl( 198 projectGraph = projectGraph, 199 dependencyTracker = dependencyTracker, 200 logger = logger, 201 ignoreUnknownProjects = false, 202 cobuiltTestPaths = cobuiltTestPaths, 203 changedFilesProvider = TestProvider(emptyList()) 204 ) 205 MatcherAssert.assertThat( 206 detector.changedProjects, 207 CoreMatchers.`is`( 208 setOf(p11.path) 209 ) 210 ) 211 MatcherAssert.assertThat( 212 detector.dependentProjects, 213 CoreMatchers.`is`( 214 setOf(p11.path) 215 ) 216 ) 217 MatcherAssert.assertThat( 218 detector.buildAll, 219 CoreMatchers.`is`( 220 true 221 ) 222 ) 223 } 224 225 @Test changeInOnenull226 fun changeInOne() { 227 val detector = AffectedModuleDetectorImpl( 228 projectGraph = projectGraph, 229 dependencyTracker = dependencyTracker, 230 logger = logger, 231 ignoreUnknownProjects = false, 232 cobuiltTestPaths = cobuiltTestPaths, 233 changedFilesProvider = TestProvider( 234 listOf(convertToFilePath("p1", "foo.java")) 235 ) 236 ) 237 MatcherAssert.assertThat( 238 detector.changedProjects, 239 CoreMatchers.`is`( 240 setOf(p1.path, p11.path) 241 ) 242 ) 243 MatcherAssert.assertThat( 244 detector.dependentProjects, 245 CoreMatchers.`is`( 246 setOf(p3.path, p4.path, p5.path, p11.path) 247 ) 248 ) 249 } 250 251 @Test changeInTwonull252 fun changeInTwo() { 253 val detector = AffectedModuleDetectorImpl( 254 projectGraph = projectGraph, 255 dependencyTracker = dependencyTracker, 256 logger = logger, 257 ignoreUnknownProjects = false, 258 cobuiltTestPaths = cobuiltTestPaths, 259 changedFilesProvider = TestProvider( 260 listOf( 261 convertToFilePath("p1", "foo.java"), 262 convertToFilePath("p2", "bar.java") 263 ) 264 ) 265 ) 266 MatcherAssert.assertThat( 267 detector.changedProjects, 268 CoreMatchers.`is`( 269 setOf(p1.path, p2.path, p11.path) 270 ) 271 ) 272 MatcherAssert.assertThat( 273 detector.dependentProjects, 274 CoreMatchers.`is`( 275 setOf(p3.path, p4.path, p5.path, p6.path, p11.path) 276 ) 277 ) 278 } 279 280 @Test changeInRootnull281 fun changeInRoot() { 282 val detector = AffectedModuleDetectorImpl( 283 projectGraph = projectGraph, 284 dependencyTracker = dependencyTracker, 285 logger = logger, 286 ignoreUnknownProjects = false, 287 cobuiltTestPaths = cobuiltTestPaths, 288 changedFilesProvider = TestProvider( 289 listOf("foo.java") 290 ) 291 ) 292 MatcherAssert.assertThat( 293 detector.changedProjects, 294 CoreMatchers.`is`( 295 setOf(p11.path) 296 ) 297 ) 298 MatcherAssert.assertThat( 299 detector.dependentProjects, 300 CoreMatchers.`is`( 301 setOf(p11.path) 302 ) 303 ) 304 MatcherAssert.assertThat( 305 detector.buildAll, 306 CoreMatchers.`is`( 307 true 308 ) 309 ) 310 } 311 312 @Test changeInRootAndSubprojectnull313 fun changeInRootAndSubproject() { 314 val detector = AffectedModuleDetectorImpl( 315 projectGraph = projectGraph, 316 dependencyTracker = dependencyTracker, 317 logger = logger, 318 ignoreUnknownProjects = false, 319 cobuiltTestPaths = cobuiltTestPaths, 320 changedFilesProvider = TestProvider( 321 listOf("foo.java", convertToFilePath("p7", "bar.java")) 322 ) 323 ) 324 MatcherAssert.assertThat( 325 detector.changedProjects, 326 CoreMatchers.`is`( 327 setOf(p7.path, p11.path) 328 ) 329 ) 330 MatcherAssert.assertThat( 331 detector.dependentProjects, 332 CoreMatchers.`is`( 333 setOf(p11.path) 334 ) 335 ) 336 MatcherAssert.assertThat( 337 detector.buildAll, 338 CoreMatchers.`is`( 339 true 340 ) 341 ) 342 } 343 344 @Test changeInCobuiltnull345 fun changeInCobuilt() { 346 val detector = AffectedModuleDetectorImpl( 347 projectGraph = projectGraph, 348 dependencyTracker = dependencyTracker, 349 logger = logger, 350 ignoreUnknownProjects = false, 351 cobuiltTestPaths = cobuiltTestPaths, 352 changedFilesProvider = TestProvider( 353 listOf( 354 convertToFilePath( 355 "p8", "foo.java" 356 ) 357 ) 358 ) 359 ) 360 MatcherAssert.assertThat( 361 detector.changedProjects, 362 CoreMatchers.`is`( 363 setOf(p8.path, p9.path, p11.path) 364 ) 365 ) 366 MatcherAssert.assertThat( 367 detector.dependentProjects, 368 CoreMatchers.`is`( 369 setOf(p11.path) 370 ) 371 ) 372 } 373 374 @Test(expected = IllegalStateException::class) changeInCobuiltMissingCobuiltnull375 fun changeInCobuiltMissingCobuilt() { 376 val detector = AffectedModuleDetectorImpl( 377 projectGraph = projectGraph, 378 dependencyTracker = dependencyTracker, 379 logger = logger, 380 ignoreUnknownProjects = false, 381 cobuiltTestPaths = setOf(setOf(":cobuilt1", ":cobuilt2", ":cobuilt3")), 382 changedFilesProvider = TestProvider( 383 listOf( 384 convertToFilePath( 385 "p8", "foo.java" 386 ) 387 ) 388 ) 389 ) 390 // This should trigger IllegalStateException due to missing cobuilt3 391 detector.changedProjects 392 } 393 394 @Test changeInCobuiltAllCobuiltsMissingnull395 fun changeInCobuiltAllCobuiltsMissing() { 396 val detector = AffectedModuleDetectorImpl( 397 projectGraph = projectGraph, 398 dependencyTracker = dependencyTracker, 399 logger = logger, 400 ignoreUnknownProjects = false, 401 cobuiltTestPaths = setOf(setOf("cobuilt3", "cobuilt4", "cobuilt5")), 402 changedFilesProvider = TestProvider( 403 listOf( 404 convertToFilePath( 405 "p8", "foo.java" 406 ) 407 ) 408 ) 409 ) 410 // There should be no exception thrown here because *all* cobuilts are missing. 411 detector.changedProjects 412 } 413 414 @Test projectSubsetnull415 fun projectSubset() { 416 val detector = AffectedModuleDetectorImpl( 417 projectGraph = projectGraph, 418 dependencyTracker = dependencyTracker, 419 logger = logger, 420 ignoreUnknownProjects = false, 421 changedFilesProvider = TestProvider( 422 listOf(convertToFilePath("p1", "foo.java")) 423 ) 424 ) 425 // Verify expectations on affected projects 426 MatcherAssert.assertThat( 427 detector.changedProjects, 428 CoreMatchers.`is`( 429 setOf(p1.path, p11.path) 430 ) 431 ) 432 MatcherAssert.assertThat( 433 detector.dependentProjects, 434 CoreMatchers.`is`( 435 setOf(p3.path, p4.path, p5.path, p11.path) 436 ) 437 ) 438 // Test changed 439 MatcherAssert.assertThat( 440 detector.getSubset(p1.path), 441 CoreMatchers.`is`( 442 ProjectSubset.CHANGED_PROJECTS 443 ) 444 ) 445 // Test dependent 446 MatcherAssert.assertThat( 447 detector.getSubset(p3.path), 448 CoreMatchers.`is`( 449 ProjectSubset.DEPENDENT_PROJECTS 450 ) 451 ) 452 // Random unrelated project should return none 453 MatcherAssert.assertThat( 454 detector.getSubset(p7.path), 455 CoreMatchers.`is`( 456 ProjectSubset.NONE 457 ) 458 ) 459 } 460 461 @Test projectSubset_noChangedFilesnull462 fun projectSubset_noChangedFiles() { 463 val detector = AffectedModuleDetectorImpl( 464 projectGraph = projectGraph, 465 dependencyTracker = dependencyTracker, 466 logger = logger, 467 ignoreUnknownProjects = false, 468 changedFilesProvider = TestProvider(emptyList()) 469 ) 470 // Verify expectations on affected projects 471 MatcherAssert.assertThat( 472 detector.affectedProjects, 473 CoreMatchers.`is`( 474 setOf(p11.path) 475 ) 476 ) 477 // No changed files in postsubmit -> return all 478 MatcherAssert.assertThat( 479 detector.getSubset(p1.path), 480 CoreMatchers.`is`( 481 ProjectSubset.NONE 482 ) 483 ) 484 MatcherAssert.assertThat( 485 detector.getSubset(p3.path), 486 CoreMatchers.`is`( 487 ProjectSubset.NONE 488 ) 489 ) 490 // Only the placeholder test should return CHANGED_PROJECTS 491 MatcherAssert.assertThat( 492 detector.getSubset(p11.path), 493 CoreMatchers.`is`( 494 ProjectSubset.CHANGED_PROJECTS 495 ) 496 ) 497 MatcherAssert.assertThat( 498 detector.buildAll, 499 CoreMatchers.`is`( 500 true 501 ) 502 ) 503 } 504 505 @Test projectSubset_unknownChangedFilesnull506 fun projectSubset_unknownChangedFiles() { 507 val detector = AffectedModuleDetectorImpl( 508 projectGraph = projectGraph, 509 dependencyTracker = dependencyTracker, 510 logger = logger, 511 ignoreUnknownProjects = false, 512 changedFilesProvider = TestProvider( 513 listOf(convertToFilePath("unknown", "file.java")) 514 ) 515 ) 516 // Verify expectations on affected projects 517 MatcherAssert.assertThat( 518 detector.changedProjects, 519 CoreMatchers.`is`( 520 setOf(p11.path) 521 ) 522 ) 523 MatcherAssert.assertThat( 524 detector.dependentProjects, 525 CoreMatchers.`is`( 526 setOf(p11.path) 527 ) 528 ) 529 // Everything should return NONE in buildall case 530 MatcherAssert.assertThat( 531 detector.getSubset(p1.path), 532 CoreMatchers.`is`( 533 ProjectSubset.NONE 534 ) 535 ) 536 MatcherAssert.assertThat( 537 detector.getSubset(p3.path), 538 CoreMatchers.`is`( 539 ProjectSubset.NONE 540 ) 541 ) 542 // Only the placeholder test should return CHANGED_PROJECTS 543 MatcherAssert.assertThat( 544 detector.getSubset(p11.path), 545 CoreMatchers.`is`( 546 ProjectSubset.CHANGED_PROJECTS 547 ) 548 ) 549 } 550 551 @Test changeInPlaygroundCommonnull552 fun changeInPlaygroundCommon() { 553 val detector = AffectedModuleDetectorImpl( 554 projectGraph = projectGraph, 555 dependencyTracker = dependencyTracker, 556 logger = logger, 557 ignoreUnknownProjects = false, 558 cobuiltTestPaths = cobuiltTestPaths, 559 changedFilesProvider = TestProvider( 560 listOf("playground-common/tmp.kt") 561 ) 562 ) 563 MatcherAssert.assertThat( 564 detector.changedProjects, 565 CoreMatchers.`is`( 566 setOf(p11.path) 567 ) 568 ) 569 MatcherAssert.assertThat( 570 detector.buildAll, 571 CoreMatchers.`is`( 572 false 573 ) 574 ) 575 } 576 577 @Test changeInGithubConfignull578 fun changeInGithubConfig() { 579 val detector = AffectedModuleDetectorImpl( 580 projectGraph = projectGraph, 581 dependencyTracker = dependencyTracker, 582 logger = logger, 583 ignoreUnknownProjects = false, 584 cobuiltTestPaths = cobuiltTestPaths, 585 changedFilesProvider = TestProvider( 586 listOf(".github/workflows/bar.txt") 587 ) 588 ) 589 MatcherAssert.assertThat( 590 detector.changedProjects, 591 CoreMatchers.`is`( 592 setOf(p11.path) 593 ) 594 ) 595 MatcherAssert.assertThat( 596 detector.buildAll, 597 CoreMatchers.`is`( 598 false 599 ) 600 ) 601 } 602 603 @Test changeInPlaygroundCommonAndRootnull604 fun changeInPlaygroundCommonAndRoot() { 605 val detector = AffectedModuleDetectorImpl( 606 projectGraph = projectGraph, 607 dependencyTracker = dependencyTracker, 608 logger = logger, 609 ignoreUnknownProjects = false, 610 cobuiltTestPaths = cobuiltTestPaths, 611 changedFilesProvider = TestProvider( 612 listOf("playground-common/tmp.kt", "root.txt") 613 ) 614 ) 615 MatcherAssert.assertThat( 616 detector.buildAll, 617 CoreMatchers.`is`( 618 true 619 ) 620 ) 621 } 622 623 @Test Only ignored filenull624 fun `Only ignored file`() { 625 val detector = AffectedModuleDetectorImpl( 626 projectGraph = projectGraph, 627 dependencyTracker = dependencyTracker, 628 logger = logger, 629 ignoreUnknownProjects = false, 630 cobuiltTestPaths = cobuiltTestPaths, 631 ignoredPaths = ignoredPaths, 632 changedFilesProvider = TestProvider( 633 listOf(convertToFilePath("ignored", "example.txt")) 634 ) 635 ) 636 MatcherAssert.assertThat( 637 detector.buildAll, 638 CoreMatchers.`is`( 639 false 640 ) 641 ) 642 } 643 644 @Test Ignored file and changed filenull645 fun `Ignored file and changed file`() { 646 val detector = AffectedModuleDetectorImpl( 647 projectGraph = projectGraph, 648 dependencyTracker = dependencyTracker, 649 logger = logger, 650 ignoreUnknownProjects = false, 651 cobuiltTestPaths = cobuiltTestPaths, 652 ignoredPaths = ignoredPaths, 653 changedFilesProvider = TestProvider( 654 listOf( 655 convertToFilePath("ignored", "example.txt"), 656 convertToFilePath("p1", "foo.kt") 657 ) 658 ) 659 ) 660 MatcherAssert.assertThat( 661 detector.changedProjects, 662 CoreMatchers.`is`( 663 setOf(p1.path, p11.path) 664 ) 665 ) 666 MatcherAssert.assertThat( 667 detector.buildAll, 668 CoreMatchers.`is`( 669 false 670 ) 671 ) 672 } 673 674 @Test Ignored file and unknown filenull675 fun `Ignored file and unknown file`() { 676 val detector = AffectedModuleDetectorImpl( 677 projectGraph = projectGraph, 678 dependencyTracker = dependencyTracker, 679 logger = logger, 680 ignoreUnknownProjects = false, 681 cobuiltTestPaths = cobuiltTestPaths, 682 ignoredPaths = ignoredPaths, 683 changedFilesProvider = TestProvider( 684 listOf( 685 convertToFilePath("ignored", "example.txt"), 686 convertToFilePath("unknown", "foo.kt") 687 ) 688 ) 689 ) 690 MatcherAssert.assertThat( 691 detector.buildAll, 692 CoreMatchers.`is`( 693 true 694 ) 695 ) 696 } 697 698 @Test Ignored file, unknown file, and changed filenull699 fun `Ignored file, unknown file, and changed file`() { 700 val detector = AffectedModuleDetectorImpl( 701 projectGraph = projectGraph, 702 dependencyTracker = dependencyTracker, 703 logger = logger, 704 ignoreUnknownProjects = false, 705 cobuiltTestPaths = cobuiltTestPaths, 706 ignoredPaths = ignoredPaths, 707 changedFilesProvider = TestProvider( 708 listOf( 709 "ignored/eg.txt", 710 convertToFilePath("unknown", "foo.kt"), 711 convertToFilePath("p1", "bar.kt") 712 ) 713 ) 714 ) 715 MatcherAssert.assertThat( 716 detector.buildAll, 717 CoreMatchers.`is`( 718 true 719 ) 720 ) 721 } 722 723 // For both Linux/Windows convertToFilePathnull724 fun convertToFilePath(vararg list: String): String { 725 return list.toList().joinToString(File.separator) 726 } 727 } 728