@@ -2,110 +2,59 @@ package com.powersync
2
2
3
3
import app.cash.turbine.turbineScope
4
4
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
9
5
import com.powersync.db.ActiveDatabaseGroup
10
- import com.powersync.db.getString
11
6
import com.powersync.db.schema.Schema
12
7
import com.powersync.testutils.UserRow
13
- import com.powersync.testutils.generatePrintLogWriter
8
+ import com.powersync.testutils.databaseTest
14
9
import com.powersync.testutils.getTempDir
15
10
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
16
15
import kotlinx.coroutines.CompletableDeferred
17
16
import kotlinx.coroutines.Dispatchers
18
17
import kotlinx.coroutines.async
19
18
import kotlinx.coroutines.delay
20
19
import kotlinx.coroutines.runBlocking
21
- import kotlinx.coroutines.test.runTest
22
20
import kotlinx.coroutines.withContext
23
- import kotlin.test.AfterTest
24
- import kotlin.test.BeforeTest
25
21
import kotlin.test.Test
26
22
import kotlin.test.assertEquals
27
- import kotlin.test.assertFailsWith
28
23
import kotlin.test.assertNotNull
29
- import kotlin.test.assertTrue
30
24
31
25
@OptIn(ExperimentalKermitApi ::class )
32
26
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
-
78
27
@Test
79
28
fun testLinksPowerSync () =
80
- runTest {
29
+ databaseTest {
81
30
database.get(" SELECT powersync_rs_version();" ) { it.getString(0 )!! }
82
31
}
83
32
84
33
@Test
85
34
fun testWAL () =
86
- runTest {
35
+ databaseTest {
87
36
val mode =
88
37
database.get(
89
38
" PRAGMA journal_mode" ,
90
39
mapper = { it.getString(0 )!! },
91
40
)
92
- assertEquals( mode, " wal" )
41
+ mode shouldBe " wal"
93
42
}
94
43
95
44
@Test
96
45
fun testFTS () =
97
- runTest {
46
+ databaseTest {
98
47
val mode =
99
48
database.get(
100
49
" SELECT sqlite_compileoption_used('ENABLE_FTS5');" ,
101
50
mapper = { it.getLong(0 )!! },
102
51
)
103
- assertEquals( mode, 1 )
52
+ mode shouldBe 1
104
53
}
105
54
106
55
@Test
107
56
fun testConcurrentReads () =
108
- runTest {
57
+ databaseTest {
109
58
database.execute(
110
59
" INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)" ,
111
60
listOf (
@@ -118,7 +67,7 @@ class DatabaseTest {
118
67
val transactionItemCreated = CompletableDeferred <Unit >()
119
68
// Start a long running writeTransaction
120
69
val transactionJob =
121
- async {
70
+ scope. async {
122
71
database.writeTransaction { tx ->
123
72
// Create another user
124
73
// External readers should not see this user while the transaction is open
@@ -156,7 +105,7 @@ class DatabaseTest {
156
105
157
106
@Test
158
107
fun testTransactionReads () =
159
- runTest {
108
+ databaseTest {
160
109
database.execute(
161
110
" INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)" ,
162
111
listOf (
@@ -187,18 +136,18 @@ class DatabaseTest {
187
136
188
137
@Test
189
138
fun testTableUpdates () =
190
- runTest {
139
+ databaseTest {
191
140
turbineScope {
192
141
val query = database.watch(" SELECT * FROM users" ) { UserRow .from(it) }.testIn(this )
193
142
194
143
// Wait for initial query
195
- assertEquals( 0 , query.awaitItem().size)
144
+ query.awaitItem() shouldHaveSize 0
196
145
197
146
database.execute(
198
147
" INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)" ,
199
148
listOf (
" Test" ,
" [email protected] " ),
200
149
)
201
- assertEquals( 1 , query.awaitItem().size)
150
+ query.awaitItem() shouldHaveSize 1
202
151
203
152
database.writeTransaction {
204
153
it.execute(
@@ -211,7 +160,7 @@ class DatabaseTest {
211
160
)
212
161
}
213
162
214
- assertEquals( 3 , query.awaitItem().size)
163
+ query.awaitItem() shouldHaveSize 3
215
164
216
165
try {
217
166
database.writeTransaction {
@@ -226,7 +175,7 @@ class DatabaseTest {
226
175
" INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)" ,
227
176
listOf (
" Test4" ,
" [email protected] " ),
228
177
)
229
- assertEquals( 4 , query.awaitItem().size)
178
+ query.awaitItem() shouldHaveSize 4
230
179
231
180
query.expectNoEvents()
232
181
query.cancel()
@@ -235,12 +184,12 @@ class DatabaseTest {
235
184
236
185
@Test
237
186
fun testClosingReadPool () =
238
- runTest {
187
+ databaseTest {
239
188
val pausedLock = CompletableDeferred <Unit >()
240
189
val inLock = CompletableDeferred <Unit >()
241
190
// Request a lock
242
191
val lockJob =
243
- async {
192
+ scope. async {
244
193
database.readLock {
245
194
inLock.complete(Unit )
246
195
runBlocking {
@@ -255,60 +204,48 @@ class DatabaseTest {
255
204
// Close the database. This should close the read pool
256
205
// The pool should wait for jobs to complete before closing
257
206
val closeJob =
258
- async {
207
+ scope. async {
259
208
database.close()
260
209
}
261
210
262
211
// 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()
265
214
266
215
// The database should not close yet
267
216
assertEquals(actual = database.closed, expected = false )
268
217
269
218
// 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
+
275
222
// Release the lock
276
223
pausedLock.complete(Unit )
277
224
lockJob.await()
278
225
closeJob.await()
279
226
280
- assertEquals(actual = database.closed, expected = true )
227
+ database.closed shouldBe true
281
228
}
282
229
283
230
@Test
284
231
fun openDBWithDirectory () =
285
- runTest {
232
+ databaseTest {
286
233
val tempDir =
287
234
getTempDir()
288
235
? : // SQLiteR, which is used on iOS, does not support opening dbs from directories
289
- return @runTest
290
-
291
- val dbFilename = " testdb"
236
+ return @databaseTest
292
237
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
305
242
}
306
243
307
244
@Test
308
245
fun warnsMultipleInstances () =
309
- runTest {
246
+ databaseTest {
310
247
// Opens a second DB with the same database filename
311
- val db2 = openDB ()
248
+ val db2 = openDatabase ()
312
249
waitFor {
313
250
assertNotNull(
314
251
logWriter.logs.find {
@@ -321,9 +258,9 @@ class DatabaseTest {
321
258
322
259
@Test
323
260
fun readConnectionsReadOnly () =
324
- runTest {
261
+ databaseTest {
325
262
val exception =
326
- assertFailsWith <PowerSyncException > {
263
+ shouldThrow <PowerSyncException > {
327
264
database.getOptional(
328
265
"""
329
266
INSERT INTO
@@ -335,23 +272,24 @@ class DatabaseTest {
335
272
parameters
= listOf (
" steven" ,
" [email protected] " ),
336
273
) {}
337
274
}
275
+
338
276
// The exception messages differ slightly between drivers
339
- assertTrue { exception.message!! .contains( " write a readonly database" ) }
277
+ exception.message shouldContain " write a readonly database"
340
278
}
341
279
342
280
@Test
343
281
fun basicReadTransaction () =
344
- runTest {
282
+ databaseTest {
345
283
val count =
346
284
database.readTransaction { it ->
347
285
it.get(" SELECT COUNT(*) from users" ) { it.getLong(0 )!! }
348
286
}
349
- assertEquals(expected = 0 , actual = count)
287
+ count shouldBe 0
350
288
}
351
289
352
290
@Test
353
291
fun localOnlyCRUD () =
354
- runTest {
292
+ databaseTest {
355
293
database.updateSchema(
356
294
schema =
357
295
Schema (
@@ -375,16 +313,16 @@ class DatabaseTest {
375
313
)
376
314
377
315
val count = database.get(" SELECT COUNT(*) FROM local_users" ) { it.getLong(0 )!! }
378
- assertEquals(actual = count, expected = 1 )
316
+ count shouldBe 1
379
317
380
318
// No CRUD entries should be present for local only tables
381
319
val crudItems = database.getAll(" SELECT id from ps_crud" ) { it.getLong(0 )!! }
382
- assertEquals(actual = crudItems.size, expected = 0 )
320
+ crudItems shouldHaveSize 0
383
321
}
384
322
385
323
@Test
386
324
fun insertOnlyCRUD () =
387
- runTest {
325
+ databaseTest {
388
326
database.updateSchema(schema = Schema (UserRow .table.copy(insertOnly = true )))
389
327
390
328
database.execute(
@@ -397,15 +335,15 @@ class DatabaseTest {
397
335
)
398
336
399
337
val crudItems = database.getAll(" SELECT id from ps_crud" ) { it.getLong(0 )!! }
400
- assertEquals(actual = crudItems.size, expected = 1 )
338
+ crudItems shouldHaveSize 1
401
339
402
340
val count = database.get(" SELECT COUNT(*) from users" ) { it.getLong(0 )!! }
403
- assertEquals(actual = count, expected = 0 )
341
+ count shouldBe 0
404
342
}
405
343
406
344
@Test
407
345
fun viewOverride () =
408
- runTest {
346
+ databaseTest {
409
347
database.updateSchema(schema = Schema (UserRow .table.copy(viewNameOverride = " people" )))
410
348
411
349
database.execute(
@@ -418,9 +356,9 @@ class DatabaseTest {
418
356
)
419
357
420
358
val crudItems = database.getAll(" SELECT id from ps_crud" ) { it.getLong(0 )!! }
421
- assertEquals(actual = crudItems.size, expected = 1 )
359
+ crudItems shouldHaveSize 1
422
360
423
361
val count = database.get(" SELECT COUNT(*) from people" ) { it.getLong(0 )!! }
424
- assertEquals(actual = count, expected = 1 )
362
+ count shouldBe 1
425
363
}
426
364
}
0 commit comments