1/*
2 * Copyright (C) 2016 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
17package androidx.room.processor
18
19import COMMON
20import androidx.room.RoomProcessor
21import androidx.room.solver.query.result.EntityRowAdapter
22import androidx.room.solver.query.result.PojoRowAdapter
23import androidx.room.testing.TestInvocation
24import androidx.room.testing.TestProcessor
25import androidx.room.vo.Database
26import androidx.room.vo.Warning
27import com.google.auto.common.MoreElements
28import com.google.common.truth.Truth
29import com.google.testing.compile.CompileTester
30import com.google.testing.compile.JavaFileObjects
31import com.google.testing.compile.JavaSourcesSubjectFactory
32import com.squareup.javapoet.ClassName
33import org.hamcrest.CoreMatchers.`is`
34import org.hamcrest.CoreMatchers.instanceOf
35import org.hamcrest.CoreMatchers.not
36import org.hamcrest.CoreMatchers.notNullValue
37import org.hamcrest.CoreMatchers.sameInstance
38import org.hamcrest.MatcherAssert.assertThat
39import org.junit.Test
40import org.junit.runner.RunWith
41import org.junit.runners.JUnit4
42import javax.tools.JavaFileObject
43import javax.tools.StandardLocation
44
45@RunWith(JUnit4::class)
46class DatabaseProcessorTest {
47    companion object {
48        const val DATABASE_PREFIX = """
49            package foo.bar;
50            import androidx.room.*;
51            """
52        val DB1: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db1",
53                """
54                $DATABASE_PREFIX
55                @Database(entities = {Book.class}, version = 42)
56                public abstract class Db1 extends RoomDatabase {
57                    abstract BookDao bookDao();
58                }
59                """)
60        val DB2: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db2",
61                """
62                $DATABASE_PREFIX
63                @Database(entities = {Book.class}, version = 42)
64                public abstract class Db2 extends RoomDatabase {
65                    abstract BookDao bookDao();
66                }
67                """)
68        val DB3: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Db3",
69                """
70                $DATABASE_PREFIX
71                @Database(entities = {Book.class}, version = 42)
72                public abstract class Db3 extends RoomDatabase {
73                }
74                """)
75        val USER: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.User",
76                """
77                package foo.bar;
78                import androidx.room.*;
79                @Entity
80                public class User {
81                    @PrimaryKey
82                    int uid;
83                }
84                """)
85        val USER_DAO: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.UserDao",
86                """
87                package foo.bar;
88                import androidx.room.*;
89                @Dao
90                public interface UserDao {
91                    @Query("SELECT * FROM user")
92                    public java.util.List<User> loadAll();
93
94                    @Insert
95                    public void insert(User... users);
96
97                    @Query("SELECT * FROM user where uid = :uid")
98                    public User loadOne(int uid);
99
100                    @TypeConverters(Converter.class)
101                    @Query("SELECT * FROM user where uid = :uid")
102                    public User loadWithConverter(int uid);
103
104                    @Query("SELECT * FROM user where uid = :uid")
105                    public Pojo loadOnePojo(int uid);
106
107                    @Query("SELECT * FROM user")
108                    public java.util.List<Pojo> loadAllPojos();
109
110                    @TypeConverters(Converter.class)
111                    @Query("SELECT * FROM user where uid = :uid")
112                    public Pojo loadPojoWithConverter(int uid);
113
114                    public static class Converter {
115                        @TypeConverter
116                        public static java.util.Date foo(Long input) {return null;}
117                    }
118
119                    public static class Pojo {
120                        public int uid;
121                    }
122                }
123                """)
124        val BOOK: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.Book",
125                """
126                package foo.bar;
127                import androidx.room.*;
128                @Entity
129                public class Book {
130                    @PrimaryKey
131                    int bookId;
132                }
133                """)
134        val BOOK_DAO: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.BookDao",
135                """
136                package foo.bar;
137                import androidx.room.*;
138                @Dao
139                public interface BookDao {
140                    @Query("SELECT * FROM book")
141                    public java.util.List<Book> loadAllBooks();
142                    @Insert
143                    public void insert(Book book);
144                }
145                """)
146    }
147
148    @Test
149    fun simple() {
150        singleDb("""
151            @Database(entities = {User.class}, version = 42)
152            public abstract class MyDb extends RoomDatabase {
153                abstract UserDao userDao();
154            }
155            """, USER, USER_DAO) { db, _ ->
156            assertThat(db.daoMethods.size, `is`(1))
157            assertThat(db.entities.size, `is`(1))
158        }.compilesWithoutError()
159    }
160
161    @Test
162    fun multiple() {
163        singleDb("""
164            @Database(entities = {User.class, Book.class}, version = 42)
165            public abstract class MyDb extends RoomDatabase {
166                abstract UserDao userDao();
167                abstract BookDao bookDao();
168            }
169            """, USER, USER_DAO, BOOK, BOOK_DAO) { db, _ ->
170            assertThat(db.daoMethods.size, `is`(2))
171            assertThat(db.entities.size, `is`(2))
172            assertThat(db.daoMethods.map { it.name }, `is`(listOf("userDao", "bookDao")))
173            assertThat(db.entities.map { it.type.toString() },
174                    `is`(listOf("foo.bar.User", "foo.bar.Book")))
175        }.compilesWithoutError()
176    }
177
178    @Test
179    fun detectMissingBaseClass() {
180        singleDb("""
181            @Database(entities = {User.class, Book.class}, version = 42)
182            public abstract class MyDb {
183            }
184            """, USER, BOOK) { _, _ ->
185        }.failsToCompile().withErrorContaining(ProcessorErrors.DB_MUST_EXTEND_ROOM_DB)
186    }
187
188    @Test
189    fun detectMissingTable() {
190        singleDb(
191                """
192                @Database(entities = {Book.class}, version = 42)
193                public abstract class MyDb extends RoomDatabase {
194                    abstract BookDao bookDao();
195                }
196                """, BOOK, JavaFileObjects.forSourceString("foo.bar.BookDao",
197                """
198                package foo.bar;
199                import androidx.room.*;
200                @Dao
201                public interface BookDao {
202                    @Query("SELECT * FROM nonExistentTable")
203                    public java.util.List<Book> loadAllBooks();
204                }
205                """)) { _, _ ->
206        }.failsToCompile().withErrorContaining("no such table: nonExistentTable")
207    }
208
209    @Test
210    fun detectDuplicateTableNames() {
211        singleDb("""
212                @Database(entities = {User.class, AnotherClass.class}, version = 42)
213                public abstract class MyDb extends RoomDatabase {
214                    abstract UserDao userDao();
215                }
216                """, USER, USER_DAO, JavaFileObjects.forSourceString("foo.bar.AnotherClass",
217                """
218                package foo.bar;
219                import androidx.room.*;
220                @Entity(tableName="user")
221                public class AnotherClass {
222                    @PrimaryKey
223                    int uid;
224                }
225                """)) { _, _ ->
226        }.failsToCompile().withErrorContaining(
227                ProcessorErrors.duplicateTableNames("user",
228                        listOf("foo.bar.User", "foo.bar.AnotherClass"))
229        )
230    }
231
232    @Test
233    fun skipBadQueryVerification() {
234        singleDb(
235                """
236                @SkipQueryVerification
237                @Database(entities = {Book.class}, version = 42)
238                public abstract class MyDb extends RoomDatabase {
239                    abstract BookDao bookDao();
240                }
241                """, BOOK, JavaFileObjects.forSourceString("foo.bar.BookDao",
242                """
243                package foo.bar;
244                import androidx.room.*;
245                @Dao
246                public interface BookDao {
247                    @Query("SELECT nonExistingField FROM Book")
248                    public java.util.List<Book> loadAllBooks();
249                }
250                """)) { _, _ ->
251        }.compilesWithoutError()
252    }
253
254    @Test
255    fun multipleDatabases() {
256        val db1_2 = JavaFileObjects.forSourceString("foo.barx.Db1",
257                """
258                package foo.barx;
259                import androidx.room.*;
260                import foo.bar.*;
261                @Database(entities = {Book.class}, version = 42)
262                public abstract class Db1 extends RoomDatabase {
263                    abstract BookDao bookDao();
264                }
265                """)
266        Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
267                .that(listOf(BOOK, BOOK_DAO, DB1, DB2, db1_2))
268                .processedWith(RoomProcessor())
269                .compilesWithoutError()
270                .and()
271                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar", "Db1_Impl.class")
272                .and()
273                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar", "Db2_Impl.class")
274                .and()
275                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.barx", "Db1_Impl.class")
276                .and()
277                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
278                        "BookDao_Db1_0_Impl.class")
279                .and()
280                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
281                        "BookDao_Db1_1_Impl.class")
282                .and()
283                .generatesFileNamed(StandardLocation.CLASS_OUTPUT, "foo.bar",
284                        "BookDao_Db2_Impl.class")
285    }
286
287    @Test
288    fun twoDaoMethodsForTheSameDao() {
289        singleDb(
290                """
291                @Database(entities = {User.class}, version = 42)
292                public abstract class MyDb extends RoomDatabase {
293                    abstract UserDao userDao();
294                    abstract UserDao userDao2();
295                }
296                """, USER, USER_DAO) { _, _ -> }
297                .failsToCompile()
298                .withErrorContaining(ProcessorErrors.DAO_METHOD_CONFLICTS_WITH_OTHERS)
299                .and()
300                .withErrorContaining(ProcessorErrors.duplicateDao(
301                        ClassName.get("foo.bar", "UserDao"), listOf("userDao", "userDao2")
302                ))
303    }
304
305    @Test
306    fun suppressedWarnings() {
307        singleDb(
308                """
309                @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
310                @Database(entities = {User.class}, version = 42)
311                public abstract class MyDb extends RoomDatabase {
312                    abstract UserDao userDao();
313                }
314                """, USER, USER_DAO) { db, invocation ->
315            assertThat(DatabaseProcessor(invocation.context, db.element)
316                    .context.logger.suppressedWarnings, `is`(setOf(Warning.CURSOR_MISMATCH)))
317        }.compilesWithoutError()
318    }
319
320    @Test
321    fun duplicateIndexNames() {
322        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
323                """
324                package foo.bar;
325                import androidx.room.*;
326                @Entity(indices = {@Index(name ="index_name", value = {"name"})})
327                public class Entity1 {
328                    @PrimaryKey
329                    int uid;
330                    String name;
331                }
332                """)
333
334        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
335                """
336                package foo.bar;
337                import androidx.room.*;
338                @Entity(indices = {@Index(name ="index_name", value = {"anotherName"})})
339                public class Entity2 {
340                    @PrimaryKey
341                    int uid;
342                    String anotherName;
343                }
344                """)
345        singleDb("""
346                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
347                public abstract class MyDb extends RoomDatabase {
348                }
349                """, entity1, entity2) { _, _ ->
350        }.failsToCompile().withErrorContaining(
351                ProcessorErrors.duplicateIndexInDatabase("index_name",
352                        listOf("foo.bar.Entity1 > index_name", "foo.bar.Entity2 > index_name"))
353        )
354    }
355
356    @Test
357    fun foreignKey_missingParent() {
358        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
359                """
360                package foo.bar;
361                import androidx.room.*;
362                @Entity(foreignKeys = @ForeignKey(entity = ${COMMON.USER_TYPE_NAME}.class,
363                        parentColumns = "lastName",
364                        childColumns = "name"))
365                public class Entity1 {
366                    @PrimaryKey
367                    int uid;
368                    String name;
369                }
370                """)
371        singleDb("""
372                @Database(entities = {Entity1.class}, version = 42)
373                public abstract class MyDb extends RoomDatabase {
374                }
375                """, entity1, COMMON.USER) { _, _ ->
376        }.failsToCompile().withErrorContaining(
377                ProcessorErrors.foreignKeyMissingParentEntityInDatabase("User", "foo.bar.Entity1")
378        )
379    }
380
381    @Test
382    fun foreignKey_missingParentIndex() {
383        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
384                """
385                package foo.bar;
386                import androidx.room.*;
387                @Entity(foreignKeys = @ForeignKey(entity = ${COMMON.USER_TYPE_NAME}.class,
388                        parentColumns = "lastName",
389                        childColumns = "name"))
390                public class Entity1 {
391                    @PrimaryKey
392                    int uid;
393                    String name;
394                }
395                """)
396        singleDb("""
397                @Database(entities = {Entity1.class, User.class}, version = 42)
398                public abstract class MyDb extends RoomDatabase {
399                }
400                """, entity1, COMMON.USER) { _, _ ->
401        }.failsToCompile().withErrorContaining(
402                ProcessorErrors.foreignKeyMissingIndexInParent(
403                        parentEntity = COMMON.USER_TYPE_NAME.toString(),
404                        parentColumns = listOf("lastName"),
405                        childEntity = "foo.bar.Entity1",
406                        childColumns = listOf("name")
407                )
408        )
409    }
410
411    @Test
412    fun foreignKey_goodWithPrimaryKey() {
413        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
414                """
415                package foo.bar;
416                import androidx.room.*;
417                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
418                    parentColumns = "uid",
419                    childColumns = "parentId"))
420                public class Entity1 {
421                    @PrimaryKey
422                    int uid;
423                    int parentId;
424                    String name;
425                }
426                """)
427
428        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
429                """
430                package foo.bar;
431                import androidx.room.*;
432                @Entity
433                public class Entity2 {
434                    @PrimaryKey
435                    int uid;
436                    String anotherName;
437                }
438                """)
439        singleDb("""
440                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
441                public abstract class MyDb extends RoomDatabase {
442                }
443                """, entity1, entity2) { _, _ ->
444        }.compilesWithoutError()
445    }
446
447    @Test
448    fun foreignKey_missingParentColumn() {
449        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
450                """
451                package foo.bar;
452                import androidx.room.*;
453                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
454                    parentColumns = {"anotherName", "anotherName2"},
455                    childColumns = {"name", "name2"}))
456                public class Entity1 {
457                    @PrimaryKey
458                    int uid;
459                    String name;
460                    String name2;
461                }
462                """)
463
464        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
465                """
466                package foo.bar;
467                import androidx.room.*;
468                @Entity
469                public class Entity2 {
470                    @PrimaryKey
471                    int uid;
472                    String anotherName;
473                }
474                """)
475        singleDb("""
476                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
477                public abstract class MyDb extends RoomDatabase {
478                }
479                """, entity1, entity2) { _, _ ->
480        }.failsToCompile().withErrorContaining(
481                ProcessorErrors.foreignKeyParentColumnDoesNotExist("foo.bar.Entity2",
482                        "anotherName2", listOf("uid", "anotherName"))
483        )
484    }
485
486    @Test
487    fun foreignKey_goodWithIndex() {
488        val entity1 = JavaFileObjects.forSourceString("foo.bar.Entity1",
489                """
490                package foo.bar;
491                import androidx.room.*;
492                @Entity(foreignKeys = @ForeignKey(entity = Entity2.class,
493                    parentColumns = {"anotherName", "anotherName2"},
494                    childColumns = {"name", "name2"}))
495                public class Entity1 {
496                    @PrimaryKey
497                    int uid;
498                    String name;
499                    String name2;
500                }
501                """)
502
503        val entity2 = JavaFileObjects.forSourceString("foo.bar.Entity2",
504                """
505                package foo.bar;
506                import androidx.room.*;
507                @Entity(indices = @Index(value = {"anotherName2", "anotherName"}, unique = true))
508                public class Entity2 {
509                    @PrimaryKey
510                    int uid;
511                    String anotherName;
512                    String anotherName2;
513                }
514                """)
515        singleDb("""
516                @Database(entities = {Entity1.class, Entity2.class}, version = 42)
517                public abstract class MyDb extends RoomDatabase {
518                }
519                """, entity1, entity2) { _, _ ->
520        }.compilesWithoutError()
521    }
522
523    @Test
524    fun insertNotAReferencedEntity() {
525        singleDb("""
526                @Database(entities = {User.class}, version = 42)
527                public abstract class MyDb extends RoomDatabase {
528                    abstract BookDao bookDao();
529                }
530                """, USER, USER_DAO, BOOK, BOOK_DAO) { _, _ ->
531        }.failsToCompile().withErrorContaining(
532                ProcessorErrors.shortcutEntityIsNotInDatabase(
533                        database = "foo.bar.MyDb",
534                        dao = "foo.bar.BookDao",
535                        entity = "foo.bar.Book"
536                )
537        )
538    }
539
540    @Test
541    fun cache_entity() {
542        singleDb("""
543                @Database(entities = {User.class}, version = 42)
544                @SkipQueryVerification
545                public abstract class MyDb extends RoomDatabase {
546                    public abstract MyUserDao userDao();
547                    @Dao
548                    interface MyUserDao {
549                        @Insert
550                        public void insert(User... users);
551
552                        @Query("SELECT * FROM user where uid = :uid")
553                        public User loadOne(int uid);
554
555                        @TypeConverters(Converter.class)
556                        @Query("SELECT * FROM user where uid = :uid")
557                        public User loadWithConverter(int uid);
558                    }
559                    public static class Converter {
560                        @TypeConverter
561                        public static java.util.Date foo(Long input) {return null;}
562                    }
563                }
564                """, USER, USER_DAO) { db, _ ->
565            val userDao = db.daoMethods.first().dao
566            val insertionMethod = userDao.insertionMethods.find { it.name == "insert" }
567            assertThat(insertionMethod, notNullValue())
568            val loadOne = userDao.queryMethods.find { it.name == "loadOne" }
569            assertThat(loadOne, notNullValue())
570            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapter
571            assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
572            val adapterEntity = (adapter as EntityRowAdapter).entity
573            assertThat(insertionMethod?.entities?.values?.first(), sameInstance(adapterEntity))
574
575            val withConverter = userDao.queryMethods.find { it.name == "loadWithConverter" }
576            assertThat(withConverter, notNullValue())
577            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapter
578            assertThat("test sanity", adapter, instanceOf(EntityRowAdapter::class.java))
579            val convAdapterEntity = (convAdapter as EntityRowAdapter).entity
580            assertThat(insertionMethod?.entities?.values?.first(),
581                    not(sameInstance(convAdapterEntity)))
582
583            assertThat(convAdapterEntity, notNullValue())
584            assertThat(adapterEntity, notNullValue())
585        }.compilesWithoutError()
586    }
587
588    @Test
589    fun cache_pojo() {
590        singleDb("""
591                @Database(entities = {User.class}, version = 42)
592                public abstract class MyDb extends RoomDatabase {
593                    public abstract UserDao userDao();
594                }
595                """, USER, USER_DAO) { db, _ ->
596            val userDao = db.daoMethods.first().dao
597            val loadOne = userDao.queryMethods.find { it.name == "loadOnePojo" }
598            assertThat(loadOne, notNullValue())
599            val adapter = loadOne?.queryResultBinder?.adapter?.rowAdapter
600            assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
601            val adapterPojo = (adapter as PojoRowAdapter).pojo
602
603            val loadAll = userDao.queryMethods.find { it.name == "loadAllPojos" }
604            assertThat(loadAll, notNullValue())
605            val loadAllAdapter = loadAll?.queryResultBinder?.adapter?.rowAdapter
606            assertThat("test sanity", loadAllAdapter, instanceOf(PojoRowAdapter::class.java))
607            val loadAllPojo = (loadAllAdapter as PojoRowAdapter).pojo
608            assertThat(adapter, not(sameInstance(loadAllAdapter)))
609            assertThat(adapterPojo, sameInstance(loadAllPojo))
610
611            val withConverter = userDao.queryMethods.find { it.name == "loadPojoWithConverter" }
612            assertThat(withConverter, notNullValue())
613            val convAdapter = withConverter?.queryResultBinder?.adapter?.rowAdapter
614            assertThat("test sanity", adapter, instanceOf(PojoRowAdapter::class.java))
615            val convAdapterPojo = (convAdapter as PojoRowAdapter).pojo
616            assertThat(convAdapterPojo, notNullValue())
617            assertThat(convAdapterPojo, not(sameInstance(adapterPojo)))
618        }.compilesWithoutError()
619    }
620
621    @Test
622    fun daoConstructor_RoomDatabase() {
623        assertConstructor(listOf(DB1), "BookDao(RoomDatabase db) {}")
624                .compilesWithoutError()
625    }
626
627    @Test
628    fun daoConstructor_specificDatabase() {
629        assertConstructor(listOf(DB1), "BookDao(Db1 db) {}")
630                .compilesWithoutError()
631    }
632
633    @Test
634    fun daoConstructor_wrongDatabase() {
635        assertConstructor(listOf(DB1, DB3), "BookDao(Db3 db) {}")
636                .failsToCompile()
637                .withErrorContaining(ProcessorErrors
638                        .daoMustHaveMatchingConstructor("foo.bar.BookDao", "foo.bar.Db1"))
639    }
640
641    @Test
642    fun daoConstructor_multipleDatabases_RoomDatabase() {
643        assertConstructor(listOf(DB1, DB2), "BookDao(RoomDatabase db) {}")
644                .compilesWithoutError()
645    }
646
647    @Test
648    fun daoConstructor_multipleDatabases_specificDatabases() {
649        assertConstructor(listOf(DB1, DB2), """
650                    BookDao(Db1 db) {}
651                    BookDao(Db2 db) {}
652                """)
653                .compilesWithoutError()
654    }
655
656    @Test
657    fun daoConstructor_multipleDatabases_empty() {
658        assertConstructor(listOf(DB1, DB2), """
659                    BookDao(Db1 db) {}
660                    BookDao() {} // Db2 uses this
661                """)
662                .compilesWithoutError()
663    }
664
665    @Test
666    fun daoConstructor_multipleDatabases_noMatch() {
667        assertConstructor(listOf(DB1, DB2), """
668                    BookDao(Db1 db) {}
669                """)
670                .failsToCompile()
671                .withErrorContaining(ProcessorErrors
672                        .daoMustHaveMatchingConstructor("foo.bar.BookDao", "foo.bar.Db2"))
673    }
674
675    fun assertConstructor(dbs: List<JavaFileObject>, constructor: String): CompileTester {
676        val bookDao = JavaFileObjects.forSourceString("foo.bar.BookDao",
677                """
678                package foo.bar;
679                import androidx.room.*;
680                @Dao
681                public abstract class BookDao {
682                    $constructor
683                }
684                """)
685        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
686                .that(listOf(BOOK, bookDao) + dbs)
687                .processedWith(RoomProcessor())
688    }
689
690    fun singleDb(input: String, vararg otherFiles: JavaFileObject,
691                 handler: (Database, TestInvocation) -> Unit): CompileTester {
692        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
693                .that(otherFiles.toMutableList()
694                        + JavaFileObjects.forSourceString("foo.bar.MyDb",
695                        DATABASE_PREFIX + input
696                ))
697                .processedWith(TestProcessor.builder()
698                        .forAnnotations(androidx.room.Database::class)
699                        .nextRunHandler { invocation ->
700                            val entity = invocation.roundEnv
701                                    .getElementsAnnotatedWith(
702                                            androidx.room.Database::class.java)
703                                    .first()
704                            val parser = DatabaseProcessor(invocation.context,
705                                    MoreElements.asType(entity))
706                            val parsedDb = parser.process()
707                            handler(parsedDb, invocation)
708                            true
709                        }
710                        .build())
711    }
712}
713