1 /* <lambda>null2 * 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 17 package androidx.sqlite.inspection.test 18 19 import android.app.Application 20 import android.database.sqlite.SQLiteClosable 21 import android.database.sqlite.SQLiteDatabase 22 import android.os.Build 23 import androidx.inspection.ArtTooling.ExitHook 24 import androidx.sqlite.inspection.SqliteInspectorProtocol.Event 25 import androidx.sqlite.inspection.SqliteInspectorProtocol.Response 26 import androidx.sqlite.inspection.test.MessageFactory.createKeepDatabasesOpenCommand 27 import androidx.sqlite.inspection.test.MessageFactory.createKeepDatabasesOpenResponse 28 import androidx.sqlite.inspection.test.MessageFactory.createTrackDatabasesCommand 29 import androidx.sqlite.inspection.test.MessageFactory.createTrackDatabasesResponse 30 import androidx.test.ext.junit.runners.AndroidJUnit4 31 import androidx.test.filters.LargeTest 32 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation 33 import com.google.common.truth.Truth.assertThat 34 import java.io.File 35 import kotlinx.coroutines.runBlocking 36 import org.junit.Rule 37 import org.junit.Test 38 import org.junit.rules.TemporaryFolder 39 import org.junit.runner.RunWith 40 41 private const val OPEN_DATABASE_COMMAND_SIGNATURE_API11: String = 42 "openDatabase" + 43 "(" + 44 "Ljava/lang/String;" + 45 "Landroid/database/sqlite/SQLiteDatabase\$CursorFactory;" + 46 "I" + 47 "Landroid/database/DatabaseErrorHandler;" + 48 ")" + 49 "Landroid/database/sqlite/SQLiteDatabase;" 50 51 private const val OPEN_DATABASE_COMMAND_SIGNATURE_API27: String = 52 "openDatabase" + 53 "(" + 54 "Ljava/io/File;" + 55 "Landroid/database/sqlite/SQLiteDatabase\$OpenParams;" + 56 ")" + 57 "Landroid/database/sqlite/SQLiteDatabase;" 58 59 private const val CREATE_IN_MEMORY_DATABASE_COMMAND_SIGNATURE_API27 = 60 "createInMemory" + 61 "(" + 62 "Landroid/database/sqlite/SQLiteDatabase\$OpenParams;" + 63 ")" + 64 "Landroid/database/sqlite/SQLiteDatabase;" 65 66 private const val ALL_REFERENCES_RELEASED_COMMAND_SIGNATURE = "onAllReferencesReleased()V" 67 68 private const val RELEASE_REFERENCE_COMMAND_SIGNATURE = "releaseReference()V" 69 70 @LargeTest 71 @RunWith(AndroidJUnit4::class) 72 class TrackDatabasesTest { 73 @get:Rule val testEnvironment = SqliteInspectorTestEnvironment() 74 75 @get:Rule val temporaryFolder = TemporaryFolder(getInstrumentation().context.cacheDir) 76 77 @Test 78 fun test_track_databases() = runBlocking { 79 val alreadyOpenDatabases = 80 listOf( 81 Database("db1").createInstance(temporaryFolder), 82 Database("db2").createInstance(temporaryFolder) 83 ) 84 85 testEnvironment.registerAlreadyOpenDatabases(alreadyOpenDatabases) 86 87 testEnvironment.sendCommand(createTrackDatabasesCommand()).let { response -> 88 assertThat(response).isEqualTo(createTrackDatabasesResponse()) 89 } 90 91 // evaluate 'already-open' instances are found 92 alreadyOpenDatabases.let { expected -> 93 val actual = expected.indices.map { testEnvironment.receiveEvent().databaseOpened } 94 testEnvironment.assertNoQueuedEvents() 95 assertThat(actual.map { it.databaseId }.distinct()).hasSize(expected.size) 96 expected.forEachIndexed { ix, _ -> 97 assertThat(actual[ix].path).isEqualTo(expected[ix].displayName) 98 } 99 } 100 101 // evaluate registered hooks 102 val possibleSignatures = 103 listOf( 104 OPEN_DATABASE_COMMAND_SIGNATURE_API11, 105 OPEN_DATABASE_COMMAND_SIGNATURE_API27, 106 CREATE_IN_MEMORY_DATABASE_COMMAND_SIGNATURE_API27 107 ) 108 val wantedSignatures = 109 when { 110 Build.VERSION.SDK_INT < 27 -> listOf(OPEN_DATABASE_COMMAND_SIGNATURE_API11) 111 else -> 112 listOf( 113 OPEN_DATABASE_COMMAND_SIGNATURE_API11, 114 OPEN_DATABASE_COMMAND_SIGNATURE_API27, 115 CREATE_IN_MEMORY_DATABASE_COMMAND_SIGNATURE_API27 116 ) 117 } 118 119 val hookEntries = 120 testEnvironment.consumeRegisteredHooks().filter { 121 possibleSignatures.contains(it.originMethod) 122 } 123 assertThat(hookEntries).hasSize(wantedSignatures.size) 124 assertThat(hookEntries.map { it.originMethod }.containsAll(wantedSignatures)).isTrue() 125 hookEntries.forEachIndexed { ix, entry -> 126 // expect one exit hook tracking database open events 127 assertThat(entry).isInstanceOf(Hook.ExitHook::class.java) 128 assertThat(entry.originClass.name).isEqualTo(SQLiteDatabase::class.java.name) 129 130 // verify that executing the registered hook will result in tracking events 131 testEnvironment.assertNoQueuedEvents() 132 @Suppress("UNCHECKED_CAST") val exitHook = entry.asExitHook as ExitHook<SQLiteDatabase> 133 val database = Database("db3_$ix").createInstance(temporaryFolder) 134 assertThat(exitHook.onExit(database)).isSameInstanceAs(database) 135 testEnvironment.receiveEvent().let { event -> 136 assertThat(event.databaseOpened.path).isEqualTo(database.displayName) 137 } 138 } 139 140 assertThat(testEnvironment.consumeRegisteredHooks()).isEmpty() 141 } 142 143 @Test 144 fun test_track_databases_the_same_database_opened_multiple_times() = runBlocking { 145 // given 146 testEnvironment.sendCommand(createTrackDatabasesCommand()) 147 val onOpenHook = 148 testEnvironment.consumeRegisteredHooks().first { 149 it.originMethod == OPEN_DATABASE_COMMAND_SIGNATURE_API11 150 } 151 @Suppress("UNCHECKED_CAST") 152 val onOpen = (onOpenHook.asExitHook as ExitHook<SQLiteDatabase>)::onExit 153 154 val seenDbIds = mutableSetOf<Int>() 155 156 fun checkDbOpenedEvent(event: Event, database: SQLiteDatabase) { 157 assertThat(event.hasDatabaseOpened()).isEqualTo(true) 158 val isNewId = seenDbIds.add(event.databaseOpened.databaseId) 159 assertThat(isNewId).isEqualTo(true) 160 assertThat(event.databaseOpened.path).isEqualTo(database.displayName) 161 } 162 163 // file based db: first open 164 val fileDbPath = "db1" 165 val fileDb = Database(fileDbPath).createInstance(temporaryFolder) 166 onOpen(fileDb) 167 checkDbOpenedEvent(testEnvironment.receiveEvent(), fileDb) 168 169 // file based db: same instance 170 onOpen(fileDb) 171 testEnvironment.assertNoQueuedEvents() 172 173 // file based db: same path 174 onOpen(Database(fileDbPath).createInstance(temporaryFolder)) 175 testEnvironment.assertNoQueuedEvents() 176 177 // in-memory database: first open 178 val inMemDb = Database(null).createInstance(temporaryFolder) 179 onOpen(inMemDb) 180 checkDbOpenedEvent(testEnvironment.receiveEvent(), inMemDb) 181 182 // in-memory database: same instance 183 onOpen(inMemDb) 184 testEnvironment.assertNoQueuedEvents() 185 186 // in-memory database: new instances (same path = :memory:) 187 repeat(3) { 188 val db = Database(null).createInstance(temporaryFolder) 189 assertThat(db.path).isEqualTo(":memory:") 190 onOpen(db) 191 checkDbOpenedEvent(testEnvironment.receiveEvent(), db) 192 } 193 } 194 195 @Test 196 fun test_track_databases_keep_db_open_toggle() = runBlocking { 197 // given 198 val hooks = startTracking() 199 200 // without inspecting 201 Database("db1").createInstance(temporaryFolder).let { db -> 202 db.close() 203 assertClosed(db) 204 } 205 206 // with inspecting (initially keepOpen = false) 207 assertNoQueuedEvents() 208 listOf("db2", null).forEach { path -> 209 openDatabase(path, hooks).let { db -> 210 val id = receiveOpenedEventId(db) 211 closeDatabase(db, hooks) 212 receiveClosedEvent(id, db.displayName) 213 assertClosed(db) 214 } 215 } 216 assertNoQueuedEvents() 217 218 // toggle keepOpen = true 219 issueKeepDatabasesOpenCommand(true) 220 assertNoQueuedEvents() 221 222 // with inspecting (now keepOpen = true) 223 val dbs = 224 listOf("db3", null).map { path -> 225 val db = openDatabase(path, hooks) 226 val id = receiveOpenedEventId(db) 227 id to db 228 } 229 dbs.forEach { (_, db) -> 230 closeDatabase(db, hooks) 231 assertOpen(db) // keep-open has worked 232 } 233 assertNoQueuedEvents() 234 235 // toggle keepOpen = false 236 issueKeepDatabasesOpenCommand(false) 237 assertNoQueuedEvents() 238 dbs.forEach { (id, db) -> 239 assertClosed(db) 240 hooks.triggerOnAllReferencesReleased(db) 241 receiveClosedEvent(id, db.displayName) 242 } 243 assertNoQueuedEvents() 244 245 // keepOpen = true with some of the same databases as before (they are not revived) 246 issueKeepDatabasesOpenCommand(true) 247 dbs.forEach { (_, db) -> assertClosed(db) } 248 assertNoQueuedEvents() 249 250 // keepOpen = false with a database with more than one reference 251 issueKeepDatabasesOpenCommand(false) 252 openDatabase("db4", hooks).let { db -> 253 db.acquireReference() // extra reference 254 255 closeDatabase(db, hooks) 256 assertOpen(db) 257 258 closeDatabase(db, hooks) 259 assertClosed(db) 260 } 261 } 262 263 @Test 264 fun test_on_closed_notification() = runBlocking { 265 // given 266 val hooks = startTracking() 267 268 // simple flow 269 assertNoQueuedEvents() 270 openDatabase("db1", hooks).let { db -> 271 val id = receiveOpenedEventId(db) 272 closeDatabase(db, hooks) 273 receiveClosedEvent(id, db.displayName) 274 assertClosed(db) 275 assertNoQueuedEvents() 276 } 277 278 // test that doesn't fire on each db.closed() 279 assertNoQueuedEvents() 280 openDatabase("db2", hooks).let { db -> 281 val id = receiveOpenedEventId(db) 282 283 db.acquireReference() // extra reference 284 285 // pass 1 286 closeDatabase(db, hooks) 287 assertOpen(db) 288 assertNoQueuedEvents() 289 290 // pass 2 291 closeDatabase(db, hooks) 292 assertClosed(db) 293 receiveClosedEvent(id, db.displayName) 294 assertNoQueuedEvents() 295 } 296 } 297 298 @Test 299 fun test_findInstances_closed() = runBlocking { 300 val db1a = Database("db1").createInstance(temporaryFolder) 301 val db2 = Database("db2").createInstance(temporaryFolder) 302 assertOpen(db1a) 303 assertOpen(db2) 304 db1a.close() 305 assertClosed(db1a) 306 307 // given 308 testEnvironment.registerAlreadyOpenDatabases(listOf(db1a, db2)) 309 val hooks = startTracking() 310 val id1 = receiveClosedEventId(db1a) 311 val id2 = receiveOpenedEventId(db2) 312 assertNoQueuedEvents() 313 314 val db1b = openDatabase("db1", hooks) 315 assertThat(receiveOpenedEventId(db1a)).isEqualTo(id1) 316 assertNoQueuedEvents() 317 318 closeDatabase(db1b, hooks) 319 receiveClosedEvent(id1, db1a.displayName) 320 321 closeDatabase(db2, hooks) 322 receiveClosedEvent(id2, db2.displayName) 323 } 324 325 @Test 326 fun test_findInstances_disk() = runBlocking { 327 val db1a = Database("db1").createInstance(temporaryFolder) 328 val db2 = Database("db2").createInstance(temporaryFolder) 329 val application = 330 object : Application() { 331 override fun databaseList(): Array<String> = 332 arrayOf(db1a.absolutePath, db2.absolutePath) 333 334 override fun getDatabasePath(name: String?) = 335 getInstrumentation().context.getDatabasePath(name) 336 } 337 338 testEnvironment.registerApplication(application) 339 val hooks = startTracking() 340 341 val id1 = receiveClosedEventId(db1a.absolutePath) 342 val id2 = receiveClosedEventId(db2.absolutePath) 343 assertNoQueuedEvents() 344 345 val db1b = openDatabase("db1", hooks) 346 receiveOpenedEvent(id1, db1a.absolutePath) 347 assertNoQueuedEvents() 348 349 openDatabase("db2", hooks) 350 receiveOpenedEvent(id2, db2.absolutePath) 351 assertNoQueuedEvents() 352 353 closeDatabase(db1b, hooks) 354 receiveClosedEvent(id1, db1a.absolutePath) 355 assertNoQueuedEvents() 356 } 357 358 @Test 359 fun test_findInstances_disk_filters_helper_files() = runBlocking { 360 val db = Database("db1").createInstance(temporaryFolder, false) 361 362 val application = 363 object : Application() { 364 override fun databaseList(): Array<String> = 365 temporaryFolder.root.list() as Array<String> 366 367 override fun getDatabasePath(name: String) = File(temporaryFolder.root, name) 368 } 369 370 // trigger some query to establish connection 371 val cursor = db.rawQuery("select * from sqlite_master", emptyArray()) 372 cursor.count 373 cursor.close() 374 375 testEnvironment.registerApplication(application) 376 testEnvironment.registerAlreadyOpenDatabases(listOf(db)) 377 val hooks = startTracking() 378 379 val id = receiveOpenedEventId(db) 380 assertNoQueuedEvents() 381 382 closeDatabase(db, hooks) 383 receiveClosedEvent(id, db.absolutePath) 384 assertNoQueuedEvents() 385 } 386 387 @Test 388 fun test_on_closed_and_reopened() = runBlocking { 389 // given 390 val hooks = startTracking() 391 392 // simple flow 393 val databaseName = "db1" 394 395 assertNoQueuedEvents() 396 var id: Int 397 openDatabase(databaseName, hooks).let { db -> 398 id = receiveOpenedEventId(db) 399 closeDatabase(db, hooks) 400 receiveClosedEvent(id, db.displayName) 401 assertClosed(db) 402 } 403 testEnvironment.assertNoQueuedEvents() 404 405 openDatabase(databaseName, hooks).let { db -> 406 assertThat(receiveOpenedEventId(db)).isEqualTo(id) 407 closeDatabase(db, hooks) 408 receiveClosedEvent(id, db.displayName) 409 assertClosed(db) 410 } 411 testEnvironment.assertNoQueuedEvents() 412 } 413 414 @Test 415 fun test_temporary_databases_same_path_different_database() { 416 // given 417 val db1 = Database(null).createInstance(temporaryFolder) 418 val db2 = Database(null).createInstance(temporaryFolder) 419 fun queryTableCount(db: SQLiteDatabase): Long = 420 db.compileStatement("select count(*) from sqlite_master").simpleQueryForLong() 421 assertThat(queryTableCount(db1)).isEqualTo(1) // android_metadata sole table 422 assertThat(queryTableCount(db2)).isEqualTo(1) // android_metadata sole table 423 assertThat(db1.path).isEqualTo(db2.path) 424 assertThat(db1.path).isEqualTo(":memory:") 425 426 // when 427 db1.execSQL("create table t1 (c1 int)") 428 429 // then 430 assertThat(queryTableCount(db1)).isEqualTo(2) 431 assertThat(queryTableCount(db2)).isEqualTo(1) 432 } 433 434 @Test 435 fun test_three_references_edge_ones_closed() = runBlocking { 436 val hooks = startTracking() 437 438 val db1a = openDatabase("path1", hooks) 439 val id1a = receiveOpenedEventId(db1a) 440 441 val db1b = openDatabase("path1", hooks) 442 assertNoQueuedEvents() 443 444 val db1c = openDatabase("path1", hooks) 445 assertNoQueuedEvents() 446 447 closeDatabase(db1a, hooks) 448 assertNoQueuedEvents() 449 450 closeDatabase(db1c, hooks) 451 assertNoQueuedEvents() 452 453 closeDatabase(db1b, hooks) 454 receiveClosedEvent(id1a, db1a.displayName) 455 } 456 457 @Test 458 fun test_keep_open_while_user_attempts_to_close() = runBlocking { 459 val hooks = startTracking() 460 assertNoQueuedEvents() 461 462 val db = openDatabase("db", hooks) 463 val id = receiveOpenedEventId(db) 464 assertNoQueuedEvents() 465 466 issueKeepDatabasesOpenCommand(true) 467 assertNoQueuedEvents() 468 469 var count = 0 470 closeDatabase(db, hooks) 471 count++ 472 closeDatabase(db, hooks) 473 count++ 474 closeDatabase(db, hooks) 475 count++ 476 assertNoQueuedEvents() 477 478 issueKeepDatabasesOpenCommand(false) 479 repeat(count) { 480 hooks.triggerReleaseReference(db) 481 if (!db.isOpen) { 482 hooks.triggerOnAllReferencesReleased(db) 483 } 484 } 485 486 receiveClosedEvent(id, db.displayName) 487 assertClosed(db) 488 assertNoQueuedEvents() 489 } 490 491 /** 492 * #dbRef -- the number of references as seen by the SQLiteDatabase object #kpoRef=0 -- the 493 * number of references acquired by KeepOpen objects #usrRef=1 -- the 'balance' of references 494 * the user owns 495 */ 496 @Test 497 fun test_keep_open_keeps_count() = runBlocking { 498 val hooks = startTracking() 499 assertNoQueuedEvents() 500 501 val db = openDatabase("db", hooks) // #dbRef=1 | #kpoRef=0 | #usrRef=1 502 receiveOpenedEventId(db) 503 assertThat(db.referenceCount).isEqualTo(1) 504 assertNoQueuedEvents() 505 506 issueKeepDatabasesOpenCommand(true) // #dbRef=1 | #kpoRef=0 | #usrRef=1 507 assertThat(db.referenceCount).isEqualTo(1) 508 assertNoQueuedEvents() 509 510 var count = 0 511 closeDatabase(db, hooks) 512 count++ // #dbRef=1 | #kpoRef=1 | #usrRef=0 513 assertThat(db.referenceCount).isEqualTo(1) 514 closeDatabase(db, hooks) 515 count++ // #dbRef=1 | #kpoRef=2 | #usrRef=-1 516 assertThat(db.referenceCount).isEqualTo(1) 517 closeDatabase(db, hooks) 518 count++ // #dbRef=1 | #kpoRef=3 | #usrRef=-2 519 assertThat(db.referenceCount).isEqualTo(1) 520 assertNoQueuedEvents() 521 522 repeat(count) { 523 db.acquireReference() // user offsetting the closed calls they made 524 } // #dbRef=4 | #kpoRef=3 | #usrRef=1 525 assertThat(db.referenceCount).isEqualTo(4) 526 527 issueKeepDatabasesOpenCommand(false) // #dbRef=1 | #kpoRef=0 | #usrRef=1 528 repeat(count + 1) { 529 hooks.triggerReleaseReference(db) 530 assertOpen(db) 531 } // #dbRef=1 | #kpoRef=0 | #usrRef=1 532 assertThat(db.referenceCount).isEqualTo(1) 533 534 assertOpen(db) 535 assertNoQueuedEvents() 536 } 537 538 /** 539 * #dbRef -- the number of references as seen by the SQLiteDatabase object #kpoRef=0 -- the 540 * number of references acquired by KeepOpen objects #usrRef=1 -- the 'balance' of references 541 * the user owns 542 */ 543 @Test 544 fun test_keep_open_off_on_off() = runBlocking { 545 val hooks = startTracking() 546 assertNoQueuedEvents() 547 548 // keep-open = false (default) 549 550 val db1 = openDatabase("db1", hooks) 551 val db2 = openDatabase("db2", hooks) 552 553 assertThat(db1.referenceCount).isEqualTo(1) // #dbRef=1 | #kpoRef=0 | #usrRef=1 554 val id1 = receiveOpenedEventId(db1) 555 val id2 = receiveOpenedEventId(db2) 556 assertThat(db1.referenceCount).isEqualTo(1) 557 assertThat(db2.referenceCount).isEqualTo(1) 558 assertNoQueuedEvents() 559 560 closeDatabase(db1, hooks) // #dbRef=0 | #kpoRef=0 | #usrRef=0 561 assertThat(db1.referenceCount).isEqualTo(0) 562 assertThat(db2.referenceCount).isEqualTo(1) 563 receiveClosedEvent(id1, db1.displayName) 564 assertNoQueuedEvents() 565 566 // keep-open = true 567 568 issueKeepDatabasesOpenCommand(true) 569 assertNoQueuedEvents() 570 assertThat(db2.referenceCount).isEqualTo(1) // #dbRef=1 | #kpoRef=0 | #usrRef=1 571 assertNoQueuedEvents() 572 573 closeDatabase(db2, hooks) // #dbRef=1 | #kpoRef=1 | #usrRef=0 574 assertThat(db2.referenceCount).isEqualTo(1) 575 assertNoQueuedEvents() 576 577 db2.acquireReference() // #dbRef=2 | #kpoRef=1 | #usrRef=1 578 assertThat(db2.referenceCount).isEqualTo(2) 579 580 // keep-open = false 581 582 issueKeepDatabasesOpenCommand(false) 583 hooks.triggerReleaseReference(db2) // #dbRef=1 | #kpoRef=0 | #usrRef=1 584 assertThat(db2.referenceCount).isEqualTo(1) 585 assertNoQueuedEvents() 586 587 closeDatabase(db2, hooks) // #dbRef=0 | #kpoRef=0 | #usrRef=0 588 assertThat(db2.referenceCount).isEqualTo(0) 589 receiveClosedEvent(id2, db2.displayName) 590 assertNoQueuedEvents() 591 } 592 593 private val SQLiteClosable.referenceCount: Int 594 get() = getFieldValue(SQLiteClosable::class.java, "mReferenceCount", this) 595 596 @Suppress("SameParameterValue") 597 private fun <T> getFieldValue(clazz: Class<*>, fieldName: String, target: Any?): T { 598 val field = clazz.declaredFields.first { it.name == fieldName } 599 field.isAccessible = true 600 val result = field.get(target) 601 @Suppress("UNCHECKED_CAST") return result as T 602 } 603 604 private fun assertNoQueuedEvents() { 605 testEnvironment.assertNoQueuedEvents() 606 } 607 608 private suspend fun startTracking(): List<Hook> { 609 testEnvironment.sendCommand(createTrackDatabasesCommand()) 610 return testEnvironment.consumeRegisteredHooks() 611 } 612 613 private fun openDatabase(path: String?, hooks: List<Hook>): SQLiteDatabase = 614 Database(path).createInstance(temporaryFolder).also { hooks.triggerOnOpened(it) } 615 616 private fun closeDatabase(database: SQLiteDatabase, hooks: List<Hook>) { 617 hooks.triggerReleaseReference(database) 618 database.close() 619 if (!database.isOpen) { 620 hooks.triggerOnAllReferencesReleased(database) 621 } 622 } 623 624 private suspend fun issueKeepDatabasesOpenCommand(setEnabled: Boolean) { 625 testEnvironment.sendCommand(createKeepDatabasesOpenCommand(setEnabled)).let { response -> 626 assertThat(response.oneOfCase).isEqualTo(Response.OneOfCase.KEEP_DATABASES_OPEN) 627 assertThat(response).isEqualTo(createKeepDatabasesOpenResponse()) 628 } 629 } 630 631 private suspend fun receiveOpenedEventId(database: SQLiteDatabase): Int = 632 receiveOpenedEventId(database.displayName) 633 634 private suspend fun receiveOpenedEventId(displayName: String): Int = 635 testEnvironment.receiveEvent().let { 636 assertThat(it.oneOfCase).isEqualTo(Event.OneOfCase.DATABASE_OPENED) 637 assertThat(it.databaseOpened.path).isEqualTo(displayName) 638 it.databaseOpened.databaseId 639 } 640 641 private suspend fun receiveClosedEventId(displayName: String): Int = 642 testEnvironment.receiveEvent().let { 643 assertThat(it.oneOfCase).isEqualTo(Event.OneOfCase.DATABASE_CLOSED) 644 assertThat(it.databaseClosed.path).isEqualTo(displayName) 645 it.databaseClosed.databaseId 646 } 647 648 private suspend fun receiveClosedEventId(database: SQLiteDatabase): Int = 649 receiveClosedEventId(database.displayName) 650 651 private suspend fun receiveOpenedEvent(id: Int, path: String) = 652 testEnvironment.receiveEvent().let { 653 assertThat(it.oneOfCase).isEqualTo(Event.OneOfCase.DATABASE_OPENED) 654 assertThat(it.databaseOpened.databaseId).isEqualTo(id) 655 assertThat(it.databaseOpened.path).isEqualTo(path) 656 } 657 658 private suspend fun receiveClosedEvent(id: Int, path: String) = 659 testEnvironment.receiveEvent().let { 660 assertThat(it.oneOfCase).isEqualTo(Event.OneOfCase.DATABASE_CLOSED) 661 assertThat(it.databaseClosed.databaseId).isEqualTo(id) 662 assertThat(it.databaseClosed.path).isEqualTo(path) 663 } 664 665 @Suppress("UNCHECKED_CAST") 666 private fun List<Hook>.triggerOnOpened(db: SQLiteDatabase) { 667 val onOpen = filter { it.originMethod == OPEN_DATABASE_COMMAND_SIGNATURE_API11 } 668 assertThat(onOpen).hasSize(1) 669 (onOpen.first().asExitHook as ExitHook<SQLiteDatabase>).onExit(db) 670 } 671 672 private fun List<Hook>.triggerReleaseReference(db: SQLiteDatabase) { 673 val onOpen = filter { it.originMethod == RELEASE_REFERENCE_COMMAND_SIGNATURE } 674 assertThat(onOpen).hasSize(1) 675 onOpen.first().asEntryHook.onEntry(db, emptyList()) 676 } 677 678 private fun List<Hook>.triggerOnAllReferencesReleased(db: SQLiteDatabase) { 679 val onReleasedHooks = 680 this.filter { it.originMethod == ALL_REFERENCES_RELEASED_COMMAND_SIGNATURE } 681 assertThat(onReleasedHooks).hasSize(2) 682 val onReleasedEntry = (onReleasedHooks.first { it is Hook.EntryHook }.asEntryHook)::onEntry 683 val onReleasedExit = (onReleasedHooks.first { it is Hook.ExitHook }.asExitHook)::onExit 684 onReleasedEntry(db, emptyList()) 685 onReleasedExit(null) 686 } 687 688 private fun assertOpen(db: SQLiteDatabase) { 689 assertThat(db.isOpen).isTrue() 690 } 691 692 private fun assertClosed(db: SQLiteDatabase) { 693 assertThat(db.isOpen).isFalse() 694 } 695 } 696