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