[go: nahoru, domu]

blob: cea5ba44734280ef716b49686b7366469cd23104 [file] [log] [blame]
Jakub Gielzak45513aa2020-01-03 14:16:12 +00001/*
2 * Copyright 2020 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.sqlite.inspection.test
18
Jakub Gielzak21ca3982020-02-07 15:10:33 +000019import android.database.sqlite.SQLiteDatabase
Jakub Gielzak45513aa2020-01-03 14:16:12 +000020import androidx.sqlite.inspection.test.MessageFactory.createGetSchemaCommand
21import androidx.sqlite.inspection.test.MessageFactory.createTrackDatabasesCommand
22import androidx.test.ext.junit.runners.AndroidJUnit4
23import androidx.test.filters.MediumTest
Jakub Gielzak21ca3982020-02-07 15:10:33 +000024import androidx.test.filters.SdkSuppress
Jakub Gielzakb5456bf2020-01-13 17:39:03 +000025import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
Jakub Gielzak45513aa2020-01-03 14:16:12 +000026import com.google.common.truth.Truth.assertThat
Jakub Gielzak45513aa2020-01-03 14:16:12 +000027import kotlinx.coroutines.runBlocking
28import org.junit.Rule
29import org.junit.Test
30import org.junit.rules.TemporaryFolder
31import org.junit.runner.RunWith
32
33@MediumTest
34@RunWith(AndroidJUnit4::class)
Jakub Gielzak21ca3982020-02-07 15:10:33 +000035@SdkSuppress(minSdkVersion = 26)
Jakub Gielzak45513aa2020-01-03 14:16:12 +000036class GetSchemaTest {
37 @get:Rule
38 val testEnvironment = SqliteInspectorTestEnvironment()
39
40 @get:Rule
Jakub Gielzakb5456bf2020-01-13 17:39:03 +000041 val temporaryFolder = TemporaryFolder(getInstrumentation().context.cacheDir)
Jakub Gielzak45513aa2020-01-03 14:16:12 +000042
43 @Test
44 fun test_get_schema_complex_tables() {
45 test_get_schema(
46 listOf(
47 Database(
48 "db1",
49 Table(
50 "table1",
51 Column("t", "TEXT"),
52 Column("nu", "NUMERIC"),
53 Column("i", "INTEGER"),
54 Column("r", "REAL"),
55 Column("b", "BLOB")
56 ),
57 Table(
58 "table2",
59 Column("id", "INTEGER"),
60 Column("name", "TEXT")
61
Jakub Gielzak21ca3982020-02-07 15:10:33 +000062 ),
63 Table(
64 "table3a",
65 Column("c1", "INT"),
66 Column("c2", "INT", primaryKey = 1)
67 ),
68 Table(
69 "table3b", // compound-primary-key
70 Column("c1", "INT", primaryKey = 2),
71 Column("c2", "INT", primaryKey = 1)
72 ),
73 Table(
74 "table4", // compound-primary-key, two unique columns
75 Column("c1", "INT", primaryKey = 1),
76 Column("c2", "INT", primaryKey = 2, isUnique = true),
77 Column("c3", "INT", isUnique = true)
78 ),
79 Table(
80 "table5", // mix: unique, primary key, notNull
81 Column("c1", "INT", isNotNull = true),
82 Column("c2", "INT", primaryKey = 1, isUnique = true),
83 Column("c3", "INT", isUnique = true, isNotNull = true)
84 ),
85 Table(
86 "table6", // compound-unique-constraint-indices in [onDatabaseCreated]
87 Column("c1", "INT"),
88 Column("c2uuu", "INT", isUnique = true),
89 Column("c3", "INT"),
90 Column("c4u", "INT", isUnique = true)
Jakub Gielzak45513aa2020-01-03 14:16:12 +000091 )
92 )
Jakub Gielzak21ca3982020-02-07 15:10:33 +000093 ),
94 onDatabaseCreated = { db ->
95 // compound-unique-constraint-indices
96 listOf(
97 "create index index6_12 on 'table6' ('c1', 'c2uuu')",
98 "create index index6_23 on 'table6' ('c2uuu', 'c3')"
99 ).forEach { query ->
100 db.execSQL(query, emptyArray())
101 }
102
103 // sanity check: verifies if the above index adding operations succeeded
104 val indexCountTable6 =
105 db.rawQuery("select count(*) from pragma_index_list('table6')", null).let {
106 it.moveToNext()
107 val count = it.getString(0)
108 it.close()
109 count
110 }
111
112 assertThat(indexCountTable6).isEqualTo("4")
113 }
Jakub Gielzak45513aa2020-01-03 14:16:12 +0000114 )
115 }
116
117 @Test
118 fun test_get_schema_multiple_databases() {
119 test_get_schema(
120 listOf(
121 Database("db3", Table("t3", Column("c3", "BLOB"))),
122 Database("db2", Table("t2", Column("c2", "TEXT"))),
123 Database("db1", Table("t1", Column("c1", "TEXT")))
124 )
125 )
126 }
127
Jakub Gielzakb467c122020-03-03 16:17:48 +0000128 @Test
129 fun test_get_schema_wrong_database_id() = runBlocking {
130 val databaseId = 123456789
131 testEnvironment.sendCommand(createGetSchemaCommand(databaseId)).let { response ->
132 assertThat(response.hasErrorOccurred()).isEqualTo(true)
133 val error = response.errorOccurred.content
134 assertThat(error.message).isEqualTo("No database with id=$databaseId")
Jakub Gielzak99b74152020-03-27 16:30:35 +0000135 assertThat(error.recoverability.isRecoverable).isEqualTo(true)
Jakub Gielzakb467c122020-03-03 16:17:48 +0000136 }
137 }
138
Jakub Gielzak21ca3982020-02-07 15:10:33 +0000139 private fun test_get_schema(
140 alreadyOpenDatabases: List<Database>,
141 onDatabaseCreated: (SQLiteDatabase) -> Unit = {}
142 ) =
143 runBlocking {
Jakub Gielzak45513aa2020-01-03 14:16:12 +0000144 assertThat(alreadyOpenDatabases).isNotEmpty() // sanity check
145
146 testEnvironment.registerAlreadyOpenDatabases(alreadyOpenDatabases.map {
Jakub Gielzak21ca3982020-02-07 15:10:33 +0000147 it.createInstance(temporaryFolder).also { db -> onDatabaseCreated(db) }
Jakub Gielzak45513aa2020-01-03 14:16:12 +0000148 })
149 testEnvironment.sendCommand(createTrackDatabasesCommand())
150 val databaseConnections =
151 alreadyOpenDatabases.indices.map { testEnvironment.receiveEvent().databaseOpened }
152
153 val schemas =
154 databaseConnections
155 .sortedBy { it.name }
156 .map {
157 testEnvironment.sendCommand(createGetSchemaCommand(it.databaseId)).getSchema
158 }
159
160 alreadyOpenDatabases
161 .sortedBy { it.name }
162 .zipSameSize(schemas)
163 .forEach { (expectedSchema, actualSchema) ->
164 val expectedTables = expectedSchema.tables.sortedBy { it.name }
165 val actualTables = actualSchema.tablesList.sortedBy { it.name }
166
167 expectedTables
168 .zipSameSize(actualTables)
169 .forEach { (expectedTable, actualTable) ->
170 assertThat(actualTable.name).isEqualTo(expectedTable.name)
171
172 val expectedColumns = expectedTable.columns.sortedBy { it.name }
173 val actualColumns = actualTable.columnsList.sortedBy { it.name }
174
175 expectedColumns
Jakub Gielzak21ca3982020-02-07 15:10:33 +0000176 .adjustForSinglePrimaryKey()
Jakub Gielzak45513aa2020-01-03 14:16:12 +0000177 .zipSameSize(actualColumns)
Jakub Gielzak21ca3982020-02-07 15:10:33 +0000178 .forEach { (expectedColumn, actualColumnProto) ->
179 val actualColumn = Column(
180 name = actualColumnProto.name,
181 type = actualColumnProto.type,
182 primaryKey = actualColumnProto.primaryKey,
183 isNotNull = actualColumnProto.isNotNull,
184 isUnique = actualColumnProto.isUnique
185 )
186 assertThat(actualColumn).isEqualTo(expectedColumn)
Jakub Gielzak45513aa2020-01-03 14:16:12 +0000187 }
188 }
189 }
190 }
191
Jakub Gielzak21ca3982020-02-07 15:10:33 +0000192 // The sole primary key in a table is by definition unique
193 private fun List<Column>.adjustForSinglePrimaryKey(): List<Column> =
194 if (this.count { it.isPrimaryKey } > 1) this
195 else this.map {
196 if (it.isPrimaryKey) it.copy(isUnique = true)
197 else it
198 }
199
Jakub Gielzak45513aa2020-01-03 14:16:12 +0000200 /** Same as [List.zip] but ensures both lists are the same size. */
201 private fun <A, B> List<A>.zipSameSize(other: List<B>): List<Pair<A, B>> {
202 assertThat(this.size).isEqualTo(other.size)
203 return this.zip(other)
204 }
205}