1 /*
<lambda>null2  * Copyright 2023 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.baselineprofile.gradle.consumer
18 
19 import androidx.baselineprofile.gradle.utils.ANDROID_APPLICATION_PLUGIN
20 import androidx.baselineprofile.gradle.utils.ANDROID_LIBRARY_PLUGIN
21 import androidx.baselineprofile.gradle.utils.ANDROID_TEST_PLUGIN
22 import androidx.baselineprofile.gradle.utils.BaselineProfileProjectSetupRule
23 import androidx.baselineprofile.gradle.utils.EXPECTED_PROFILE_FOLDER
24 import androidx.baselineprofile.gradle.utils.Fixtures
25 import androidx.baselineprofile.gradle.utils.TestAgpVersion
26 import androidx.baselineprofile.gradle.utils.TestAgpVersion.TEST_AGP_VERSION_8_1_1
27 import androidx.baselineprofile.gradle.utils.TestAgpVersion.TEST_AGP_VERSION_8_3_1
28 import androidx.baselineprofile.gradle.utils.TestAgpVersion.TEST_AGP_VERSION_CURRENT
29 import androidx.baselineprofile.gradle.utils.VariantProfile
30 import androidx.baselineprofile.gradle.utils.build
31 import androidx.baselineprofile.gradle.utils.buildAndAssertThatOutput
32 import androidx.baselineprofile.gradle.utils.buildAndFailAndAssertThatOutput
33 import androidx.baselineprofile.gradle.utils.camelCase
34 import androidx.baselineprofile.gradle.utils.require
35 import androidx.baselineprofile.gradle.utils.requireInOrder
36 import androidx.baselineprofile.gradle.utils.toUri
37 import com.google.common.truth.Truth.assertThat
38 import com.google.common.truth.Truth.assertWithMessage
39 import java.io.File
40 import org.junit.Rule
41 import org.junit.Test
42 import org.junit.runner.RunWith
43 import org.junit.runners.Parameterized
44 
45 @RunWith(Parameterized::class)
46 class BaselineProfileConsumerPluginTest(private val agpVersion: TestAgpVersion) {
47 
48     companion object {
49         @Parameterized.Parameters(name = "agpVersion={0}")
50         @JvmStatic
51         fun parameters() = TestAgpVersion.all()
52     }
53 
54     @get:Rule
55     val projectSetup = BaselineProfileProjectSetupRule(forceAgpVersion = agpVersion.versionString)
56 
57     private val gradleRunner by lazy { projectSetup.consumer.gradleRunner }
58 
59     private fun baselineProfileFile(variantName: String) =
60         projectSetup.baselineProfileFile(variantName)
61 
62     private fun startupProfileFile(variantName: String) =
63         projectSetup.startupProfileFile(variantName)
64 
65     private fun mergedArtProfile(variantName: String) = projectSetup.mergedArtProfile(variantName)
66 
67     private fun readBaselineProfileFileContent(variantName: String) =
68         projectSetup.readBaselineProfileFileContent(variantName)
69 
70     private fun readStartupProfileFileContent(variantName: String) =
71         projectSetup.readStartupProfileFileContent(variantName)
72 
73     @Test
74     fun testGenerateTaskWithNoFlavorsForLibrary() {
75         projectSetup.consumer.setup(androidPlugin = ANDROID_LIBRARY_PLUGIN)
76         projectSetup.producer.setupWithoutFlavors(
77             releaseProfileLines =
78                 listOf(
79                     Fixtures.CLASS_1_METHOD_1,
80                     Fixtures.CLASS_1,
81                     Fixtures.CLASS_2_METHOD_1,
82                     Fixtures.CLASS_2
83                 ),
84             releaseStartupProfileLines =
85                 listOf(
86                     Fixtures.CLASS_3_METHOD_1,
87                     Fixtures.CLASS_3,
88                     Fixtures.CLASS_4_METHOD_1,
89                     Fixtures.CLASS_4
90                 )
91         )
92 
93         gradleRunner.build("generateBaselineProfile") {
94             val notFound =
95                 it.lines()
96                     .requireInOrder(
97                         "A baseline profile was generated for the variant `release`:",
98                         "${baselineProfileFile("main").toUri()}"
99                     )
100             assertThat(notFound).isEmpty()
101         }
102 
103         assertThat(readBaselineProfileFileContent("main"))
104             .containsExactly(
105                 Fixtures.CLASS_1,
106                 Fixtures.CLASS_1_METHOD_1,
107                 Fixtures.CLASS_2,
108                 Fixtures.CLASS_2_METHOD_1,
109                 Fixtures.CLASS_3_METHOD_1,
110                 Fixtures.CLASS_3,
111                 Fixtures.CLASS_4_METHOD_1,
112                 Fixtures.CLASS_4
113             )
114 
115         assertThat(startupProfileFile("main").exists()).isFalse()
116     }
117 
118     @Test
119     fun testGenerateTaskWithNoFlavorsForApplication() {
120         projectSetup.consumer.setup(androidPlugin = ANDROID_APPLICATION_PLUGIN)
121         projectSetup.producer.setupWithoutFlavors(
122             releaseProfileLines =
123                 listOf(
124                     Fixtures.CLASS_1_METHOD_1,
125                     Fixtures.CLASS_1,
126                     Fixtures.CLASS_2_METHOD_1,
127                     Fixtures.CLASS_2
128                 ),
129             releaseStartupProfileLines =
130                 listOf(
131                     Fixtures.CLASS_3_METHOD_1,
132                     Fixtures.CLASS_3,
133                     Fixtures.CLASS_4_METHOD_1,
134                     Fixtures.CLASS_4
135                 )
136         )
137 
138         gradleRunner.build("generateBaselineProfile") {
139             val notFound =
140                 it.lines()
141                     .requireInOrder(
142                         "A baseline profile was generated for the variant `release`:",
143                         "${baselineProfileFile("release").toUri()}",
144                         "A startup profile was generated for the variant `release`:",
145                         "${startupProfileFile("release").toUri()}"
146                     )
147             assertThat(notFound).isEmpty()
148         }
149 
150         assertThat(readBaselineProfileFileContent("release"))
151             .containsExactly(
152                 Fixtures.CLASS_1,
153                 Fixtures.CLASS_1_METHOD_1,
154                 Fixtures.CLASS_2,
155                 Fixtures.CLASS_2_METHOD_1,
156                 Fixtures.CLASS_3,
157                 Fixtures.CLASS_3_METHOD_1,
158                 Fixtures.CLASS_4,
159                 Fixtures.CLASS_4_METHOD_1,
160             )
161 
162         assertThat(readStartupProfileFileContent("release"))
163             .containsExactly(
164                 Fixtures.CLASS_3,
165                 Fixtures.CLASS_3_METHOD_1,
166                 Fixtures.CLASS_4,
167                 Fixtures.CLASS_4_METHOD_1,
168             )
169     }
170 
171     @Test
172     fun testGenerateTaskWithNoFlavorsForApplicationAndNoStartupProfile() {
173         projectSetup.consumer.setup(androidPlugin = ANDROID_APPLICATION_PLUGIN)
174         projectSetup.producer.setupWithoutFlavors(
175             releaseProfileLines =
176                 listOf(
177                     Fixtures.CLASS_1_METHOD_1,
178                     Fixtures.CLASS_1,
179                 ),
180             releaseStartupProfileLines = listOf()
181         )
182 
183         gradleRunner.withArguments("generateBaselineProfile", "--stacktrace").build()
184 
185         assertThat(readBaselineProfileFileContent("release"))
186             .containsExactly(
187                 Fixtures.CLASS_1,
188                 Fixtures.CLASS_1_METHOD_1,
189             )
190 
191         assertThat(startupProfileFile("release").exists()).isFalse()
192     }
193 
194     @Test
195     fun testGenerateTaskWithFlavorsAndDefaultMerge() {
196         projectSetup.consumer.setup(
197             androidPlugin = ANDROID_APPLICATION_PLUGIN,
198             flavors = true,
199             dependencyOnProducerProject = true
200         )
201         projectSetup.producer.setupWithFreeAndPaidFlavors(
202             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
203             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
204             freeReleaseStartupProfileLines = listOf(Fixtures.CLASS_3_METHOD_1, Fixtures.CLASS_3),
205             paidReleaseStartupProfileLines = listOf(Fixtures.CLASS_4_METHOD_1, Fixtures.CLASS_4),
206         )
207 
208         // Asserts that all per-variant, per-flavor and per-build type tasks are being generated.
209         gradleRunner.buildAndAssertThatOutput("tasks") {
210             contains("generateReleaseBaselineProfile - ")
211             contains("generateFreeReleaseBaselineProfile - ")
212             contains("generatePaidReleaseBaselineProfile - ")
213         }
214 
215         gradleRunner.build("generateReleaseBaselineProfile") {
216             arrayOf("freeRelease", "paidRelease").forEach { variantName ->
217                 val notFound =
218                     it.lines()
219                         .requireInOrder(
220                             "A baseline profile was generated for the variant `$variantName`:",
221                             "${baselineProfileFile(variantName).toUri()}",
222                             "A startup profile was generated for the variant `$variantName`:",
223                             "${startupProfileFile(variantName).toUri()}"
224                         )
225 
226                 assertWithMessage(
227                         """
228                 |The following lines in gradle output were not found:
229                 |${notFound.joinToString("\n")}
230                 |
231                 |Full gradle output:
232                 |$it
233             """
234                             .trimMargin()
235                     )
236                     .that(notFound)
237                     .isEmpty()
238             }
239         }
240 
241         assertThat(readBaselineProfileFileContent("freeRelease"))
242             .containsExactly(
243                 Fixtures.CLASS_1,
244                 Fixtures.CLASS_1_METHOD_1,
245                 Fixtures.CLASS_3,
246                 Fixtures.CLASS_3_METHOD_1,
247             )
248 
249         assertThat(readBaselineProfileFileContent("paidRelease"))
250             .containsExactly(
251                 Fixtures.CLASS_2,
252                 Fixtures.CLASS_2_METHOD_1,
253                 Fixtures.CLASS_4,
254                 Fixtures.CLASS_4_METHOD_1,
255             )
256     }
257 
258     @Test
259     fun testPluginAppliedToLibraryModule() {
260         projectSetup.producer.setup()
261         projectSetup.consumer.setup(
262             androidPlugin = ANDROID_LIBRARY_PLUGIN,
263             addAppTargetPlugin = false,
264             dependencyOnProducerProject = true
265         )
266         gradleRunner.withArguments("generateBaselineProfile", "--stacktrace").build()
267         // This should not fail.
268     }
269 
270     @Test
271     fun testPluginAppliedToNonApplicationAndNonLibraryModule() {
272         projectSetup.producer.setup()
273         projectSetup.consumer.setup(
274             androidPlugin = ANDROID_TEST_PLUGIN,
275             addAppTargetPlugin = false,
276             dependencyOnProducerProject = true
277         )
278 
279         gradleRunner.withArguments("generateReleaseBaselineProfile", "--stacktrace").buildAndFail()
280     }
281 
282     @Test
283     fun testSrcSetAreAddedToVariantsForApplications() {
284         projectSetup.producer.setupWithFreeAndPaidFlavors()
285         projectSetup.consumer.setup(
286             androidPlugin = ANDROID_APPLICATION_PLUGIN,
287             flavors = true,
288             additionalGradleCodeBlock =
289                 """
290                 androidComponents {
291                     onVariants(selector()) { variant ->
292                         tasks.register(variant.name + "Sources", DisplaySourceSets) { t ->
293                             t.srcs.set(variant.sources.baselineProfiles.all)
294                         }
295                     }
296                 }
297             """
298                     .trimIndent()
299         )
300 
301         data class VariantExpectedSrcSets(val variantName: String, val expectedDirs: List<String>)
302 
303         fun variantBaselineProfileSrcSetDir(variantName: String): Array<String> {
304             return (listOf("src/$variantName/baselineProfiles")).toTypedArray()
305         }
306 
307         arrayOf(
308                 VariantExpectedSrcSets(
309                     variantName = "freeRelease",
310                     expectedDirs =
311                         listOf(
312                             "src/main/baselineProfiles",
313                             "src/free/baselineProfiles",
314                             "src/release/baselineProfiles",
315                             *variantBaselineProfileSrcSetDir("freeRelease"),
316                             "src/freeRelease/generated/baselineProfiles",
317                         )
318                 ),
319                 VariantExpectedSrcSets(
320                     variantName = "paidRelease",
321                     expectedDirs =
322                         listOf(
323                             "src/main/baselineProfiles",
324                             "src/paid/baselineProfiles",
325                             "src/release/baselineProfiles",
326                             *variantBaselineProfileSrcSetDir("paidRelease"),
327                             "src/paidRelease/generated/baselineProfiles",
328                         )
329                 ),
330                 *(listOf(
331                         VariantExpectedSrcSets(
332                             variantName = "freeBenchmarkRelease",
333                             expectedDirs =
334                                 listOf(
335                                     "src/main/baselineProfiles",
336                                     "src/free/baselineProfiles",
337                                     "src/benchmarkRelease/baselineProfiles",
338                                     "src/freeBenchmarkRelease/baselineProfiles",
339                                     "src/freeRelease/generated/baselineProfiles",
340                                 )
341                         ),
342                         VariantExpectedSrcSets(
343                             variantName = "paidBenchmarkRelease",
344                             expectedDirs =
345                                 listOf(
346                                     "src/main/baselineProfiles",
347                                     "src/paid/baselineProfiles",
348                                     "src/benchmarkRelease/baselineProfiles",
349                                     "src/paidBenchmarkRelease/baselineProfiles",
350                                     "src/paidRelease/generated/baselineProfiles",
351                                 )
352                         )
353                     ))
354                     .toTypedArray()
355             )
356             .forEach {
357                 val expected =
358                     it.expectedDirs
359                         .map { dir -> File(projectSetup.consumer.rootDir, dir) }
360                         .onEach { f ->
361                             // Expected src set location. Note that src sets are not added if the
362                             // folder does
363                             // not exist so we need to create it.
364                             f.mkdirs()
365                             f.deleteOnExit()
366                         }
367 
368                 gradleRunner.buildAndAssertThatOutput("${it.variantName}Sources") {
369                     expected.forEach { e -> contains(e.absolutePath) }
370                 }
371             }
372     }
373 
374     @Test
375     fun testWhenPluginIsAppliedAndNoDependencyIsSetShouldFailWithErrorMsg() {
376         projectSetup.consumer.setup(
377             androidPlugin = ANDROID_APPLICATION_PLUGIN,
378             flavors = false,
379             dependencyOnProducerProject = false
380         )
381         gradleRunner.build("generateReleaseBaselineProfile", "--stacktrace") {
382             assertThat(it.replace("\n", " "))
383                 .contains(
384                     "The baseline profile consumer plugin is applied to this module but no " +
385                         "dependency has been set for variant `release`"
386                 )
387         }
388     }
389 
390     @Test
391     fun testExperimentalPropertiesNotSet() {
392         projectSetup.producer.setupWithFreeAndPaidFlavors(
393             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
394             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2)
395         )
396         projectSetup.consumer.setup(
397             androidPlugin = ANDROID_LIBRARY_PLUGIN,
398             dependencyOnProducerProject = true,
399             flavors = true,
400             buildTypeAnotherRelease = true
401         )
402 
403         arrayOf(
404                 "printExperimentalPropertiesForVariantFreeRelease",
405                 "printExperimentalPropertiesForVariantPaidRelease",
406                 "printExperimentalPropertiesForVariantFreeAnotherRelease",
407                 "printExperimentalPropertiesForVariantPaidAnotherRelease",
408             )
409             .forEach {
410                 gradleRunner.buildAndAssertThatOutput(it) {
411                     doesNotContain("android.experimental.art-profile-r8-rewriting=")
412                     doesNotContain("android.experimental.r8.dex-startup-optimization=")
413                 }
414             }
415     }
416 
417     @Test
418     fun testFilterAndSortAndMerge() {
419         projectSetup.consumer.setup(
420             androidPlugin = ANDROID_LIBRARY_PLUGIN,
421             flavors = true,
422             baselineProfileBlock =
423                 """
424                 filter {
425                     include("com.sample.Utils")
426                 }
427             """
428                     .trimIndent()
429         )
430         projectSetup.producer.setupWithFreeAndPaidFlavors(
431             freeReleaseProfileLines =
432                 listOf(
433                     Fixtures.CLASS_1_METHOD_1,
434                     Fixtures.CLASS_1_METHOD_2,
435                     Fixtures.CLASS_1,
436                 ),
437             paidReleaseProfileLines =
438                 listOf(
439                     Fixtures.CLASS_2_METHOD_1,
440                     Fixtures.CLASS_2_METHOD_2,
441                     Fixtures.CLASS_2_METHOD_3,
442                     Fixtures.CLASS_2_METHOD_4,
443                     Fixtures.CLASS_2_METHOD_5,
444                     Fixtures.CLASS_2,
445                 )
446         )
447 
448         gradleRunner.withArguments("generateBaselineProfile", "--stacktrace").build()
449 
450         // In the final output there should be :
451         //  - one single file in src/main/generated/baselineProfiles (because this is a library).
452         //  - There should be only the Utils class [CLASS_2] because of the include filter.
453         //  - The method `someOtherMethod` [CLASS_2_METHOD_3] should be included only once
454         //      (despite being included multiple times with different flags).
455         assertThat(readBaselineProfileFileContent("main"))
456             .containsExactly(
457                 Fixtures.CLASS_2,
458                 Fixtures.CLASS_2_METHOD_1,
459                 Fixtures.CLASS_2_METHOD_2,
460                 Fixtures.CLASS_2_METHOD_3,
461             )
462     }
463 
464     @Test
465     fun testFilterPerVariant() {
466         projectSetup.consumer.setup(
467             androidPlugin = ANDROID_APPLICATION_PLUGIN,
468             flavors = true,
469             baselineProfileBlock =
470                 """
471                 filter {
472                     include("com.sample.Activity")
473                 }
474                 variants {
475                     freeRelease {
476                         filter { include("com.sample.Utils") }
477                     }
478                     paidRelease {
479                         filter { include("com.sample.Fragment") }
480                     }
481                 }
482             """
483                     .trimIndent()
484         )
485 
486         val commonProfile =
487             listOf(
488                 Fixtures.CLASS_1,
489                 Fixtures.CLASS_1_METHOD_1,
490                 Fixtures.CLASS_1_METHOD_2,
491                 Fixtures.CLASS_2,
492                 Fixtures.CLASS_2_METHOD_1,
493                 Fixtures.CLASS_2_METHOD_2,
494                 Fixtures.CLASS_2_METHOD_3,
495                 Fixtures.CLASS_3,
496                 Fixtures.CLASS_3_METHOD_1,
497             )
498         projectSetup.producer.setupWithFreeAndPaidFlavors(
499             freeReleaseProfileLines = commonProfile,
500             paidReleaseProfileLines = commonProfile,
501         )
502 
503         gradleRunner.withArguments("generateBaselineProfile", "--stacktrace").build()
504 
505         assertThat(readBaselineProfileFileContent("freeRelease"))
506             .containsExactly(
507                 Fixtures.CLASS_1,
508                 Fixtures.CLASS_1_METHOD_1,
509                 Fixtures.CLASS_1_METHOD_2,
510                 Fixtures.CLASS_2,
511                 Fixtures.CLASS_2_METHOD_1,
512                 Fixtures.CLASS_2_METHOD_2,
513                 Fixtures.CLASS_2_METHOD_3,
514             )
515         assertThat(readBaselineProfileFileContent("paidRelease"))
516             .containsExactly(
517                 Fixtures.CLASS_1,
518                 Fixtures.CLASS_1_METHOD_1,
519                 Fixtures.CLASS_1_METHOD_2,
520                 Fixtures.CLASS_3,
521                 Fixtures.CLASS_3_METHOD_1,
522             )
523     }
524 
525     @Test
526     fun testSaveInSrcTrueAndAutomaticGenerationDuringBuildTrue() {
527         projectSetup.consumer.setup(
528             androidPlugin = ANDROID_APPLICATION_PLUGIN,
529             flavors = true,
530             baselineProfileBlock =
531                 """
532                 saveInSrc = true
533                 automaticGenerationDuringBuild = true
534             """
535                     .trimIndent()
536         )
537         projectSetup.producer.setupWithFreeAndPaidFlavors(
538             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
539             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
540         )
541 
542         // Asserts that assembling release triggers generation of profile
543         gradleRunner.build("assembleFreeRelease", "--dry-run") {
544             val notFound =
545                 it.lines()
546                     .requireInOrder(
547                         ":${projectSetup.consumer.name}:mergeFreeReleaseBaselineProfile",
548                         ":${projectSetup.consumer.name}:copyFreeReleaseBaselineProfileIntoSrc",
549                         ":${projectSetup.consumer.name}:mergeFreeReleaseArtProfile",
550                         ":${projectSetup.consumer.name}:compileFreeReleaseArtProfile",
551                         ":${projectSetup.consumer.name}:assembleFreeRelease"
552                     )
553             assertThat(notFound).isEmpty()
554         }
555 
556         // Asserts that the profile is generated in the src folder
557         gradleRunner.build("generateFreeReleaseBaselineProfile") {
558             assertThat(readBaselineProfileFileContent("freeRelease"))
559                 .containsExactly(
560                     Fixtures.CLASS_1,
561                     Fixtures.CLASS_1_METHOD_1,
562                 )
563         }
564     }
565 
566     @Test
567     fun testSaveInSrcTrueAndAutomaticGenerationDuringBuildFalse() {
568         projectSetup.consumer.setup(
569             androidPlugin = ANDROID_APPLICATION_PLUGIN,
570             flavors = true,
571             baselineProfileBlock =
572                 """
573                 saveInSrc = true
574                 automaticGenerationDuringBuild = false
575             """
576                     .trimIndent()
577         )
578         projectSetup.producer.setupWithFreeAndPaidFlavors(
579             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
580             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
581         )
582 
583         // Asserts that assembling release does not trigger generation of profile
584         gradleRunner.buildAndAssertThatOutput("assembleFreeRelease", "--dry-run") {
585             arrayOf("mergeFreeReleaseBaselineProfile", "copyFreeReleaseBaselineProfileIntoSrc")
586                 .forEach { doesNotContain(":${projectSetup.consumer.name}:$it") }
587             arrayOf(
588                     "mergeFreeReleaseArtProfile",
589                     "compileFreeReleaseArtProfile",
590                     "assembleFreeRelease"
591                 )
592                 .forEach { contains(":${projectSetup.consumer.name}:$it") }
593         }
594 
595         // Asserts that the profile is generated in the src folder
596         gradleRunner.build("generateFreeReleaseBaselineProfile") {
597             assertThat(readBaselineProfileFileContent("freeRelease"))
598                 .containsExactly(
599                     Fixtures.CLASS_1,
600                     Fixtures.CLASS_1_METHOD_1,
601                 )
602         }
603     }
604 
605     @Test
606     fun testSaveInSrcFalseAndAutomaticGenerationDuringBuildTrue() {
607         projectSetup.consumer.setup(
608             androidPlugin = ANDROID_APPLICATION_PLUGIN,
609             flavors = true,
610             baselineProfileBlock =
611                 """
612                 saveInSrc = false
613                 automaticGenerationDuringBuild = true
614             """
615                     .trimIndent()
616         )
617         projectSetup.producer.setupWithFreeAndPaidFlavors(
618             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
619             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
620             freeReleaseStartupProfileLines = listOf(Fixtures.CLASS_3_METHOD_1, Fixtures.CLASS_3),
621             paidReleaseStartupProfileLines = listOf(Fixtures.CLASS_4_METHOD_1, Fixtures.CLASS_4),
622         )
623 
624         // Asserts that assembling release triggers generation of profile
625         gradleRunner.build("assembleFreeRelease", "--dry-run") {
626 
627             // Assert sequence of tasks is found
628             val notFound =
629                 it.lines()
630                     .requireInOrder(
631                         ":${projectSetup.consumer.name}:mergeFreeReleaseBaselineProfile",
632                         ":${projectSetup.consumer.name}:mergeFreeReleaseArtProfile",
633                         ":${projectSetup.consumer.name}:compileFreeReleaseArtProfile",
634                         ":${projectSetup.consumer.name}:assembleFreeRelease"
635                     )
636             assertThat(notFound).isEmpty()
637 
638             // Asserts that the copy task is disabled, because of `saveInSrc` set to false.
639             assertThat(it)
640                 .doesNotContain(
641                     ":${projectSetup.consumer.name}:copyFreeReleaseBaselineProfileIntoSrc"
642                 )
643         }
644 
645         // Asserts that the profile is not generated in the src folder
646         gradleRunner.build("generateFreeReleaseBaselineProfile") {
647             // Note that here the profiles are generated in the intermediates so the output does
648             // not matter.
649             val notFound =
650                 it.lines()
651                     .requireInOrder(
652                         "A baseline profile was generated for the variant `freeRelease`:",
653                         "A startup profile was generated for the variant `freeRelease`:",
654                     )
655             assertThat(notFound).isEmpty()
656         }
657 
658         assertThat(baselineProfileFile("freeRelease").exists()).isFalse()
659     }
660 
661     @Test
662     fun testSaveInSrcFalseAndAutomaticGenerationDuringBuildFalse() {
663         projectSetup.producer.setup()
664         projectSetup.consumer.setup(
665             androidPlugin = ANDROID_APPLICATION_PLUGIN,
666             baselineProfileBlock =
667                 """
668                 saveInSrc = false
669                 automaticGenerationDuringBuild = false
670             """
671                     .trimIndent()
672         )
673         gradleRunner
674             .withArguments("generateReleaseBaselineProfile", "--stacktrace")
675             .buildAndFail()
676             .output
677             .replace(System.lineSeparator(), " ")
678             .also {
679                 assertThat(it)
680                     .contains(
681                         "The current configuration of flags `saveInSrc` and " +
682                             "`automaticGenerationDuringBuild` is not supported"
683                     )
684             }
685     }
686 
687     @Test
688     fun testWhenFiltersFilterOutAllTheProfileRules() {
689         projectSetup.consumer.setup(
690             androidPlugin = ANDROID_LIBRARY_PLUGIN,
691             baselineProfileBlock =
692                 """
693                 filter { include("nothing.**") }
694             """
695                     .trimIndent()
696         )
697         projectSetup.producer.setupWithoutFlavors(
698             releaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
699         )
700 
701         gradleRunner
702             .withArguments("generateBaselineProfile", "--stacktrace")
703             .buildAndFail()
704             .output
705             .replace(System.lineSeparator(), " ")
706             .also {
707                 assertThat(it)
708                     .contains(
709                         "The baseline profile consumer plugin is configured with filters that " +
710                             "exclude all the profile rules"
711                     )
712             }
713     }
714 
715     @Test
716     fun testWhenProfileProducerProducesEmptyProfile() {
717         projectSetup.consumer.setup(androidPlugin = ANDROID_LIBRARY_PLUGIN)
718         projectSetup.producer.setupWithoutFlavors(releaseProfileLines = listOf())
719         gradleRunner.buildAndAssertThatOutput("generateBaselineProfile") {
720             contains("No baseline profile rules were generated")
721         }
722     }
723 
724     @Test
725     fun testVariantConfigurationOverrideForFlavors() {
726         projectSetup.producer.setupWithFreeAndPaidFlavors(
727             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
728             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
729         )
730         projectSetup.consumer.setup(
731             androidPlugin = ANDROID_LIBRARY_PLUGIN,
732             flavors = true,
733             baselineProfileBlock =
734                 """
735 
736                 // Global configuration
737                 saveInSrc = true
738                 automaticGenerationDuringBuild = false
739                 baselineProfileOutputDir = "generated/baselineProfiles"
740                 mergeIntoMain = true
741 
742                 // Per variant configuration overrides global configuration.
743                 variants {
744                     free {
745                         saveInSrc = false
746                         automaticGenerationDuringBuild = true
747                         baselineProfileOutputDir = "somefolder"
748                         mergeIntoMain = false
749                     }
750                     paidRelease {
751                         saveInSrc = false
752                         automaticGenerationDuringBuild = true
753                         baselineProfileOutputDir = "someOtherfolder"
754                         mergeIntoMain = false
755                     }
756                 }
757 
758             """
759                     .trimIndent()
760         )
761 
762         gradleRunner.buildAndAssertThatOutput(
763             "printBaselineProfileExtensionForVariantFreeRelease"
764         ) {
765             contains("saveInSrc=`false`")
766             contains("automaticGenerationDuringBuild=`true`")
767             contains("baselineProfileOutputDir=`somefolder`")
768             contains("mergeIntoMain=`false`")
769         }
770 
771         gradleRunner.buildAndAssertThatOutput(
772             "printBaselineProfileExtensionForVariantPaidRelease"
773         ) {
774             contains("saveInSrc=`false`")
775             contains("automaticGenerationDuringBuild=`true`")
776             contains("baselineProfileOutputDir=`someOtherfolder`")
777             contains("mergeIntoMain=`false`")
778         }
779     }
780 
781     @Test
782     fun testVariantConfigurationOverrideForBuildTypes() {
783         projectSetup.producer.setupWithFreeAndPaidFlavors(
784             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
785             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
786         )
787         projectSetup.consumer.setup(
788             androidPlugin = ANDROID_APPLICATION_PLUGIN,
789             flavors = true,
790             baselineProfileBlock =
791                 """
792 
793                 // Global configuration
794                 saveInSrc = true
795                 automaticGenerationDuringBuild = false
796                 baselineProfileOutputDir = "generated/baselineProfiles"
797                 mergeIntoMain = true
798 
799                 // Per variant configuration overrides global configuration.
800                 variants {
801                     release {
802                         saveInSrc = false
803                         automaticGenerationDuringBuild = true
804                         baselineProfileOutputDir = "myReleaseFolder"
805                         mergeIntoMain = false
806                     }
807                     paidRelease {
808                         saveInSrc = false
809                         automaticGenerationDuringBuild = true
810                         baselineProfileOutputDir = "someOtherfolder"
811                         mergeIntoMain = false
812                     }
813                 }
814 
815             """
816                     .trimIndent()
817         )
818 
819         gradleRunner.buildAndAssertThatOutput(
820             "printBaselineProfileExtensionForVariantFreeRelease"
821         ) {
822             contains("saveInSrc=`false`")
823             contains("automaticGenerationDuringBuild=`true`")
824             contains("baselineProfileOutputDir=`myReleaseFolder`")
825             contains("mergeIntoMain=`false`")
826         }
827 
828         gradleRunner.buildAndAssertThatOutput(
829             "printBaselineProfileExtensionForVariantPaidRelease"
830         ) {
831             contains("saveInSrc=`false`")
832             contains("automaticGenerationDuringBuild=`true`")
833             contains("baselineProfileOutputDir=`someOtherfolder`")
834             contains("mergeIntoMain=`false`")
835         }
836     }
837 
838     @Test
839     fun testVariantConfigurationOverrideForFlavorsAndBuildType() {
840         projectSetup.producer.setupWithFreeAndPaidFlavors(
841             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
842             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
843         )
844         projectSetup.consumer.setup(
845             androidPlugin = ANDROID_LIBRARY_PLUGIN,
846             flavors = true,
847             baselineProfileBlock =
848                 """
849                 variants {
850                     free {
851                         saveInSrc = true
852                     }
853                     release {
854                         saveInSrc = false
855                     }
856                 }
857 
858             """
859                     .trimIndent()
860         )
861         gradleRunner
862             .withArguments("printBaselineProfileExtensionForVariantFreeRelease", "--stacktrace")
863             .buildAndFail()
864             .output
865             .let {
866                 assertThat(it)
867                     .contains("The per-variant configuration for baseline profiles is ambiguous")
868             }
869     }
870 
871     @Test
872     fun testVariantDependenciesWithFlavors() {
873         projectSetup.producer.setupWithFreeAndPaidFlavors(
874             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
875             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
876         )
877 
878         // In this setup no dependency is being added through the dependency block.
879         // Instead dependencies are being added through per-variant configuration block.
880         projectSetup.consumer.setup(
881             androidPlugin = ANDROID_APPLICATION_PLUGIN,
882             flavors = true,
883             dependencyOnProducerProject = false,
884             baselineProfileBlock =
885                 """
886                 variants {
887                     free {
888                         from(project(":${projectSetup.producer.name}"))
889                     }
890                     paid {
891                         from(project(":${projectSetup.producer.name}"))
892                     }
893                 }
894 
895             """
896                     .trimIndent()
897         )
898         gradleRunner.withArguments("generateReleaseBaselineProfile", "--stacktrace").build()
899 
900         assertThat(readBaselineProfileFileContent("freeRelease"))
901             .containsExactly(
902                 Fixtures.CLASS_1,
903                 Fixtures.CLASS_1_METHOD_1,
904             )
905         assertThat(readBaselineProfileFileContent("paidRelease"))
906             .containsExactly(
907                 Fixtures.CLASS_2,
908                 Fixtures.CLASS_2_METHOD_1,
909             )
910     }
911 
912     @Test
913     fun testPartialResults() {
914         projectSetup.consumer.setup(androidPlugin = ANDROID_APPLICATION_PLUGIN)
915 
916         // Function to setup the producer, run the generate profile command and assert output
917         fun setupProducerGenerateAndAssert(
918             partial: Boolean,
919             generatedProfiles: Map<String, List<String>>,
920             actualProfile: List<String>
921         ) {
922             projectSetup.producer.setup(
923                 variantProfiles =
924                     listOf(
925                         VariantProfile(
926                             flavor = null,
927                             buildType = "release",
928                             profileFileLines = generatedProfiles
929                         )
930                     )
931             )
932 
933             val args =
934                 listOfNotNull(
935                     "generateBaselineProfile",
936                     if (partial) "-Pandroid.testInstrumentationRunnerArguments.class=someClass"
937                     else null
938                 )
939 
940             projectSetup.consumer.gradleRunner.build(*args.toTypedArray()) {}
941 
942             assertThat(readBaselineProfileFileContent("release"))
943                 .containsExactly(*actualProfile.toTypedArray())
944         }
945 
946         // Full generation, 2 new tests.
947         setupProducerGenerateAndAssert(
948             partial = false,
949             generatedProfiles =
950                 mapOf(
951                     "myTest1" to listOf(Fixtures.CLASS_1, Fixtures.CLASS_1_METHOD_1),
952                     "myTest2" to listOf(Fixtures.CLASS_2, Fixtures.CLASS_2_METHOD_1)
953                 ),
954             actualProfile =
955                 listOf(
956                     Fixtures.CLASS_1,
957                     Fixtures.CLASS_1_METHOD_1,
958                     Fixtures.CLASS_2,
959                     Fixtures.CLASS_2_METHOD_1
960                 )
961         )
962 
963         // Partial generation, modify 1 test.
964         setupProducerGenerateAndAssert(
965             partial = true,
966             generatedProfiles =
967                 mapOf("myTest1" to listOf(Fixtures.CLASS_3, Fixtures.CLASS_3_METHOD_1)),
968             actualProfile =
969                 listOf(
970                     Fixtures.CLASS_3,
971                     Fixtures.CLASS_3_METHOD_1,
972                     Fixtures.CLASS_2,
973                     Fixtures.CLASS_2_METHOD_1
974                 )
975         )
976 
977         // Partial generation, add 1 test.
978         setupProducerGenerateAndAssert(
979             partial = true,
980             generatedProfiles =
981                 mapOf("myTest3" to listOf(Fixtures.CLASS_4, Fixtures.CLASS_4_METHOD_1)),
982             actualProfile =
983                 listOf(
984                     Fixtures.CLASS_3,
985                     Fixtures.CLASS_3_METHOD_1,
986                     Fixtures.CLASS_4,
987                     Fixtures.CLASS_4_METHOD_1,
988                     Fixtures.CLASS_2,
989                     Fixtures.CLASS_2_METHOD_1
990                 )
991         )
992 
993         // Full generation, 2 new tests.
994         setupProducerGenerateAndAssert(
995             partial = false,
996             generatedProfiles =
997                 mapOf(
998                     "myTest1-new" to listOf(Fixtures.CLASS_1, Fixtures.CLASS_1_METHOD_1),
999                     "myTest2-new" to listOf(Fixtures.CLASS_2, Fixtures.CLASS_2_METHOD_1)
1000                 ),
1001             actualProfile =
1002                 listOf(
1003                     Fixtures.CLASS_1,
1004                     Fixtures.CLASS_1_METHOD_1,
1005                     Fixtures.CLASS_2,
1006                     Fixtures.CLASS_2_METHOD_1
1007                 )
1008         )
1009     }
1010 
1011     @Test
1012     fun testBaselineProfileIsInMergeArtProfileIntermediate() {
1013         projectSetup.consumer.setup(
1014             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1015             flavors = true,
1016             baselineProfileBlock =
1017                 """
1018                 saveInSrc = true
1019                 automaticGenerationDuringBuild = true
1020             """
1021                     .trimIndent()
1022         )
1023 
1024         data class VariantAndProfile(val variantName: String, val profile: List<String>)
1025 
1026         val freeRelease =
1027             VariantAndProfile(
1028                 variantName = "freeRelease",
1029                 profile =
1030                     listOf(
1031                         Fixtures.CLASS_1,
1032                         Fixtures.CLASS_1_METHOD_1,
1033                         Fixtures.CLASS_1_METHOD_2,
1034                         Fixtures.CLASS_3,
1035                         Fixtures.CLASS_3_METHOD_1,
1036                     )
1037             )
1038         val paidRelease =
1039             VariantAndProfile(
1040                 variantName = "paidRelease",
1041                 profile =
1042                     listOf(
1043                         Fixtures.CLASS_1,
1044                         Fixtures.CLASS_1_METHOD_1,
1045                         Fixtures.CLASS_1_METHOD_2,
1046                         Fixtures.CLASS_2,
1047                         Fixtures.CLASS_2_METHOD_1,
1048                         Fixtures.CLASS_2_METHOD_2,
1049                         Fixtures.CLASS_2_METHOD_3,
1050                     )
1051             )
1052         projectSetup.producer.setupWithFreeAndPaidFlavors(
1053             freeReleaseProfileLines = freeRelease.profile,
1054             paidReleaseProfileLines = paidRelease.profile,
1055         )
1056 
1057         val variants = arrayOf(freeRelease, paidRelease)
1058         val tasks = variants.map { camelCase("merge", it.variantName, "ArtProfile") }
1059         gradleRunner.build(*(tasks.toTypedArray())) {}
1060 
1061         variants.forEach {
1062             val notFound =
1063                 mergedArtProfile(it.variantName).readLines().require(*(it.profile).toTypedArray())
1064             assertThat(notFound).isEmpty()
1065         }
1066     }
1067 
1068     @Test
1069     fun testMultidimensionalFlavorsAndMatchingFallbacks() {
1070         projectSetup.consumer.setupWithBlocks(
1071             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1072             flavorsBlock =
1073                 """
1074                 flavorDimensions = ["tier", "color"]
1075                 free { dimension "tier" }
1076                 red { dimension "color" }
1077                 paid {
1078                     dimension "tier"
1079                     matchingFallbacks += "free"
1080                 }
1081                 blue {
1082                     dimension "color"
1083                     matchingFallbacks += "red"
1084                 }
1085             """
1086                     .trimIndent(),
1087             buildTypesBlock = "",
1088             dependencyOnProducerProject = false,
1089             dependenciesBlock =
1090                 """
1091                 implementation(project(":${projectSetup.dependency.name}"))
1092             """
1093                     .trimIndent(),
1094             baselineProfileBlock =
1095                 """
1096                 variants {
1097                     free { from(project(":${projectSetup.producer.name}")) }
1098                     red { from(project(":${projectSetup.producer.name}")) }
1099                     paid { from(project(":${projectSetup.producer.name}")) }
1100                     // blue is already covered by the intersection of the other dimensions so no
1101                     // need to specify it.
1102                 }
1103 
1104             """
1105                     .trimIndent()
1106         )
1107         projectSetup.producer.setup(
1108             variantProfiles =
1109                 listOf(
1110                     VariantProfile(
1111                         flavorDimensions =
1112                             mapOf(
1113                                 "tier" to "free",
1114                                 "color" to "red",
1115                             ),
1116                         buildType = "release",
1117                         profileFileLines =
1118                             mapOf(
1119                                 "some-test-output" to
1120                                     listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
1121                             ),
1122                         startupFileLines =
1123                             mapOf(
1124                                 "some-startup-test-output" to
1125                                     listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
1126                             ),
1127                     )
1128                 )
1129         )
1130 
1131         arrayOf("freeRedRelease", "freeBlueRelease", "paidRedRelease", "paidBlueRelease").forEach {
1132             variantName ->
1133             gradleRunner.build(camelCase("generate", variantName, "baselineProfile")) {
1134                 assertThat(readBaselineProfileFileContent(variantName))
1135                     .containsExactly(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
1136             }
1137         }
1138     }
1139 
1140     @Test
1141     fun testSkipGeneration() {
1142         projectSetup.consumer.setup(ANDROID_APPLICATION_PLUGIN)
1143         projectSetup.producer.setupWithoutFlavors(
1144             releaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
1145         )
1146 
1147         gradleRunner.build("generateBaselineProfile", "-Pandroidx.baselineprofile.skipgeneration") {
1148             assertThat(baselineProfileFile("release").exists()).isFalse()
1149         }
1150     }
1151 
1152     @Test
1153     fun testSkipGenerationWithPreviousResults() {
1154         projectSetup.consumer.setup(ANDROID_APPLICATION_PLUGIN)
1155         projectSetup.producer.setupWithoutFlavors(
1156             releaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
1157         )
1158 
1159         gradleRunner.build("generateBaselineProfile") {
1160             assertThat(readBaselineProfileFileContent("release"))
1161                 .containsExactly(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
1162         }
1163 
1164         projectSetup.producer.setupWithoutFlavors(
1165             releaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2)
1166         )
1167 
1168         gradleRunner.build("generateBaselineProfile", "-Pandroidx.baselineprofile.skipgeneration") {
1169 
1170             // Note that the baseline profile should still contain the previous profile rules
1171             // and not the updated ones, as running with `skipgeneration` will disable the
1172             // generation tasks.
1173             assertThat(readBaselineProfileFileContent("release"))
1174                 .containsExactly(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
1175         }
1176     }
1177 
1178     @Test
1179     fun testVariantSpecificDependencies() {
1180         projectSetup.producer.setupWithoutFlavors(
1181             releaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
1182         )
1183         projectSetup.consumer.setup(
1184             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1185             dependenciesBlock =
1186                 """
1187                releaseImplementation(project(":${projectSetup.dependency.name}"))
1188             """
1189                     .trimIndent()
1190         )
1191         gradleRunner.build("generateReleaseBaselineProfile", "--stacktrace") {
1192             assertThat(readBaselineProfileFileContent("release"))
1193                 .containsExactly(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
1194         }
1195     }
1196 
1197     @Test
1198     fun testVariantSpecificDependenciesWithFlavorsAndMultipleBuildTypes() {
1199         projectSetup.consumer.setupWithBlocks(
1200             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1201             flavorsBlock =
1202                 """
1203                 flavorDimensions = ["tier"]
1204                 free { dimension "tier" }
1205                 paid { dimension "tier" }
1206             """
1207                     .trimIndent(),
1208             buildTypesBlock =
1209                 """
1210                 anotherRelease { initWith(release) }
1211             """
1212                     .trimIndent(),
1213             dependencyOnProducerProject = true,
1214             dependenciesBlock =
1215                 """
1216                 releaseImplementation(project(":${projectSetup.dependency.name}"))
1217                 anotherReleaseImplementation(project(":${projectSetup.dependency.name}"))
1218             """
1219                     .trimIndent(),
1220         )
1221         projectSetup.producer.setup(
1222             variantProfiles =
1223                 listOf(
1224                     VariantProfile(
1225                         flavorDimensions = mapOf("tier" to "free"),
1226                         buildType = "release",
1227                         profileFileLines =
1228                             mapOf(
1229                                 "test-output-baseline-free-release" to
1230                                     listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
1231                             ),
1232                         startupFileLines = mapOf()
1233                     ),
1234                     VariantProfile(
1235                         flavorDimensions = mapOf("tier" to "paid"),
1236                         buildType = "release",
1237                         profileFileLines =
1238                             mapOf(
1239                                 "test-output-baseline-paid-release" to
1240                                     listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2)
1241                             ),
1242                         startupFileLines = mapOf()
1243                     ),
1244                     VariantProfile(
1245                         flavorDimensions = mapOf("tier" to "free"),
1246                         buildType = "anotherRelease",
1247                         profileFileLines =
1248                             mapOf(
1249                                 "test-output-baseline-free-anotherRelease" to
1250                                     listOf(Fixtures.CLASS_3_METHOD_1, Fixtures.CLASS_3)
1251                             ),
1252                         startupFileLines = mapOf()
1253                     ),
1254                     VariantProfile(
1255                         flavorDimensions = mapOf("tier" to "paid"),
1256                         buildType = "anotherRelease",
1257                         profileFileLines =
1258                             mapOf(
1259                                 "test-output-baseline-paid-anotherRelease" to
1260                                     listOf(Fixtures.CLASS_4_METHOD_1, Fixtures.CLASS_4)
1261                             ),
1262                         startupFileLines = mapOf()
1263                     ),
1264                 )
1265         )
1266 
1267         data class Expected(val variantName: String, val profileLines: List<String>)
1268         arrayOf(
1269                 Expected(
1270                     variantName = "freeRelease",
1271                     profileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1)
1272                 ),
1273                 Expected(
1274                     variantName = "paidRelease",
1275                     profileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2)
1276                 ),
1277                 Expected(
1278                     variantName = "freeAnotherRelease",
1279                     profileLines = listOf(Fixtures.CLASS_3_METHOD_1, Fixtures.CLASS_3)
1280                 ),
1281                 Expected(
1282                     variantName = "paidAnotherRelease",
1283                     profileLines = listOf(Fixtures.CLASS_4_METHOD_1, Fixtures.CLASS_4)
1284                 ),
1285             )
1286             .forEach { expected ->
1287                 gradleRunner.build(camelCase("generate", expected.variantName, "baselineProfile")) {
1288                     assertThat(readBaselineProfileFileContent(expected.variantName))
1289                         .containsExactlyElementsIn(expected.profileLines)
1290                 }
1291             }
1292     }
1293 
1294     @Test
1295     fun whenBenchmarkVariantsAreDisabledShouldNotify() {
1296         projectSetup.consumer.setup(
1297             dependencyOnProducerProject = true,
1298             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1299             additionalGradleCodeBlock =
1300                 """
1301                 androidComponents {
1302                     beforeVariants(selector()) { variant ->
1303                         variant.enable = variant.buildType != "benchmarkRelease"
1304                     }
1305                 }
1306             """
1307                     .trimIndent()
1308         )
1309         projectSetup.producer.setupWithoutFlavors(
1310             releaseProfileLines =
1311                 listOf(
1312                     Fixtures.CLASS_1_METHOD_1,
1313                     Fixtures.CLASS_1,
1314                     Fixtures.CLASS_2_METHOD_1,
1315                     Fixtures.CLASS_2
1316                 ),
1317             releaseStartupProfileLines =
1318                 listOf(
1319                     Fixtures.CLASS_3_METHOD_1,
1320                     Fixtures.CLASS_3,
1321                     Fixtures.CLASS_4_METHOD_1,
1322                     Fixtures.CLASS_4
1323                 )
1324         )
1325 
1326         gradleRunner.buildAndAssertThatOutput("tasks", "--info") {
1327             contains("Variant `benchmarkRelease` is disabled.")
1328         }
1329     }
1330 
1331     @Test
1332     fun testProfileStats() {
1333         projectSetup.consumer.setup(androidPlugin = ANDROID_APPLICATION_PLUGIN)
1334 
1335         // Test no previous execution
1336         projectSetup.producer.setupWithoutFlavors(
1337             releaseProfileLines =
1338                 listOf(
1339                     Fixtures.CLASS_1_METHOD_1,
1340                     Fixtures.CLASS_1,
1341                 ),
1342             releaseStartupProfileLines =
1343                 listOf(
1344                     Fixtures.CLASS_1_METHOD_1,
1345                     Fixtures.CLASS_1,
1346                 )
1347         )
1348         gradleRunner.build("generateBaselineProfile") {
1349             val notFound =
1350                 it.lines()
1351                     .requireInOrder(
1352                         "Comparison with previous baseline profile:",
1353                         "Comparison with previous startup profile:",
1354                     )
1355             assertThat(notFound.size).isEqualTo(2)
1356         }
1357 
1358         // Test unchanged
1359         gradleRunner.build("generateBaselineProfile", "--rerun-tasks") {
1360             println(it)
1361             val notFound =
1362                 it.lines()
1363                     .requireInOrder(
1364                         "Comparison with previous baseline profile:",
1365                         "  2 Old rules",
1366                         "  2 New rules",
1367                         "  0 Added rules (0.00%)",
1368                         "  0 Removed rules (0.00%)",
1369                         "  2 Unmodified rules (100.00%)",
1370                         "Comparison with previous startup profile:",
1371                         "  2 Old rules",
1372                         "  2 New rules",
1373                         "  0 Added rules (0.00%)",
1374                         "  0 Removed rules (0.00%)",
1375                         "  2 Unmodified rules (100.00%)",
1376                     )
1377             assertThat(notFound).isEmpty()
1378         }
1379 
1380         // Test added
1381         projectSetup.producer.setupWithoutFlavors(
1382             releaseProfileLines =
1383                 listOf(
1384                     Fixtures.CLASS_1_METHOD_1,
1385                     Fixtures.CLASS_1,
1386                     Fixtures.CLASS_2_METHOD_2,
1387                     Fixtures.CLASS_2,
1388                 ),
1389             releaseStartupProfileLines =
1390                 listOf(
1391                     Fixtures.CLASS_1_METHOD_1,
1392                     Fixtures.CLASS_1,
1393                     Fixtures.CLASS_2_METHOD_2,
1394                     Fixtures.CLASS_2,
1395                 )
1396         )
1397         gradleRunner.build("generateBaselineProfile", "--rerun-tasks") {
1398             println(it)
1399             val notFound =
1400                 it.lines()
1401                     .requireInOrder(
1402                         "Comparison with previous baseline profile:",
1403                         "  2 Old rules",
1404                         "  4 New rules",
1405                         "  2 Added rules (50.00%)",
1406                         "  0 Removed rules (0.00%)",
1407                         "  2 Unmodified rules (50.00%)",
1408                         "Comparison with previous startup profile:",
1409                         "  2 Old rules",
1410                         "  4 New rules",
1411                         "  2 Added rules (50.00%)",
1412                         "  0 Removed rules (0.00%)",
1413                         "  2 Unmodified rules (50.00%)",
1414                     )
1415             assertThat(notFound).isEmpty()
1416         }
1417 
1418         // Test removed
1419         projectSetup.producer.setupWithoutFlavors(
1420             releaseProfileLines =
1421                 listOf(
1422                     Fixtures.CLASS_2_METHOD_2,
1423                     Fixtures.CLASS_2,
1424                 ),
1425             releaseStartupProfileLines =
1426                 listOf(
1427                     Fixtures.CLASS_2_METHOD_2,
1428                     Fixtures.CLASS_2,
1429                 )
1430         )
1431         gradleRunner.build("generateBaselineProfile", "--rerun-tasks") {
1432             println(it)
1433             val notFound =
1434                 it.lines()
1435                     .requireInOrder(
1436                         "Comparison with previous baseline profile:",
1437                         "  4 Old rules",
1438                         "  2 New rules",
1439                         "  0 Added rules (0.00%)",
1440                         "  2 Removed rules (50.00%)",
1441                         "  2 Unmodified rules (50.00%)",
1442                         "Comparison with previous startup profile:",
1443                         "  4 Old rules",
1444                         "  2 New rules",
1445                         "  0 Added rules (0.00%)",
1446                         "  2 Removed rules (50.00%)",
1447                         "  2 Unmodified rules (50.00%)",
1448                     )
1449             assertThat(notFound).isEmpty()
1450         }
1451     }
1452 
1453     @Test
1454     fun testSuppressWarningMaxAgpVersion() {
1455         val requiredLines =
1456             listOf(
1457                 "This version of the Baseline Profile Gradle Plugin was tested with versions below",
1458                 // We skip the lines in between because they may contain changing version numbers.
1459                 "baselineProfile {",
1460                 "    warnings {",
1461                 "        maxAgpVersion = false",
1462                 "    }",
1463                 "}"
1464             )
1465         projectSetup.producer.setupWithoutFlavors(
1466             releaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
1467         )
1468 
1469         // Setup with default warnings
1470         projectSetup.consumer.setup(androidPlugin = ANDROID_APPLICATION_PLUGIN)
1471         projectSetup.consumer.gradleRunner.build(
1472             "generateBaselineProfile",
1473             "-Pandroidx.benchmark.test.maxagpversion=1.0.0"
1474         ) {
1475             val notFound = it.lines().requireInOrder(*requiredLines.toTypedArray())
1476             assertThat(notFound).isEmpty()
1477         }
1478 
1479         // Setup turning off warning
1480         projectSetup.consumer.setup(
1481             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1482             baselineProfileBlock =
1483                 """
1484                 warnings {
1485                     maxAgpVersion = false
1486                 }
1487             """
1488                     .trimIndent()
1489         )
1490         projectSetup.consumer.gradleRunner.build(
1491             "generateBaselineProfile",
1492             "-Pandroidx.benchmark.test.maxagpversion=1.0.0"
1493         ) {
1494             val notFound = it.lines().requireInOrder(*requiredLines.toTypedArray())
1495             assertThat(notFound).isEqualTo(requiredLines)
1496         }
1497     }
1498 
1499     @Test
1500     fun testSuppressWarningWithProperty() {
1501         val requiredLines =
1502             listOf(
1503                 "This version of the Baseline Profile Gradle Plugin was tested with versions below",
1504                 // We skip the lines in between because they may contain changing version numbers.
1505                 "baselineProfile {",
1506                 "    warnings {",
1507                 "        maxAgpVersion = false",
1508                 "    }",
1509                 "}"
1510             )
1511 
1512         projectSetup.consumer.setup(androidPlugin = ANDROID_APPLICATION_PLUGIN)
1513         projectSetup.producer.setupWithoutFlavors(
1514             releaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
1515         )
1516 
1517         val gradleCmds =
1518             arrayOf(
1519                 "generateBaselineProfile",
1520                 "-Pandroidx.benchmark.test.maxagpversion=1.0.0",
1521             )
1522 
1523         // Run with no suppress warnings property
1524         projectSetup.consumer.gradleRunner.build(*gradleCmds) {
1525             val notFound = it.lines().requireInOrder(*requiredLines.toTypedArray())
1526             assertThat(notFound).isEmpty()
1527         }
1528 
1529         // Run with suppress warnings property
1530         projectSetup.consumer.gradleRunner.build(
1531             *gradleCmds,
1532             "-Pandroidx.baselineprofile.suppresswarnings"
1533         ) {
1534             val notFound = it.lines().requireInOrder(*requiredLines.toTypedArray())
1535             assertThat(notFound).isEqualTo(requiredLines)
1536         }
1537     }
1538 
1539     @Test
1540     fun testMergeArtAndStartupProfilesShouldDependOnProfileGeneration() {
1541         projectSetup.producer.setupWithFreeAndPaidFlavors(
1542             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
1543             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
1544         )
1545 
1546         arrayOf(
1547                 Pair(true, true),
1548                 Pair(true, false),
1549                 Pair(false, true),
1550             )
1551             .forEach { (saveInSrc, automaticGenerationDuringBuild) ->
1552                 projectSetup.consumer.setup(
1553                     androidPlugin = ANDROID_APPLICATION_PLUGIN,
1554                     flavors = true,
1555                     baselineProfileBlock =
1556                         """
1557                 saveInSrc = $saveInSrc
1558                 automaticGenerationDuringBuild = $automaticGenerationDuringBuild
1559             """
1560                             .trimIndent()
1561                 )
1562                 gradleRunner.build("generateFreeReleaseBaselineProfile", "assembleFreeRelease") {}
1563             }
1564     }
1565 }
1566 
1567 @RunWith(Parameterized::class)
1568 class BaselineProfileConsumerPluginTestWithAgp81(private val agpVersion: TestAgpVersion) {
1569 
1570     companion object {
1571         @Parameterized.Parameters(name = "agpVersion={0}")
1572         @JvmStatic
parametersnull1573         fun parameters() = TestAgpVersion.atLeast(TEST_AGP_VERSION_8_1_1)
1574     }
1575 
1576     @get:Rule
1577     val projectSetup = BaselineProfileProjectSetupRule(forceAgpVersion = agpVersion.versionString)
1578 
1579     @Test
1580     fun verifyGenerateTasks() {
1581         projectSetup.producer.setupWithFreeAndPaidFlavors(
1582             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
1583             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
1584             freeAnotherReleaseProfileLines = listOf(Fixtures.CLASS_3_METHOD_1, Fixtures.CLASS_3),
1585             paidAnotherReleaseProfileLines = listOf(Fixtures.CLASS_4_METHOD_1, Fixtures.CLASS_4),
1586         )
1587         projectSetup.consumer.setup(
1588             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1589             dependencyOnProducerProject = true,
1590             flavors = true,
1591             buildTypeAnotherRelease = true
1592         )
1593         projectSetup.consumer.gradleRunner.build("tasks") {
1594             val notFound =
1595                 it.lines()
1596                     .require(
1597                         "generateBaselineProfile - ",
1598                         "generateReleaseBaselineProfile - ",
1599                         "generateAnotherReleaseBaselineProfile - ",
1600                         "generateFreeBaselineProfile - ",
1601                         "generatePaidBaselineProfile - ",
1602                         "generateFreeReleaseBaselineProfile - ",
1603                         "generatePaidReleaseBaselineProfile - ",
1604                         "generateFreeAnotherReleaseBaselineProfile - ",
1605                         "generatePaidAnotherReleaseBaselineProfile - ",
1606                     )
1607             assertThat(notFound).isEmpty()
1608         }
1609 
1610         val name = projectSetup.consumer.name
1611 
1612         projectSetup.consumer.gradleRunner.build("generateBaselineProfile", "--dry-run") {
1613             val notFound =
1614                 it.lines()
1615                     .require(
1616                         ":$name:copyFreeReleaseBaselineProfileIntoSrc",
1617                         ":$name:copyPaidReleaseBaselineProfileIntoSrc",
1618                         ":$name:copyFreeAnotherReleaseBaselineProfileIntoSrc",
1619                         ":$name:copyPaidAnotherReleaseBaselineProfileIntoSrc",
1620                     )
1621             assertThat(notFound).isEmpty()
1622         }
1623 
1624         projectSetup.consumer.gradleRunner.build("generateReleaseBaselineProfile", "--dry-run") {
1625             val notFound =
1626                 it.lines()
1627                     .require(
1628                         ":$name:copyFreeReleaseBaselineProfileIntoSrc",
1629                         ":$name:copyPaidReleaseBaselineProfileIntoSrc",
1630                     )
1631             assertThat(notFound).isEmpty()
1632         }
1633 
1634         projectSetup.consumer.gradleRunner.build(
1635             "generateAnotherReleaseBaselineProfile",
1636             "--dry-run"
1637         ) {
1638             val notFound =
1639                 it.lines()
1640                     .require(
1641                         ":$name:copyFreeAnotherReleaseBaselineProfileIntoSrc",
1642                         ":$name:copyPaidAnotherReleaseBaselineProfileIntoSrc",
1643                     )
1644             assertThat(notFound).isEmpty()
1645         }
1646 
1647         projectSetup.consumer.gradleRunner.build("generateFreeBaselineProfile", "--dry-run") {
1648             val notFound =
1649                 it.lines()
1650                     .require(
1651                         ":$name:copyFreeReleaseBaselineProfileIntoSrc",
1652                         ":$name:copyFreeAnotherReleaseBaselineProfileIntoSrc",
1653                     )
1654             assertThat(notFound).isEmpty()
1655         }
1656 
1657         projectSetup.consumer.gradleRunner.build("generatePaidBaselineProfile", "--dry-run") {
1658             val notFound =
1659                 it.lines()
1660                     .require(
1661                         ":$name:copyPaidReleaseBaselineProfileIntoSrc",
1662                         ":$name:copyPaidAnotherReleaseBaselineProfileIntoSrc",
1663                     )
1664             assertThat(notFound).isEmpty()
1665         }
1666     }
1667 
1668     @Test
verifyTasksWithAndroidTestPluginnull1669     fun verifyTasksWithAndroidTestPlugin() {
1670         projectSetup.consumer.setup(
1671             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1672             flavors = true,
1673             baselineProfileBlock =
1674                 """
1675                 saveInSrc = true
1676                 automaticGenerationDuringBuild = true
1677             """
1678                     .trimIndent(),
1679             additionalGradleCodeBlock =
1680                 """
1681                 androidComponents {
1682                     onVariants(selector()) { variant ->
1683                         tasks.register(variant.name + "BaselineProfileSrcSet", PrintTask) { t ->
1684                             t.text.set(variant.sources.baselineProfiles.directories.toString())
1685                         }
1686                     }
1687                 }
1688             """
1689                     .trimIndent()
1690         )
1691         projectSetup.producer.setupWithFreeAndPaidFlavors(
1692             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
1693             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
1694         )
1695 
1696         // Asserts that running connected checks on a benchmark variants also triggers
1697         // baseline profile generation (due to `automaticGenerationDuringBuild` true`).
1698         projectSetup.producer.gradleRunner.build(
1699             "connectedFreeBenchmarkReleaseAndroidTest",
1700             "--dry-run"
1701         ) { text ->
1702             val consumerName = projectSetup.consumer.name
1703             val producerName = projectSetup.producer.name
1704 
1705             val notFound =
1706                 text
1707                     .lines()
1708                     .requireInOrder(
1709                         ":$consumerName:packageFreeNonMinifiedRelease",
1710                         ":$producerName:connectedFreeNonMinifiedReleaseAndroidTest",
1711                         ":$producerName:collectFreeNonMinifiedReleaseBaselineProfile",
1712                         ":$consumerName:mergeFreeReleaseBaselineProfile",
1713                         ":$consumerName:copyFreeReleaseBaselineProfileIntoSrc",
1714                         ":$consumerName:mergeFreeBenchmarkReleaseArtProfile",
1715                         ":$consumerName:compileFreeBenchmarkReleaseArtProfile",
1716                         ":$consumerName:packageFreeBenchmarkRelease",
1717                         ":$consumerName:createFreeBenchmarkReleaseApkListingFileRedirect",
1718                         ":$producerName:connectedFreeBenchmarkReleaseAndroidTest"
1719                     )
1720 
1721             assertThat(notFound).isEmpty()
1722         }
1723     }
1724 
1725     @Test
automaticGenerationDuringBuildNotCompatibleWithLibraryModulenull1726     fun automaticGenerationDuringBuildNotCompatibleWithLibraryModule() {
1727         projectSetup.consumer.setup(
1728             androidPlugin = ANDROID_LIBRARY_PLUGIN,
1729             baselineProfileBlock =
1730                 """
1731                 saveInSrc = true
1732                 automaticGenerationDuringBuild = true
1733             """
1734                     .trimIndent()
1735         )
1736         projectSetup.producer.setupWithoutFlavors(
1737             releaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
1738         )
1739 
1740         // Asserts that running connected checks on a benchmark variants also triggers
1741         // baseline profile generation (due to `automaticGenerationDuringBuild` true`).
1742         projectSetup.consumer.gradleRunner.buildAndFailAndAssertThatOutput(
1743             "generateBaselineProfile",
1744             "--dry-run"
1745         ) {
1746             contains(
1747                 "The flag `automaticGenerationDuringBuild` is not compatible with library " +
1748                     "modules. Please remove the flag `automaticGenerationDuringBuild` " +
1749                     "in your com.android.library module"
1750             )
1751         }
1752     }
1753 
1754     @Test
testExperimentalPropertiesSetnull1755     fun testExperimentalPropertiesSet() {
1756         projectSetup.producer.setupWithFreeAndPaidFlavors(
1757             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
1758             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2)
1759         )
1760         projectSetup.consumer.setup(
1761             androidPlugin = ANDROID_LIBRARY_PLUGIN,
1762             dependencyOnProducerProject = true,
1763             flavors = true,
1764             buildTypeAnotherRelease = true,
1765             baselineProfileBlock =
1766                 """
1767                 baselineProfileRulesRewrite = true
1768                 dexLayoutOptimization = true
1769             """
1770                     .trimIndent()
1771         )
1772 
1773         arrayOf(
1774                 "printExperimentalPropertiesForVariantFreeRelease",
1775                 "printExperimentalPropertiesForVariantPaidRelease",
1776                 "printExperimentalPropertiesForVariantFreeAnotherRelease",
1777                 "printExperimentalPropertiesForVariantPaidAnotherRelease",
1778             )
1779             .forEach {
1780                 projectSetup.consumer.gradleRunner.buildAndAssertThatOutput(it) {
1781                     // These properties are ignored in agp 8.0
1782                     contains("android.experimental.art-profile-r8-rewriting=true")
1783                     contains("android.experimental.r8.dex-startup-optimization=true")
1784                 }
1785             }
1786     }
1787 
1788     @Test
testGenerateTaskWithFlavorsAndMergeAllnull1789     fun testGenerateTaskWithFlavorsAndMergeAll() {
1790         projectSetup.consumer.setup(
1791             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1792             flavors = true,
1793             dependencyOnProducerProject = true,
1794             baselineProfileBlock =
1795                 """
1796                 mergeIntoMain = true
1797             """
1798                     .trimIndent()
1799         )
1800         projectSetup.producer.setupWithFreeAndPaidFlavors(
1801             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
1802             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2)
1803         )
1804 
1805         // Asserts that all per-variant, per-flavor and per-build type tasks are being generated.
1806         projectSetup.consumer.gradleRunner.buildAndAssertThatOutput("tasks") {
1807             contains("generateBaselineProfile - ")
1808             doesNotContain("generateReleaseBaselineProfile - ")
1809             doesNotContain("generateFreeReleaseBaselineProfile - ")
1810             doesNotContain("generatePaidReleaseBaselineProfile - ")
1811         }
1812 
1813         projectSetup.consumer.gradleRunner
1814             .withArguments("generateBaselineProfile", "--stacktrace")
1815             .build()
1816 
1817         val lines =
1818             File(
1819                     projectSetup.consumer.rootDir,
1820                     "src/main/$EXPECTED_PROFILE_FOLDER/baseline-prof.txt"
1821                 )
1822                 .readLines()
1823         assertThat(lines)
1824             .containsExactly(
1825                 Fixtures.CLASS_1,
1826                 Fixtures.CLASS_1_METHOD_1,
1827                 Fixtures.CLASS_2,
1828                 Fixtures.CLASS_2_METHOD_1,
1829             )
1830     }
1831 
1832     @Test
testExperimentalPropertyHideVariantInAndroidStudionull1833     fun testExperimentalPropertyHideVariantInAndroidStudio() {
1834         projectSetup.producer.setupWithFreeAndPaidFlavors(
1835             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
1836             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2)
1837         )
1838 
1839         val taskList =
1840             listOf(
1841                 "printExperimentalPropertiesForVariantFreeNonMinifiedRelease",
1842                 "printExperimentalPropertiesForVariantFreeBenchmarkRelease",
1843                 "printExperimentalPropertiesForVariantPaidNonMinifiedRelease",
1844                 "printExperimentalPropertiesForVariantPaidBenchmarkRelease",
1845                 "printExperimentalPropertiesForVariantFreeNonMinifiedAnotherRelease",
1846                 "printExperimentalPropertiesForVariantFreeBenchmarkAnotherRelease",
1847                 "printExperimentalPropertiesForVariantPaidNonMinifiedAnotherRelease",
1848                 "printExperimentalPropertiesForVariantPaidBenchmarkAnotherRelease",
1849             )
1850 
1851         // Setup consumer module with DEFAULT configuration
1852         projectSetup.consumer.setup(
1853             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1854             dependencyOnProducerProject = true,
1855             flavors = true,
1856             buildTypeAnotherRelease = true,
1857         )
1858         taskList.forEach {
1859             projectSetup.consumer.gradleRunner.buildAndAssertThatOutput(it) {
1860                 contains("androidx.baselineProfile.hideInStudio=true")
1861             }
1862         }
1863 
1864         // Setup consumer module NOT HIDING the build types
1865         projectSetup.consumer.setup(
1866             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1867             dependencyOnProducerProject = true,
1868             flavors = true,
1869             buildTypeAnotherRelease = true,
1870             baselineProfileBlock =
1871                 """
1872                 hideSyntheticBuildTypesInAndroidStudio = false
1873             """
1874                     .trimIndent()
1875         )
1876         taskList.forEach {
1877             projectSetup.consumer.gradleRunner.buildAndAssertThatOutput(it) {
1878                 doesNotContain("androidx.baselineProfile.hideInStudio=")
1879             }
1880         }
1881 
1882         // Setup consumer module HIDING the build types
1883         projectSetup.consumer.setup(
1884             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1885             dependencyOnProducerProject = true,
1886             flavors = true,
1887             buildTypeAnotherRelease = true,
1888             baselineProfileBlock =
1889                 """
1890                 hideSyntheticBuildTypesInAndroidStudio = true
1891             """
1892                     .trimIndent()
1893         )
1894         taskList.forEach {
1895             projectSetup.consumer.gradleRunner.buildAndAssertThatOutput(it) {
1896                 contains("androidx.baselineProfile.hideInStudio=true")
1897             }
1898         }
1899     }
1900 }
1901 
1902 @RunWith(Parameterized::class)
1903 class BaselineProfileConsumerPluginTestWithAgp83(private val agpVersion: TestAgpVersion) {
1904 
1905     companion object {
1906         @Parameterized.Parameters(name = "agpVersion={0}")
1907         @JvmStatic
parametersnull1908         fun parameters() = TestAgpVersion.atLeast(TEST_AGP_VERSION_8_3_1)
1909     }
1910 
1911     @get:Rule
1912     val projectSetup = BaselineProfileProjectSetupRule(forceAgpVersion = agpVersion.versionString)
1913 
1914     private val gradleRunner by lazy { projectSetup.consumer.gradleRunner }
1915 
1916     @Test
testSrcSetAreAddedToVariantsForLibrariesnull1917     fun testSrcSetAreAddedToVariantsForLibraries() {
1918         projectSetup.producer.setupWithoutFlavors()
1919         projectSetup.consumer.setup(
1920             androidPlugin = ANDROID_LIBRARY_PLUGIN,
1921             additionalGradleCodeBlock =
1922                 """
1923                 androidComponents {
1924                     onVariants(selector()) { variant ->
1925                         tasks.register(variant.name + "Sources", DisplaySourceSets) { t ->
1926                             t.srcs.set(variant.sources.baselineProfiles.all)
1927                         }
1928                     }
1929                 }
1930             """
1931                     .trimIndent()
1932         )
1933 
1934         val expected =
1935             listOf(
1936                     "src/main/baselineProfiles",
1937                     "src/main/generated/baselineProfiles",
1938                     "src/release/baselineProfiles",
1939                 )
1940                 .map { dir -> File(projectSetup.consumer.rootDir, dir) }
1941                 .onEach { f ->
1942                     // Expected src set location. Note that src sets are not added if the folder
1943                     // does
1944                     // not exist so we need to create it.
1945                     f.mkdirs()
1946                     f.deleteOnExit()
1947                 }
1948 
1949         gradleRunner.buildAndAssertThatOutput("releaseSources") {
1950             expected.forEach { e -> contains(e.absolutePath) }
1951         }
1952     }
1953 }
1954 
1955 @RunWith(Parameterized::class)
1956 class BaselineProfileConsumerPluginTestWithKmp(agpVersion: TestAgpVersion) {
1957 
1958     companion object {
1959         @Parameterized.Parameters(name = "agpVersion={0}")
1960         @JvmStatic
parametersnull1961         fun parameters() = TestAgpVersion.atLeast(TEST_AGP_VERSION_8_3_1)
1962     }
1963 
1964     @get:Rule
1965     val projectSetup =
1966         BaselineProfileProjectSetupRule(
1967             forceAgpVersion = agpVersion.versionString,
1968             addKotlinGradlePluginToClasspath = true
1969         )
1970 
1971     private val gradleRunner by lazy { projectSetup.consumer.gradleRunner }
1972 
1973     @Test
testSrcSetAreAddedToVariantsForApplicationsWithKmpnull1974     fun testSrcSetAreAddedToVariantsForApplicationsWithKmp() {
1975         projectSetup.producer.setupWithoutFlavors(releaseProfileLines = listOf())
1976         projectSetup.consumer.setupWithBlocks(
1977             androidPlugin = ANDROID_APPLICATION_PLUGIN,
1978             otherPluginsBlock =
1979                 """
1980                 id("org.jetbrains.kotlin.multiplatform")
1981             """
1982                     .trimIndent(),
1983             dependenciesBlock =
1984                 """
1985                 implementation(project(":${projectSetup.dependency.name}"))
1986             """
1987                     .trimIndent(),
1988             additionalGradleCodeBlock =
1989                 """
1990                 kotlin {
1991                     jvm { }
1992                     androidTarget { }
1993                     sourceSets {
1994                         androidMain { }
1995                     }
1996                 }
1997 
1998                 androidComponents {
1999                     onVariants(selector()) { variant ->
2000                         tasks.register(variant.name + "Sources", DisplaySourceSets) { t ->
2001                             t.srcs.set(variant.sources.baselineProfiles.all)
2002                         }
2003                     }
2004                 }
2005             """
2006                     .trimIndent()
2007         )
2008 
2009         val expected =
2010             listOf(
2011                     "src/main/baselineProfiles",
2012                     "src/release/baselineProfiles",
2013                     "src/androidRelease/generated/baselineProfiles",
2014                 )
2015                 .map { dir -> File(projectSetup.consumer.rootDir, dir) }
2016                 .onEach { f ->
2017                     // Expected src set location. Note that src sets are not added if the folder
2018                     // does
2019                     // not exist so we need to create it.
2020                     f.mkdirs()
2021                     f.deleteOnExit()
2022                 }
2023 
2024         gradleRunner.buildAndAssertThatOutput("releaseSources") {
2025             expected.forEach { e -> contains(e.absolutePath) }
2026         }
2027     }
2028 
2029     @Test
testSrcSetAreAddedToVariantsForLibrariesWithKmpnull2030     fun testSrcSetAreAddedToVariantsForLibrariesWithKmp() {
2031         projectSetup.producer.setupWithoutFlavors(releaseProfileLines = listOf())
2032         projectSetup.consumer.setupWithBlocks(
2033             androidPlugin = ANDROID_LIBRARY_PLUGIN,
2034             otherPluginsBlock =
2035                 """
2036                 id("org.jetbrains.kotlin.multiplatform")
2037             """
2038                     .trimIndent(),
2039             dependenciesBlock =
2040                 """
2041                 implementation(project(":${projectSetup.dependency.name}"))
2042             """
2043                     .trimIndent(),
2044             additionalGradleCodeBlock =
2045                 """
2046                 kotlin {
2047                     jvm { }
2048                     androidTarget("androidTargetCustom") { }
2049                 }
2050 
2051                 androidComponents {
2052                     onVariants(selector()) { variant ->
2053                         tasks.register(variant.name + "Sources", DisplaySourceSets) { t ->
2054                             t.srcs.set(variant.sources.baselineProfiles.all)
2055                         }
2056                     }
2057                 }
2058             """
2059                     .trimIndent()
2060         )
2061 
2062         val expected =
2063             listOf(
2064                     "src/main/baselineProfiles",
2065                     "src/release/baselineProfiles",
2066                     "src/androidTargetCustomMain/generated/baselineProfiles",
2067                 )
2068                 .map { dir -> File(projectSetup.consumer.rootDir, dir) }
2069                 .onEach { f ->
2070                     // Expected src set location. Note that src sets are not added if the folder
2071                     // does
2072                     // not exist so we need to create it.
2073                     f.mkdirs()
2074                     f.deleteOnExit()
2075                 }
2076 
2077         gradleRunner.buildAndAssertThatOutput("releaseSources") {
2078             expected.forEach { e -> contains(e.absolutePath) }
2079         }
2080     }
2081 }
2082 
2083 @RunWith(Parameterized::class)
2084 class BaselineProfileConsumerPluginTestWithFtl(agpVersion: TestAgpVersion) {
2085 
2086     companion object {
2087         @Parameterized.Parameters(name = "agpVersion={0}")
2088         @JvmStatic
parametersnull2089         fun parameters() = TestAgpVersion.atLeast(TEST_AGP_VERSION_CURRENT)
2090     }
2091 
2092     @get:Rule
2093     val projectSetup = BaselineProfileProjectSetupRule(forceAgpVersion = agpVersion.versionString)
2094 
2095     @Test
2096     fun testGenerateBaselineProfileWithFtlArtifact() {
2097         projectSetup.consumer.setup(androidPlugin = ANDROID_APPLICATION_PLUGIN)
2098 
2099         // The difference with FTL is that artifacts are added as global artifacts instead of
2100         // per test result. This different setup can be specified in the `VariantProfile`.
2101         projectSetup.producer.setup(
2102             variantProfiles =
2103                 VariantProfile.release(
2104                     ftlFileLines = listOf(Fixtures.CLASS_2_METHOD_1),
2105                     useGsSchema = false,
2106                 )
2107         )
2108 
2109         projectSetup.consumer.gradleRunner.build("generateBaselineProfile", "--info") {
2110             println(it)
2111             val notFound =
2112                 it.lines()
2113                     .requireInOrder(
2114                         "A baseline profile was generated for the variant `release`:",
2115                         "${projectSetup.baselineProfileFile("release").toUri()}",
2116                     )
2117             assertThat(notFound).isEmpty()
2118         }
2119 
2120         assertThat(projectSetup.readBaselineProfileFileContent("release"))
2121             .containsExactly(Fixtures.CLASS_2_METHOD_1)
2122     }
2123 
2124     @Test
testGenerateBaselineProfileWithFtlArtifactInGoogleStorageShouldNotCrashnull2125     fun testGenerateBaselineProfileWithFtlArtifactInGoogleStorageShouldNotCrash() {
2126         projectSetup.consumer.setup(androidPlugin = ANDROID_APPLICATION_PLUGIN)
2127 
2128         // The difference with FTL is that artifacts are added as global artifacts instead of
2129         // per test result. This different setup can be specified in the `VariantProfile`.
2130         projectSetup.producer.setup(
2131             variantProfiles =
2132                 VariantProfile.release(
2133                     ftlFileLines = listOf(Fixtures.CLASS_2_METHOD_1),
2134                     useGsSchema = true,
2135                 )
2136         )
2137 
2138         projectSetup.consumer.gradleRunner.build("generateBaselineProfile", "--info") {
2139             println(it)
2140             val notFound =
2141                 it.lines()
2142                     .requireInOrder(
2143                         "No baseline profile rules were generated for the variant `release`",
2144                         "No startup profile rules were generated for the variant `release`"
2145                     )
2146             assertThat(notFound).isEmpty()
2147         }
2148 
2149         assertThat(projectSetup.baselineProfileFile("release").exists()).isFalse()
2150         assertThat(projectSetup.startupProfileFile("release").exists()).isFalse()
2151     }
2152 }
2153