1 /*
2  * 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 @file:Suppress("UnstableApiUsage")
18 
19 package androidx.wear.protolayout.lint
20 
21 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
22 import org.junit.Test
23 import org.junit.runner.RunWith
24 import org.junit.runners.JUnit4
25 
26 @RunWith(JUnit4::class)
27 class ProtoLayoutMinSchemaDetectorTest : LintDetectorTest() {
getDetectornull28     override fun getDetector() = ProtoLayoutMinSchemaDetector()
29 
30     override fun getIssues() = mutableListOf(ProtoLayoutMinSchemaDetector.ISSUE)
31 
32     private val requiresSchemaAnnotationStub =
33         java(
34             """
35             package androidx.wear.protolayout.expression;
36 
37             @Retention(CLASS)
38             @Target({TYPE, METHOD, CONSTRUCTOR, FIELD})
39             public @interface RequiresSchemaVersion {
40                 int major();
41                 int minor();
42             }
43         """
44                 .trimIndent()
45         )
46     private val requiresApiAnnotationStub =
47         kotlin(
48             """
49             package androidx.annotation
50             @Retention(AnnotationRetention.BINARY)
51             @Target(
52                 AnnotationTarget.ANNOTATION_CLASS,
53                 AnnotationTarget.CLASS,
54                 AnnotationTarget.FUNCTION,
55                 AnnotationTarget.PROPERTY_GETTER,
56                 AnnotationTarget.PROPERTY_SETTER,
57                 AnnotationTarget.CONSTRUCTOR,
58                 AnnotationTarget.FIELD,
59                 AnnotationTarget.FILE
60             )
61             public actual annotation class RequiresApi(
62                 val value: Int = 1,
63                 val api: Int = 1
64             )
65             """
66                 .trimIndent()
67         )
68 
69     @Test
70     fun `calling V1_0 API doesn't`() {
71         lint()
72             .files(
73                 requiresSchemaAnnotationStub,
74                 kotlin(
75                         """
76             package foo
77             import androidx.wear.protolayout.expression.RequiresSchemaVersion
78 
79             @RequiresSchemaVersion(major=1, minor=0)
80             class WithAnnotation {
81               fun unAnnotatedMethod(){}
82             }
83                 """
84                     )
85                     .indented(),
86                 kotlin(
87                         """
88             package foo
89             import androidx.wear.protolayout.expression.RequiresSchemaVersion
90 
91             class Bar {
92               @RequiresSchemaVersion(major=1, minor=0)
93               fun bar() {}
94 
95               fun baz() { bar() }
96             }
97         """
98                     )
99                     .indented()
100             )
101             .issues(ProtoLayoutMinSchemaDetector.ISSUE)
102             .run()
103             .expectClean()
104     }
105 
106     @Test
107     fun `calling V1_2 API requires SDK version check`() {
108         lint()
109             .files(
110                 requiresSchemaAnnotationStub,
111                 kotlin(
112                         """
113             package foo
114             import androidx.wear.protolayout.expression.RequiresSchemaVersion
115 
116             @RequiresSchemaVersion(major=1, minor=200)
117             class WithAnnotation {
118               fun unAnnotatedMethod(){}
119 
120               @RequiresSchemaVersion(major=1, minor=200)
121               fun annotatedMethod(){}
122 
123               @RequiresSchemaVersion(major=1, minor=200)
124               fun unreferencedMethod(){}
125 
126               companion object {
127                 @RequiresSchemaVersion(major=1, minor=200)
128                 const val ANNOTATED_CONST = 10
129               }
130             }
131                 """
132                     )
133                     .indented(),
134                 kotlin(
135                         """
136             package foo
137             import androidx.wear.protolayout.expression.RequiresSchemaVersion
138 
139             class Bar {
140               private val withAnnotation = WithAnnotation()
141               private val fieldAssignment = withAnnotation.annotatedMethod()
142 
143               @RequiresSchemaVersion(major=1, minor=200)
144               fun bar() {}
145 
146               fun baz() {
147                 bar()
148                 withAnnotation.unAnnotatedMethod()
149                 withAnnotation.annotatedMethod()
150                 //TODO: b/308552481 - This should fail
151                 val b = withAnnotation.ANNOTATED_CONST
152               }
153             }
154         """
155                     )
156                     .indented()
157             )
158             .issues(ProtoLayoutMinSchemaDetector.ISSUE)
159             .run()
160             .expect(
161                 """
162 src/foo/Bar.kt:6: Error: This API is not guaranteed to be available on the device (requires schema 1.200). [ProtoLayoutMinSchema]
163   private val fieldAssignment = withAnnotation.annotatedMethod()
164                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
165 src/foo/Bar.kt:12: Error: This API is not guaranteed to be available on the device (requires schema 1.200). [ProtoLayoutMinSchema]
166     bar()
167     ~~~~~
168 src/foo/Bar.kt:14: Error: This API is not guaranteed to be available on the device (requires schema 1.200). [ProtoLayoutMinSchema]
169     withAnnotation.annotatedMethod()
170     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
171 3 errors, 0 warnings
172             """
173                     .trimIndent()
174             )
175 
176         lint()
177             .files(
178                 requiresSchemaAnnotationStub,
179                 kotlin(
180                         """
181             package foo
182             import androidx.wear.protolayout.expression.RequiresSchemaVersion
183 
184             @RequiresSchemaVersion(major=1, minor=200)
185             class WithAnnotation {
186               fun unAnnotatedMethod(){}
187 
188               @RequiresSchemaVersion(major=1, minor=200)
189               fun annotatedMethod(){}
190 
191               @RequiresSchemaVersion(major=1, minor=200)
192               fun unreferencedMethod(){}
193             }
194                 """
195                     )
196                     .indented(),
197                 kotlin(
198                         """
199             package foo
200             import androidx.wear.protolayout.expression.RequiresSchemaVersion
201             import android.os.Build
202 
203             class Bar {
204               private val withAnnotation = WithAnnotation()
205 
206               @RequiresSchemaVersion(major=1, minor=200)
207               fun bar() {}
208 
209               fun baz() {
210                 if (Build.VERSION.SDK_INT >= 33) {
211                   bar()
212                   withAnnotation.unAnnotatedMethod()
213                   withAnnotation.annotatedMethod()
214                 }
215               }
216             }
217         """
218                     )
219                     .indented()
220             )
221             .issues(ProtoLayoutMinSchemaDetector.ISSUE)
222             .run()
223             .expectClean()
224     }
225 
226     @Test
227     fun `calling V1_2 API requires SDK version check (Java)`() {
228         lint()
229             .files(
230                 requiresSchemaAnnotationStub,
231                 java(
232                         """
233             package foo;
234             import androidx.wear.protolayout.expression.RequiresSchemaVersion;
235 
236             class Bar {
237               @RequiresSchemaVersion(major=1, minor=200)
238               public static final int ANNOTATED_CONSTANT = 10;
239 
240               @RequiresSchemaVersion(major=1, minor=200)
241               void bar() {}
242 
243               void baz() {
244                 bar();
245                 // TODO: b/308552481: This should fail
246                 int t = ANNOTATED_CONSTANT;
247               }
248             }
249         """
250                     )
251                     .indented()
252             )
253             .issues(ProtoLayoutMinSchemaDetector.ISSUE)
254             .run()
255             .expect(
256                 """
257 src/foo/Bar.java:12: Error: This API is not guaranteed to be available on the device (requires schema 1.200). [ProtoLayoutMinSchema]
258     bar();
259     ~~~~~
260 1 errors, 0 warnings
261             """
262                     .trimIndent()
263             )
264 
265         lint()
266             .files(
267                 requiresSchemaAnnotationStub,
268                 java(
269                         """
270             package foo;
271             import androidx.wear.protolayout.expression.RequiresSchemaVersion;
272             import android.os.Build;
273 
274             class Bar {
275               @RequiresSchemaVersion(major=1, minor=200)
276               void bar() {}
277 
278               void baz() {
279                 if (Build.VERSION.SDK_INT >= 33) {
280                   bar();
281                 }
282               }
283             }
284         """
285                     )
286                     .indented()
287             )
288             .issues(ProtoLayoutMinSchemaDetector.ISSUE)
289             .run()
290             .expectClean()
291     }
292 
293     @Test
294     fun `annotated call-site doesn't requires SDK version check`() {
295         lint()
296             .files(
297                 requiresSchemaAnnotationStub,
298                 requiresApiAnnotationStub,
299                 kotlin(
300                         """
301             package foo
302             import androidx.wear.protolayout.expression.RequiresSchemaVersion
303 
304             class BarK {
305               @RequiresSchemaVersion(major=1, minor=200)
306               private val fieldAssignment = bar()
307 
308               @RequiresSchemaVersion(major=1, minor=200)
309               fun bar() = 1
310 
311               @RequiresSchemaVersion(major=1, minor=200)
312               fun baz2() { bar() }
313 
314               @RequiresSchemaVersion(major=1, minor=300)
315               fun baz3() { bar() }
316             }
317             """
318                     )
319                     .indented(),
320                 java(
321                         """
322             package foo;
323             import androidx.wear.protolayout.expression.RequiresSchemaVersion;
324 
325             class BarJ {
326               @RequiresSchemaVersion(major=1, minor=200)
327               private static final int fieldAssignment = bar();
328 
329               @RequiresSchemaVersion(major=1, minor=200)
330               public static int bar() { return 1;}
331 
332               @RequiresSchemaVersion(major=1, minor=200)
333               void baz2() { bar(); }
334 
335               @RequiresSchemaVersion(major=1, minor=300)
336               void baz3() { bar(); }
337             }
338         """
339                     )
340                     .indented(),
341                 kotlin(
342                         """
343             package foo
344             import androidx.wear.protolayout.expression.RequiresSchemaVersion
345             import androidx.annotation.RequiresApi
346 
347             @RequiresApi(33)
348             class BazK {
349               private val fieldAssignment = bar()
350 
351               @RequiresSchemaVersion(major=1, minor=200)
352               fun bar() = 1
353 
354               @RequiresApi(30)
355               fun baz2() { bar() }
356 
357               @RequiresApi(34)
358               fun baz3() { BarJ.baz3() }
359             }
360             """
361                     )
362                     .indented()
363             )
364             .issues(ProtoLayoutMinSchemaDetector.ISSUE)
365             .run()
366             .expectClean()
367     }
368 
369     @Test
project with proper minSdk doesn't requires SDK version checknull370     fun `project with proper minSdk doesn't requires SDK version check`() {
371         lint()
372             .files(
373                 manifest().minSdk(34),
374                 requiresSchemaAnnotationStub,
375                 kotlin(
376                         """
377             package foo
378             import androidx.wear.protolayout.expression.RequiresSchemaVersion
379 
380             class BarK {
381               @RequiresSchemaVersion(major=1, minor=200)
382               private val fieldAssignment = bar()
383 
384               @RequiresSchemaVersion(major=1, minor=200)
385               fun bar() = 1
386 
387               @RequiresSchemaVersion(major=1, minor=200)
388               fun baz2() { bar() }
389 
390               @RequiresSchemaVersion(major=1, minor=300)
391               fun baz3() { bar() }
392             }
393             """
394                     )
395                     .indented(),
396                 java(
397                         """
398             package foo;
399             import androidx.wear.protolayout.expression.RequiresSchemaVersion;
400 
401             class BarJ {
402               private static final int fieldAssignment = bar();
403 
404               fun bar() {}
405 
406               fun baz2() { bar(); }
407 
408               fun baz3() { bar(); }
409             }
410         """
411                     )
412                     .indented()
413             )
414             .issues(ProtoLayoutMinSchemaDetector.ISSUE)
415             .run()
416             .expectClean()
417     }
418 }
419