1 /*
2  * Copyright 2021 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 @file:Suppress("UnstableApiUsage", "GroovyUnusedAssignment")
18 
19 package androidx.build.lint
20 
21 import androidx.build.lint.BanInappropriateExperimentalUsage.Companion.getMavenCoordinatesFromPath
22 import androidx.build.lint.BanInappropriateExperimentalUsage.Companion.isAnnotationAlwaysAllowed
23 import androidx.build.lint.Stubs.Companion.JetpackOptIn
24 import androidx.build.lint.Stubs.Companion.JetpackRequiresOptIn
25 import com.android.tools.lint.checks.infrastructure.ProjectDescription
26 import org.junit.Test
27 import org.junit.runner.RunWith
28 import org.junit.runners.JUnit4
29 
30 /**
31  * Important:
32  *
33  * [BanInappropriateExperimentalUsage] uses the atomic library groups list generated from production
34  * data. For tests, we want to overwrite this to provide a different list of atomic library groups;
35  * the file location is lint-checks/src/test/resources/atomic-library-groups.txt.
36  *
37  * Note that the filename must match
38  * [BanInappropriateExperimentalUsage.ATOMIC_LIBRARY_GROUPS_FILENAME].
39  */
40 @RunWith(JUnit4::class)
41 class BanInappropriateExperimentalUsageTest :
42     AbstractLintDetectorTest(
43         useDetector = BanInappropriateExperimentalUsage(),
44         useIssues = listOf(BanInappropriateExperimentalUsage.ISSUE),
45         stubs = arrayOf(Stubs.OptIn),
46     ) {
47 
48     @Test
Check if annotation is always allowednull49     fun `Check if annotation is always allowed`() {
50 
51         // These annotations are used in AndroidX
52         assertTrue(isAnnotationAlwaysAllowed("com.google.devtools.ksp.KspExperimental"))
53         assertTrue(isAnnotationAlwaysAllowed("kotlin.contracts.ExperimentalContracts"))
54         assertTrue(isAnnotationAlwaysAllowed("kotlin.ExperimentalStdlibApi"))
55         assertTrue(isAnnotationAlwaysAllowed("kotlin.experimental.ExperimentalTypeInference"))
56         assertTrue(isAnnotationAlwaysAllowed("kotlinx.coroutines.DelicateCoroutinesApi"))
57         assertTrue(isAnnotationAlwaysAllowed("kotlinx.coroutines.ExperimentalCoroutinesApi"))
58         assertTrue(
59             isAnnotationAlwaysAllowed(
60                 "org.jetbrains.kotlin.extensions.internal.InternalNonStableExtensionPoints"
61             )
62         )
63         assertTrue(isAnnotationAlwaysAllowed("org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI"))
64 
65         assertFalse(isAnnotationAlwaysAllowed("androidx.foo.bar"))
66         assertFalse(isAnnotationAlwaysAllowed("com.google.foo.bar"))
67     }
68 
69     @Test
getLibraryFromPath should return correct Maven coordinatesnull70     fun `getLibraryFromPath should return correct Maven coordinates`() {
71 
72         val paging =
73             getMavenCoordinatesFromPath(
74                 "/path/to/checkout/out/androidx/paging/paging-common/build/libs/paging-common-3.2.0-alpha01.jar"
75             )
76         val room =
77             getMavenCoordinatesFromPath(
78                 "/path/to/checkout/out/androidx/room/room-compiler-processing/build/libs/room-compiler-processing-2.5.0-alpha02.jar"
79             )
80         val uiTest =
81             getMavenCoordinatesFromPath(
82                 "/path/to/checkout/out/androidx/compose/ui/ui-test/build/libs/ui-test-jvmstubs-1.8.0-beta01.jar"
83             )
84 
85         assertNotNull(paging!!)
86         assertEquals("androidx.paging", paging.groupId)
87         assertEquals("paging-common", paging.artifactId)
88         assertEquals("3.2.0-alpha01", paging.version)
89 
90         assertNotNull(room!!)
91         assertEquals("androidx.room", room.groupId)
92         assertEquals("room-compiler-processing", room.artifactId)
93         assertEquals("2.5.0-alpha02", room.version)
94 
95         assertNotNull(uiTest!!)
96         assertEquals("androidx.compose.ui", uiTest.groupId)
97         assertEquals("ui-test", uiTest.artifactId)
98         assertEquals("jvmstubs-1.8.0-beta01", uiTest.version)
99 
100         val invalid = getMavenCoordinatesFromPath("/foo/bar/baz")
101         assertNull(invalid)
102     }
103 
104     @Test
Test same atomic module Experimental usage via Gradle modelnull105     fun `Test same atomic module Experimental usage via Gradle model`() {
106         val provider =
107             project()
108                 .name("provider")
109                 .files(
110                     ktSample("sample.annotation.provider.WithinGroupExperimentalAnnotatedClass"),
111                     ktSample("sample.annotation.provider.ExperimentalSampleAnnotation"),
112                     gradle(
113                             """
114                     apply plugin: 'com.android.library'
115                     group=sample.annotation.provider
116                     """
117                         )
118                         .indented(),
119                 )
120 
121         val expected =
122             """
123 No warnings.
124         """
125                 .trimIndent()
126 
127         check(provider).expect(expected)
128     }
129 
130     @Test
Test same non-atomic module Experimental usage via Gradle modelnull131     fun `Test same non-atomic module Experimental usage via Gradle model`() {
132         val provider =
133             project()
134                 .name("provider")
135                 .files(
136                     ktSample("sample.annotation.provider.WithinGroupExperimentalAnnotatedClass"),
137                     ktSample("sample.annotation.provider.ExperimentalSampleAnnotation"),
138                     gradle(
139                             """
140                     apply plugin: 'com.android.library'
141                     group=sample.annotation.provider
142                     """
143                         )
144                         .indented(),
145                 )
146 
147         val expected =
148             """
149 No warnings.
150         """
151                 .trimIndent()
152 
153         check(provider).expect(expected)
154     }
155 
156     @Test
Test cross-module Experimental usage via Gradle modelnull157     fun `Test cross-module Experimental usage via Gradle model`() {
158 
159         val provider =
160             project()
161                 .name("provider")
162                 .type(ProjectDescription.Type.LIBRARY)
163                 .report(false)
164                 .files(
165                     JetpackRequiresOptIn,
166                     ktSample("sample.annotation.provider.ExperimentalSampleAnnotation"),
167                     javaSample("sample.annotation.provider.ExperimentalSampleAnnotationJava"),
168                     javaSample("sample.annotation.provider.RequiresOptInSampleAnnotationJava"),
169                     javaSample(
170                         "sample.annotation.provider.RequiresOptInSampleAnnotationJavaDuplicate"
171                     ),
172                     javaSample(
173                         "sample.annotation.provider.RequiresAndroidXOptInSampleAnnotationJava"
174                     ),
175                     javaSample(
176                         "sample.annotation.provider.RequiresAndroidXOptInSampleAnnotationJavaDuplicate"
177                     ),
178                     gradle(
179                             """
180                     apply plugin: 'com.android.library'
181                     group=sample.annotation.provider
182                     """
183                         )
184                         .indented(),
185                 )
186 
187         val consumer =
188             project()
189                 .name("consumer")
190                 .type(ProjectDescription.Type.LIBRARY)
191                 .dependsOn(provider)
192                 .files(
193                     JetpackOptIn,
194                     ktSample("androidx.sample.consumer.OutsideGroupExperimentalAnnotatedClass"),
195                     gradle(
196                             """
197                     apply plugin: 'com.android.library'
198                     group=androidx.sample.consumer
199                     """
200                         )
201                         .indented(),
202                 )
203 
204         val expected =
205             """
206 ../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:35: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
207     @ExperimentalSampleAnnotationJava
208     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
209 ../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:40: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
210     @RequiresOptInSampleAnnotationJava
211     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
212 ../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:45: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
213     @kotlin.OptIn(RequiresOptInSampleAnnotationJava::class)
214     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
215 ../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:50: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
216     @kotlin.OptIn(
217     ^
218 ../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:58: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
219     @kotlin.OptIn(
220     ^
221 ../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:66: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
222     @androidx.annotation.OptIn(RequiresAndroidXOptInSampleAnnotationJava::class)
223     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
224 ../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:71: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
225     @androidx.annotation.OptIn(
226     ^
227 ../consumer/src/main/kotlin/androidx/sample/consumer/OutsideGroupExperimentalAnnotatedClass.kt:79: Error: Experimental and RequiresOptIn APIs may only be used within the same-version group where they were defined. [IllegalExperimentalApiUsage]
228     @androidx.annotation.OptIn(
229     ^
230 8 errors, 0 warnings
231         """
232                 .trimIndent()
233 
234         check(provider, consumer).expect(expected)
235     }
236 
237     @Test
Test cross-module Experimental usage in alpha via Gradle modelnull238     fun `Test cross-module Experimental usage in alpha via Gradle model`() {
239 
240         val provider =
241             project()
242                 .name("provider")
243                 .type(ProjectDescription.Type.LIBRARY)
244                 .report(false)
245                 .files(
246                     JetpackRequiresOptIn,
247                     ktSample("sample.annotation.provider.ExperimentalSampleAnnotation"),
248                     javaSample("sample.annotation.provider.ExperimentalSampleAnnotationJava"),
249                     javaSample("sample.annotation.provider.RequiresOptInSampleAnnotationJava"),
250                     javaSample(
251                         "sample.annotation.provider.RequiresOptInSampleAnnotationJavaDuplicate"
252                     ),
253                     javaSample(
254                         "sample.annotation.provider.RequiresAndroidXOptInSampleAnnotationJava"
255                     ),
256                     javaSample(
257                         "sample.annotation.provider.RequiresAndroidXOptInSampleAnnotationJavaDuplicate"
258                     ),
259                     gradle(
260                             """
261                     apply plugin: 'com.android.library'
262                     group=sample.annotation.provider
263                     version=1.0.0-beta02
264                     """
265                         )
266                         .indented(),
267                 )
268 
269         val consumer =
270             project()
271                 .name("consumer")
272                 .type(ProjectDescription.Type.LIBRARY)
273                 .dependsOn(provider)
274                 .files(
275                     JetpackOptIn,
276                     ktSample("androidx.sample.consumer.OutsideGroupExperimentalAnnotatedClass"),
277                     gradle(
278                             """
279                     apply plugin: 'com.android.library'
280                     group=androidx.sample.consumer
281                     version=1.0.0-alpha01
282                     """
283                         )
284                         .indented(),
285                 )
286 
287         val expected =
288             """
289 No warnings.
290         """
291                 .trimIndent()
292 
293         check(provider, consumer).expect(expected)
294     }
295 }
296