Skip to content

Commit 2761784

Browse files
committed
Merge branch 'main' into fix-download-race-condition
2 parents 924c45d + fc06c3b commit 2761784

File tree

11 files changed

+358
-355
lines changed

11 files changed

+358
-355
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
## 1.0.0-BETA29 (unreleased)
44

55
* Fix potential race condition between jobs in `connect()` and `disconnect()`.
6+
* [JVM Windows] Fixed PowerSync Extension temporary file deletion error on process shutdown.
7+
* [iOS] Fixed issue where automatic driver migrations would fail with the error:
8+
```
9+
Sqlite operation failure database is locked attempted to run migration and failed. closing connection
10+
```
611
* Fix race condition causing data received during uploads not to be applied.
712

813
## 1.0.0-BETA28

core/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ kotlin {
261261
implementation(libs.kotlin.test)
262262
implementation(libs.test.coroutines)
263263
implementation(libs.test.turbine)
264+
implementation(libs.test.kotest.assertions)
264265
implementation(libs.kermit.test)
265266
implementation(libs.ktor.client.mock)
266267
implementation(libs.test.turbine)

core/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt

+50-112
Original file line numberDiff line numberDiff line change
@@ -2,110 +2,59 @@ package com.powersync
22

33
import app.cash.turbine.turbineScope
44
import co.touchlab.kermit.ExperimentalKermitApi
5-
import co.touchlab.kermit.Logger
6-
import co.touchlab.kermit.Severity
7-
import co.touchlab.kermit.TestConfig
8-
import co.touchlab.kermit.TestLogWriter
95
import com.powersync.db.ActiveDatabaseGroup
10-
import com.powersync.db.getString
116
import com.powersync.db.schema.Schema
127
import com.powersync.testutils.UserRow
13-
import com.powersync.testutils.generatePrintLogWriter
8+
import com.powersync.testutils.databaseTest
149
import com.powersync.testutils.getTempDir
1510
import com.powersync.testutils.waitFor
11+
import io.kotest.assertions.throwables.shouldThrow
12+
import io.kotest.matchers.collections.shouldHaveSize
13+
import io.kotest.matchers.shouldBe
14+
import io.kotest.matchers.string.shouldContain
1615
import kotlinx.coroutines.CompletableDeferred
1716
import kotlinx.coroutines.Dispatchers
1817
import kotlinx.coroutines.async
1918
import kotlinx.coroutines.delay
2019
import kotlinx.coroutines.runBlocking
21-
import kotlinx.coroutines.test.runTest
2220
import kotlinx.coroutines.withContext
23-
import kotlin.test.AfterTest
24-
import kotlin.test.BeforeTest
2521
import kotlin.test.Test
2622
import kotlin.test.assertEquals
27-
import kotlin.test.assertFailsWith
2823
import kotlin.test.assertNotNull
29-
import kotlin.test.assertTrue
3024

3125
@OptIn(ExperimentalKermitApi::class)
3226
class DatabaseTest {
33-
private val logWriter =
34-
TestLogWriter(
35-
loggable = Severity.Debug,
36-
)
37-
38-
private val logger =
39-
Logger(
40-
TestConfig(
41-
minSeverity = Severity.Debug,
42-
logWriterList = listOf(logWriter, generatePrintLogWriter()),
43-
),
44-
)
45-
46-
private lateinit var database: PowerSyncDatabase
47-
48-
private fun openDB() =
49-
PowerSyncDatabase(
50-
factory = com.powersync.testutils.factory,
51-
schema = Schema(UserRow.table),
52-
dbFilename = "testdb",
53-
logger = logger,
54-
)
55-
56-
@BeforeTest
57-
fun setupDatabase() {
58-
logWriter.reset()
59-
60-
database = openDB()
61-
62-
runBlocking {
63-
database.disconnectAndClear(true)
64-
}
65-
}
66-
67-
@AfterTest
68-
fun tearDown() {
69-
runBlocking {
70-
if (!database.closed) {
71-
database.disconnectAndClear(true)
72-
database.close()
73-
}
74-
}
75-
com.powersync.testutils.cleanup("testdb")
76-
}
77-
7827
@Test
7928
fun testLinksPowerSync() =
80-
runTest {
29+
databaseTest {
8130
database.get("SELECT powersync_rs_version();") { it.getString(0)!! }
8231
}
8332

8433
@Test
8534
fun testWAL() =
86-
runTest {
35+
databaseTest {
8736
val mode =
8837
database.get(
8938
"PRAGMA journal_mode",
9039
mapper = { it.getString(0)!! },
9140
)
92-
assertEquals(mode, "wal")
41+
mode shouldBe "wal"
9342
}
9443

9544
@Test
9645
fun testFTS() =
97-
runTest {
46+
databaseTest {
9847
val mode =
9948
database.get(
10049
"SELECT sqlite_compileoption_used('ENABLE_FTS5');",
10150
mapper = { it.getLong(0)!! },
10251
)
103-
assertEquals(mode, 1)
52+
mode shouldBe 1
10453
}
10554

10655
@Test
10756
fun testConcurrentReads() =
108-
runTest {
57+
databaseTest {
10958
database.execute(
11059
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
11160
listOf(
@@ -118,7 +67,7 @@ class DatabaseTest {
11867
val transactionItemCreated = CompletableDeferred<Unit>()
11968
// Start a long running writeTransaction
12069
val transactionJob =
121-
async {
70+
scope.async {
12271
database.writeTransaction { tx ->
12372
// Create another user
12473
// External readers should not see this user while the transaction is open
@@ -156,7 +105,7 @@ class DatabaseTest {
156105

157106
@Test
158107
fun testTransactionReads() =
159-
runTest {
108+
databaseTest {
160109
database.execute(
161110
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
162111
listOf(
@@ -187,18 +136,18 @@ class DatabaseTest {
187136

188137
@Test
189138
fun testTableUpdates() =
190-
runTest {
139+
databaseTest {
191140
turbineScope {
192141
val query = database.watch("SELECT * FROM users") { UserRow.from(it) }.testIn(this)
193142

194143
// Wait for initial query
195-
assertEquals(0, query.awaitItem().size)
144+
query.awaitItem() shouldHaveSize 0
196145

197146
database.execute(
198147
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
199148
listOf("Test", "[email protected]"),
200149
)
201-
assertEquals(1, query.awaitItem().size)
150+
query.awaitItem() shouldHaveSize 1
202151

203152
database.writeTransaction {
204153
it.execute(
@@ -211,7 +160,7 @@ class DatabaseTest {
211160
)
212161
}
213162

214-
assertEquals(3, query.awaitItem().size)
163+
query.awaitItem() shouldHaveSize 3
215164

216165
try {
217166
database.writeTransaction {
@@ -226,7 +175,7 @@ class DatabaseTest {
226175
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
227176
listOf("Test4", "[email protected]"),
228177
)
229-
assertEquals(4, query.awaitItem().size)
178+
query.awaitItem() shouldHaveSize 4
230179

231180
query.expectNoEvents()
232181
query.cancel()
@@ -235,12 +184,12 @@ class DatabaseTest {
235184

236185
@Test
237186
fun testClosingReadPool() =
238-
runTest {
187+
databaseTest {
239188
val pausedLock = CompletableDeferred<Unit>()
240189
val inLock = CompletableDeferred<Unit>()
241190
// Request a lock
242191
val lockJob =
243-
async {
192+
scope.async {
244193
database.readLock {
245194
inLock.complete(Unit)
246195
runBlocking {
@@ -255,60 +204,48 @@ class DatabaseTest {
255204
// Close the database. This should close the read pool
256205
// The pool should wait for jobs to complete before closing
257206
val closeJob =
258-
async {
207+
scope.async {
259208
database.close()
260209
}
261210

262211
// Wait a little for testing
263-
// Spawns in a different context for the delay to actually take affect
264-
async { withContext(Dispatchers.Default) { delay(500) } }.await()
212+
// Spawns in a different context for the delay to actually take effect
213+
scope.async { withContext(Dispatchers.Default) { delay(500) } }.await()
265214

266215
// The database should not close yet
267216
assertEquals(actual = database.closed, expected = false)
268217

269218
// Any new readLocks should throw
270-
val exception = assertFailsWith<PowerSyncException> { database.readLock {} }
271-
assertEquals(
272-
expected = "Cannot process connection pool request",
273-
actual = exception.message,
274-
)
219+
val exception = shouldThrow<PowerSyncException> { database.readLock {} }
220+
exception.message shouldBe "Cannot process connection pool request"
221+
275222
// Release the lock
276223
pausedLock.complete(Unit)
277224
lockJob.await()
278225
closeJob.await()
279226

280-
assertEquals(actual = database.closed, expected = true)
227+
database.closed shouldBe true
281228
}
282229

283230
@Test
284231
fun openDBWithDirectory() =
285-
runTest {
232+
databaseTest {
286233
val tempDir =
287234
getTempDir()
288235
?: // SQLiteR, which is used on iOS, does not support opening dbs from directories
289-
return@runTest
290-
291-
val dbFilename = "testdb"
236+
return@databaseTest
292237

293-
val db =
294-
PowerSyncDatabase(
295-
factory = com.powersync.testutils.factory,
296-
schema = Schema(UserRow.table),
297-
dbFilename = dbFilename,
298-
dbDirectory = getTempDir(),
299-
logger = logger,
300-
)
301-
302-
val path = db.get("SELECT file FROM pragma_database_list;") { it.getString(0)!! }
303-
assertTrue { path.contains(tempDir) }
304-
db.close()
238+
// On platforms that support it, openDatabase() from our test utils should use a temporary
239+
// location.
240+
val path = database.get("SELECT file FROM pragma_database_list;") { it.getString(0)!! }
241+
path shouldContain tempDir
305242
}
306243

307244
@Test
308245
fun warnsMultipleInstances() =
309-
runTest {
246+
databaseTest {
310247
// Opens a second DB with the same database filename
311-
val db2 = openDB()
248+
val db2 = openDatabase()
312249
waitFor {
313250
assertNotNull(
314251
logWriter.logs.find {
@@ -321,9 +258,9 @@ class DatabaseTest {
321258

322259
@Test
323260
fun readConnectionsReadOnly() =
324-
runTest {
261+
databaseTest {
325262
val exception =
326-
assertFailsWith<PowerSyncException> {
263+
shouldThrow<PowerSyncException> {
327264
database.getOptional(
328265
"""
329266
INSERT INTO
@@ -335,23 +272,24 @@ class DatabaseTest {
335272
parameters = listOf("steven", "[email protected]"),
336273
) {}
337274
}
275+
338276
// The exception messages differ slightly between drivers
339-
assertTrue { exception.message!!.contains("write a readonly database") }
277+
exception.message shouldContain "write a readonly database"
340278
}
341279

342280
@Test
343281
fun basicReadTransaction() =
344-
runTest {
282+
databaseTest {
345283
val count =
346284
database.readTransaction { it ->
347285
it.get("SELECT COUNT(*) from users") { it.getLong(0)!! }
348286
}
349-
assertEquals(expected = 0, actual = count)
287+
count shouldBe 0
350288
}
351289

352290
@Test
353291
fun localOnlyCRUD() =
354-
runTest {
292+
databaseTest {
355293
database.updateSchema(
356294
schema =
357295
Schema(
@@ -375,16 +313,16 @@ class DatabaseTest {
375313
)
376314

377315
val count = database.get("SELECT COUNT(*) FROM local_users") { it.getLong(0)!! }
378-
assertEquals(actual = count, expected = 1)
316+
count shouldBe 1
379317

380318
// No CRUD entries should be present for local only tables
381319
val crudItems = database.getAll("SELECT id from ps_crud") { it.getLong(0)!! }
382-
assertEquals(actual = crudItems.size, expected = 0)
320+
crudItems shouldHaveSize 0
383321
}
384322

385323
@Test
386324
fun insertOnlyCRUD() =
387-
runTest {
325+
databaseTest {
388326
database.updateSchema(schema = Schema(UserRow.table.copy(insertOnly = true)))
389327

390328
database.execute(
@@ -397,15 +335,15 @@ class DatabaseTest {
397335
)
398336

399337
val crudItems = database.getAll("SELECT id from ps_crud") { it.getLong(0)!! }
400-
assertEquals(actual = crudItems.size, expected = 1)
338+
crudItems shouldHaveSize 1
401339

402340
val count = database.get("SELECT COUNT(*) from users") { it.getLong(0)!! }
403-
assertEquals(actual = count, expected = 0)
341+
count shouldBe 0
404342
}
405343

406344
@Test
407345
fun viewOverride() =
408-
runTest {
346+
databaseTest {
409347
database.updateSchema(schema = Schema(UserRow.table.copy(viewNameOverride = "people")))
410348

411349
database.execute(
@@ -418,9 +356,9 @@ class DatabaseTest {
418356
)
419357

420358
val crudItems = database.getAll("SELECT id from ps_crud") { it.getLong(0)!! }
421-
assertEquals(actual = crudItems.size, expected = 1)
359+
crudItems shouldHaveSize 1
422360

423361
val count = database.get("SELECT COUNT(*) from people") { it.getLong(0)!! }
424-
assertEquals(actual = count, expected = 1)
362+
count shouldBe 1
425363
}
426364
}

0 commit comments

Comments
 (0)