1 /*
2  * Copyright (C) 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 android.adservices.lint.test
18 
19 import android.adservices.lint.prod.RoomDatabaseMigrationDetector
20 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
21 import com.android.tools.lint.checks.infrastructure.TestFile
22 import com.android.tools.lint.checks.infrastructure.TestLintTask
23 import com.android.tools.lint.detector.api.Detector
24 import com.android.tools.lint.detector.api.Issue
25 import org.junit.Test
26 import org.junit.runner.RunWith
27 import org.junit.runners.JUnit4
28 
29 @RunWith(JUnit4::class)
30 class RoomDatabaseMigrationDetectorTest : LintDetectorTest() {
getDetectornull31     override fun getDetector(): Detector = RoomDatabaseMigrationDetector()
32 
33     override fun getIssues(): List<Issue> =
34         listOf(
35             RoomDatabaseMigrationDetector.ISSUE_ERROR,
36             RoomDatabaseMigrationDetector.ISSUE_WARNING
37         )
38 
39     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
40 
41     @Test
42     fun testMigrationPath_happyCase() {
43         lint()
44             .files(
45                 java(
46                     """
47 package com.android.adservices.service.common.fake.packagename;
48 
49 import androidx.room.AutoMigration;
50 import androidx.room.Database;
51 import androidx.room.RoomDatabase;
52 
53 @Database(
54     entities = {},
55     autoMigrations = {
56         @AutoMigration(from = 1, to = 2),
57     },
58     version = FakeDatabase.DATABASE_VERSION)
59 public class FakeDatabase extends RoomDatabase {
60     public static final int DATABASE_VERSION = 2;
61     public static final String DATABASE_NAME = "fake.db";
62 
63     // Singleton and dao declaration.
64 }
65 """
66                 ),
67                 java(
68                     """
69 package com.android.adservices.data;
70 
71 import com.android.adservices.service.common.fake.packagename.FakeDatabase;
72 
73 class RoomDatabaseRegistration {
74     FakeDatabase mFakeDatabase;
75 }
76 """
77                 ),
78                 *stubs
79             )
80             .issues(RoomDatabaseMigrationDetector.ISSUE_ERROR)
81             .run()
82             .expectClean()
83     }
84 
85     @Test
testMigrationPath_missingDatabaseAnnotationErrornull86     fun testMigrationPath_missingDatabaseAnnotationError() {
87         lint()
88             .files(
89                 java(
90                     """
91 package com.android.adservices.service.common.fake.packagename;
92 
93 import androidx.room.AutoMigration;
94 import androidx.room.Database;
95 import androidx.room.RoomDatabase;
96 
97 public class FakeDatabase extends RoomDatabase {
98     public static final String DATABASE_NAME = "fake.db";
99 
100     // Singleton and dao declaration.
101 }
102 """
103                 ),
104                 java(
105                     """
106 package com.android.adservices.data;
107 
108 import com.android.adservices.service.common.fake.packagename.FakeDatabase;
109 
110 class RoomDatabaseRegistration {
111     FakeDatabase mFakeDatabase;
112 }
113 """
114                 ),
115                 *stubs
116             )
117             .issues(RoomDatabaseMigrationDetector.ISSUE_ERROR)
118             .run()
119             .expectContains(RoomDatabaseMigrationDetector.MISSING_DATABASE_ANNOTATION_ERROR)
120             .expectContains(createErrorCountString(1, 0))
121     }
122 
123     @Test
testMigrationPath_missingMigrationPathAttributenull124     fun testMigrationPath_missingMigrationPathAttribute() {
125         lint()
126             .files(
127                 java(
128                     """
129 package com.android.adservices.service.common.fake.packagename;
130 
131 import androidx.room.AutoMigration;
132 import androidx.room.Database;
133 import androidx.room.RoomDatabase;
134 
135 @Database(
136     entities = {},
137     version = FakeDatabase.DATABASE_VERSION)
138 public class FakeDatabase extends RoomDatabase {
139     public static final int DATABASE_VERSION = 2;
140     public static final String DATABASE_NAME = "fake.db";
141 
142     // Singleton and dao declaration.
143 }
144 """
145                 ),
146                 java(
147                     """
148 package com.android.adservices.data;
149 
150 import com.android.adservices.service.common.fake.packagename.FakeDatabase;
151 
152 class RoomDatabaseRegistration {
153     FakeDatabase mFakeDatabase;
154 }
155 """
156                 ),
157                 *stubs
158             )
159             .issues(RoomDatabaseMigrationDetector.ISSUE_ERROR)
160             .run()
161             .expectContains(RoomDatabaseMigrationDetector.MISSING_AUTO_MIGRATION_ATTRIBUTE_ERROR)
162             .expectContains(createErrorCountString(1, 0))
163     }
164     @Test
testMigrationPath_incompleteMigrationPathnull165     fun testMigrationPath_incompleteMigrationPath() {
166         lint()
167             .files(
168                 java(
169                     """
170 package com.android.adservices.service.common.fake.packagename;
171 
172 import androidx.room.AutoMigration;
173 import androidx.room.Database;
174 import androidx.room.RoomDatabase;
175 
176 @Database(
177     entities = {},
178     autoMigrations = {
179         @AutoMigration(from = 1, to = 2),
180     },
181     version = FakeDatabase.DATABASE_VERSION)
182 public class FakeDatabase extends RoomDatabase {
183     public static final int DATABASE_VERSION = 3;
184     public static final String DATABASE_NAME = "fake.db";
185 
186     // Singleton and dao declaration.
187 }
188 """
189                 ),
190                 java(
191                     """
192 package com.android.adservices.data;
193 
194 import com.android.adservices.service.common.fake.packagename.FakeDatabase;
195 
196 class RoomDatabaseRegistration {
197     FakeDatabase mFakeDatabase;
198 }
199 """
200                 ),
201                 *stubs
202             )
203             .issues(RoomDatabaseMigrationDetector.ISSUE_ERROR)
204             .run()
205             .expectContains(RoomDatabaseMigrationDetector.INCOMPLETE_MIGRATION_PATH_ERROR)
206             .expectContains(createErrorCountString(0, 1))
207     }
208 
209     @Test
testMigrationPath_missingDatabaseVersionFieldInClassnull210     fun testMigrationPath_missingDatabaseVersionFieldInClass() {
211         lint()
212             .files(
213                 java(
214                     """
215 package com.android.adservices.service.common.fake.packagename;
216 
217 import androidx.room.AutoMigration;
218 import androidx.room.Database;
219 import androidx.room.RoomDatabase;
220 
221 @Database(
222     entities = {},
223     autoMigrations = {
224         @AutoMigration(from = 1, to = 2),
225     },
226     version = 2)
227 public class FakeDatabase extends RoomDatabase {
228     public static final String DATABASE_NAME = "fake.db";
229 
230     // Singleton and dao declaration.
231 }
232 """
233                 ),
234                 java(
235                     """
236 package com.android.adservices.data;
237 
238 import com.android.adservices.service.common.fake.packagename.FakeDatabase;
239 
240 class RoomDatabaseRegistration {
241     FakeDatabase mFakeDatabase;
242 }
243 """
244                 ),
245                 *stubs
246             )
247             .issues(RoomDatabaseMigrationDetector.ISSUE_ERROR)
248             .run()
249             .expectContains(RoomDatabaseMigrationDetector.MISSING_DATABASE_VERSION_FIELD_ERROR)
250             .expectContains(createErrorCountString(1, 0))
251     }
252 
253     @Test
testMigrationPath_missingDatabaseVersionAnnotationFieldnull254     fun testMigrationPath_missingDatabaseVersionAnnotationField() {
255         lint()
256             .files(
257                 java(
258                     """
259 package com.android.adservices.service.common.fake.packagename;
260 
261 import androidx.room.AutoMigration;
262 import androidx.room.Database;
263 import androidx.room.RoomDatabase;
264 
265 @Database(
266     entities = {},
267     autoMigrations = {
268         @AutoMigration(from = 1, to = 2),
269     })
270 public class FakeDatabase extends RoomDatabase {
271     public static final String DATABASE_NAME = "fake.db";
272 
273     // Singleton and dao declaration.
274 }
275 """
276                 ),
277                 java(
278                     """
279 package com.android.adservices.data;
280 
281 import com.android.adservices.service.common.fake.packagename.FakeDatabase;
282 
283 class RoomDatabaseRegistration {
284     FakeDatabase mFakeDatabase;
285 }
286 """
287                 ),
288                 *stubs
289             )
290             .issues(RoomDatabaseMigrationDetector.ISSUE_ERROR)
291             .run()
292             .expectContains(
293                 RoomDatabaseMigrationDetector.MISSING_DATABASE_VERSION_ANNOTATION_ATTRIBUTE_ERROR
294             )
295             .expectContains(RoomDatabaseMigrationDetector.MISSING_DATABASE_VERSION_FIELD_ERROR)
296             .expectContains(createErrorCountString(2, 0))
297     }
298 
299     @Test
testMigrationPath_failedToReferenceDatabaseVersionErrornull300     fun testMigrationPath_failedToReferenceDatabaseVersionError() {
301         lint()
302             .files(
303                 java(
304                     """
305 package com.android.adservices.service.common.fake.packagename;
306 
307 import androidx.room.AutoMigration;
308 import androidx.room.Database;
309 import androidx.room.RoomDatabase;
310 
311 @Database(
312     entities = {},
313     autoMigrations = {
314         @AutoMigration(from = 1, to = 2),
315     },
316     version = 2)
317 public class FakeDatabase extends RoomDatabase {
318     public static final int DATABASE_VERSION = 2;
319     public static final String DATABASE_NAME = "fake.db";
320 
321     // Singleton and dao declaration.
322 }
323 """
324                 ),
325                 java(
326                     """
327 package com.android.adservices.data;
328 
329 import com.android.adservices.service.common.fake.packagename.FakeDatabase;
330 
331 class RoomDatabaseRegistration {
332     FakeDatabase mFakeDatabase;
333 }
334 """
335                 ),
336                 *stubs
337             )
338             .issues(RoomDatabaseMigrationDetector.ISSUE_ERROR)
339             .run()
340             .expectContains(
341                 RoomDatabaseMigrationDetector.FAILED_REF_VERSION_FIELD_IN_ANNOTATION_ERROR
342             )
343             .expectContains(createErrorCountString(1, 0))
344     }
345 
346     @Test
testMigrationPath_schemaExportFalseErrornull347     fun testMigrationPath_schemaExportFalseError() {
348         lint()
349             .files(
350                 java(
351                     """
352 package com.android.adservices.service.common.fake.packagename;
353 
354 import androidx.room.AutoMigration;
355 import androidx.room.Database;
356 import androidx.room.RoomDatabase;
357 
358 @Database(
359     entities = {},
360     autoMigrations = {
361         @AutoMigration(from = 1, to = 2),
362     },
363     version = FakeDatabase.DATABASE_VERSION,
364     exportSchema = false)
365 public class FakeDatabase extends RoomDatabase {
366     public static final int DATABASE_VERSION = 2;
367     public static final String DATABASE_NAME = "fake.db";
368 
369     // Singleton and dao declaration.
370 }
371 """
372                 ),
373                 java(
374                     """
375 package com.android.adservices.data;
376 
377 import com.android.adservices.service.common.fake.packagename.FakeDatabase;
378 
379 class RoomDatabaseRegistration {
380     FakeDatabase mFakeDatabase;
381 }
382 """
383                 ),
384                 *stubs
385             )
386             .issues(RoomDatabaseMigrationDetector.ISSUE_ERROR)
387             .run()
388             .expectContains(RoomDatabaseMigrationDetector.SCHEMA_EXPORT_FALSE_ERROR)
389             .expectContains(createErrorCountString(1, 0))
390     }
391 
392     @Test
testMigrationPath_schemaExportTrue_happyCasenull393     fun testMigrationPath_schemaExportTrue_happyCase() {
394         lint()
395             .files(
396                 java(
397                     """
398 package com.android.adservices.service.common.fake.packagename;
399 
400 import androidx.room.AutoMigration;
401 import androidx.room.Database;
402 import androidx.room.RoomDatabase;
403 
404 @Database(
405     entities = {},
406     autoMigrations = {
407         @AutoMigration(from = 1, to = 2),
408     },
409     version = FakeDatabase.DATABASE_VERSION,
410     exportSchema = true)
411 public class FakeDatabase extends RoomDatabase {
412     public static final int DATABASE_VERSION = 2;
413     public static final String DATABASE_NAME = "fake.db";
414 
415     // Singleton and dao declaration.
416 }
417 """
418                 ),
419                 java(
420                     """
421 package com.android.adservices.data;
422 
423 import com.android.adservices.service.common.fake.packagename.FakeDatabase;
424 
425 class RoomDatabaseRegistration {
426     FakeDatabase mFakeDatabase;
427 }
428 """
429                 ),
430                 *stubs
431             )
432             .issues(RoomDatabaseMigrationDetector.ISSUE_ERROR)
433             .run()
434             .expectClean()
435     }
436 
437     @Test
testMigrationPath_databaseNotRegisterednull438     fun testMigrationPath_databaseNotRegistered() {
439         lint()
440             .files(
441                 java(
442                     """
443 package com.android.adservices.service.common.fake.packagename;
444 
445 import androidx.room.AutoMigration;
446 import androidx.room.Database;
447 import androidx.room.RoomDatabase;
448 
449 @Database(
450     entities = {},
451     autoMigrations = {
452         @AutoMigration(from = 1, to = 2),
453     },
454     version = FakeDatabase.DATABASE_VERSION)
455 public class FakeDatabase extends RoomDatabase {
456     public static final int DATABASE_VERSION = 2;
457     public static final String DATABASE_NAME = "fake.db";
458 
459     // Singleton and dao declaration.
460 }
461 """
462                 ),
463                 java(
464                     """
465 package com.android.adservices.data;
466 
467 import com.android.adservices.service.common.fake.packagename.FakeDatabase;
468 
469 class RoomDatabaseRegistration {
470 }
471 """
472                 ),
473                 *stubs
474             )
475             .issues(RoomDatabaseMigrationDetector.ISSUE_ERROR)
476             .run()
477             .expectContains(
478                 RoomDatabaseMigrationDetector.DATABASE_NOT_REGISTERED_ERROR.format(
479                     "com.android.adservices.service.common.fake.packagename.FakeDatabase"
480                 )
481             )
482             .expectContains(createErrorCountString(1, 0))
483     }
484 
485     @Test
testMigrationPath_databaseRegisterClassMissingnull486     fun testMigrationPath_databaseRegisterClassMissing() {
487         lint()
488             .files(
489                 java(
490                     """
491 package com.android.adservices.service.common.fake.packagename;
492 
493 import androidx.room.AutoMigration;
494 import androidx.room.Database;
495 import androidx.room.RoomDatabase;
496 
497 @Database(
498     entities = {},
499     autoMigrations = {
500         @AutoMigration(from = 1, to = 2),
501     },
502     version = FakeDatabase.DATABASE_VERSION)
503 public class FakeDatabase extends RoomDatabase {
504     public static final int DATABASE_VERSION = 2;
505     public static final String DATABASE_NAME = "fake.db";
506 
507     // Singleton and dao declaration.
508 }
509 """
510                 ),
511                 *stubs
512             )
513             .issues(RoomDatabaseMigrationDetector.ISSUE_ERROR)
514             .run()
515             .expectContains(RoomDatabaseMigrationDetector.DATABASE_REGISTRATION_CLASS_MISSING_ERROR)
516             .expectContains(createErrorCountString(1, 0))
517     }
518 
519     @Test
testMigrationPath_noDatabaseClass_happyCasenull520     fun testMigrationPath_noDatabaseClass_happyCase() {
521         lint().files(*stubs).issues(RoomDatabaseMigrationDetector.ISSUE_ERROR).run().expectClean()
522     }
523 
524     private val database: TestFile =
525         kotlin(
526             """
527 package androidx.room
528 
529 @kotlin.annotation.Target @kotlin.annotation.Retention public final annotation class Database public constructor(entities: kotlin.Array<kotlin.reflect.KClass<*>> /* = compiled code */, views: kotlin.Array<kotlin.reflect.KClass<*>> /* = compiled code */, version: kotlin.Int, exportSchema: kotlin.Boolean /* = compiled code */, autoMigrations: kotlin.Array<androidx.room.AutoMigration> /* = compiled code */) : kotlin.Annotation {
530     public final val autoMigrations: kotlin.Array<androidx.room.AutoMigration> /* compiled code */
531 
532     public final val entities: kotlin.Array<kotlin.reflect.KClass<*>> /* compiled code */
533 
534     public final val exportSchema: kotlin.Boolean /* compiled code */
535 
536     public final val version: kotlin.Int /* compiled code */
537 
538     public final val views: kotlin.Array<kotlin.reflect.KClass<*>> /* compiled code */
539 }
540 """
541         )
542 
543     private val autoMigration: TestFile =
544         kotlin(
545             """
546 package androidx.room
547 
548 @kotlin.annotation.Target @kotlin.annotation.Retention public final annotation class AutoMigration public constructor(from: kotlin.Int, to: kotlin.Int, spec: kotlin.reflect.KClass<*> /* = compiled code */) : kotlin.Annotation {
549     public final val from: kotlin.Int /* compiled code */
550 
551     public final val spec: kotlin.reflect.KClass<*> /* compiled code */
552 
553     public final val to: kotlin.Int /* compiled code */
554 }"""
555         )
556 
557     private val roomDatabase: TestFile =
558         kotlin(
559             """
560 package androidx.room
561 
562 abstract class RoomDatabase
563 """
564                 .trimIndent()
565         )
566 
567     private val stubs = arrayOf(database, roomDatabase, autoMigration)
568 
createErrorCountStringnull569     private fun createErrorCountString(errors: Int, warnings: Int): String {
570         return "%d errors, %d warnings".format(errors, warnings)
571     }
572 }
573