Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
include:
- os: macos-latest
name: watchos
targets: watchosSimulatorArm64Test tvosSimulatorArm64Test
targets: watchosSimulatorArm64Test
- os: macos-latest
name: tvos
targets: tvosSimulatorArm64Test
Expand Down
37 changes: 0 additions & 37 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ plugins {
alias(libs.plugins.mavenPublishPlugin) apply false
alias(libs.plugins.downloadPlugin) apply false
alias(libs.plugins.kotlinter) apply false
alias(libs.plugins.keeper) apply false
alias(libs.plugins.kotlin.atomicfu) apply false
alias(libs.plugins.cocoapods) apply false
alias(libs.plugins.ksp) apply false
Expand All @@ -25,42 +24,6 @@ plugins {
id("dokka-convention")
}

allprojects {
repositories {
mavenCentral()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
maven("https://www.jetbrains.com/intellij-repository/releases")
maven("https://cache-redirector.jetbrains.com/intellij-dependencies")
// Repo for the backported Android IntelliJ Plugin by Jetbrains used in Ultimate
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-ide-plugin-dependencies/")
}

configurations.configureEach {
exclude(group = "com.jetbrains.rd")
exclude(group = "com.github.jetbrains", module = "jetCheck")
exclude(group = "com.jetbrains.intellij.platform", module = "wsl-impl")
exclude(group = "org.roaringbitmap")
exclude(group = "com.jetbrains.infra")
exclude(group = "org.jetbrains.teamcity")
exclude(group = "org.roaringbitmap")
exclude(group = "ai.grazie.spell")
exclude(group = "ai.grazie.model")
exclude(group = "ai.grazie.utils")
exclude(group = "ai.grazie.nlp")

// We have a transitive dependency on this due to Kermit, but need the fixed version to support Java 8
resolutionStrategy.force("co.touchlab:stately-collections:${libs.versions.stately.get()}")
}
}
subprojects {
val GROUP: String by project
val LIBRARY_VERSION: String by project

group = GROUP
version = LIBRARY_VERSION
}

tasks.getByName<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
Expand Down
16 changes: 6 additions & 10 deletions core-tests-android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import com.slack.keeper.optInToKeeper

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.keeper)
}

dependencies {
implementation(projects.core)
implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.material)
implementation(libs.test.coroutines)
implementation(libs.test.turbine)

androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.test.coroutines)
androidTestImplementation(libs.test.turbine)
}

android {
Expand All @@ -35,7 +32,10 @@ android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("debug")
}
}
Expand All @@ -48,7 +48,3 @@ android {
jvmTarget = "11"
}
}

androidComponents {
beforeVariants { it.optInToKeeper() }
}
18 changes: 17 additions & 1 deletion core-tests-android/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,20 @@

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile

# This is the entrypoint in the app, we want to minify everything else to simulate a real app build.
-keep class com.powersync.testutils.IntegrationTestHelpers {
*;
}

# Not used in main app, needed for testing
-keep class kotlin.** {
*;
}

-keep class androidx.tracing.Trace {
public static void beginSection(java.lang.String);
public static void endSection();
public static void forceEnableAppTracing();
}
Original file line number Diff line number Diff line change
@@ -1,235 +1,47 @@
package com.powersync

import com.powersync.testutils.IntegrationTestHelpers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import app.cash.turbine.turbineScope
import com.powersync.db.schema.Schema
import com.powersync.testutils.UserRow
import kotlinx.coroutines.*
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class AndroidDatabaseTest {
private lateinit var database: PowerSyncDatabase
private val helpers = IntegrationTestHelpers(
InstrumentationRegistry.getInstrumentation().targetContext
)

@Before
fun setupDatabase() {
database =
PowerSyncDatabase(
factory = DatabaseDriverFactory(InstrumentationRegistry.getInstrumentation().targetContext),
schema = Schema(UserRow.table),
dbFilename = "testdb",
)

runBlocking {
database.disconnectAndClear(true)
}
helpers.setup()
}

@After
fun tearDown() {
runBlocking {
database.disconnectAndClear(true)
database.close()
}
helpers.tearDown()
}

@Test
fun testLinksPowerSync() =
runTest {
database.get("SELECT powersync_rs_version() AS r;") { it.getString(0)!! }
}
fun testLinksPowerSync() = helpers.testLinksPowerSync()

@Test
fun testTableUpdates() =
runTest {
turbineScope {
val query = database.watch("SELECT * FROM users") { UserRow.from(it) }.testIn(this)

// Wait for initial query
assertEquals(0, query.awaitItem().size)

database.execute(
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
listOf("Test", "test@example.org"),
)
assertEquals(1, query.awaitItem().size)

database.writeTransaction {
it.execute(
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
listOf("Test2", "test2@example.org"),
)
it.execute(
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
listOf("Test3", "test3@example.org"),
)
}

assertEquals(3, query.awaitItem().size)

try {
database.writeTransaction {
it.execute("DELETE FROM users;")
it.execute("syntax error, revert please")
}
} catch (e: Exception) {
// Ignore
}

database.execute(
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
listOf("Test4", "test4@example.org"),
)
assertEquals(4, query.awaitItem().size)

query.expectNoEvents()
query.cancel()
}
}
fun testTableUpdates() = helpers.testTableUpdates()

@Test
fun testConcurrentReads() =
runTest {
database.execute(
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
listOf(
"steven",
"s@journeyapps.com",
),
)

val pausedTransaction = CompletableDeferred<Unit>()
val transactionItemCreated = CompletableDeferred<Unit>()
// Start a long running writeTransaction
val transactionJob =
async {
database.writeTransaction { tx ->
// Create another user
// External readers should not see this user while the transaction is open
tx.execute(
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
listOf(
"steven",
"s@journeyapps.com",
),
)

transactionItemCreated.complete(Unit)

// Block this transaction until we free it
runBlocking {
pausedTransaction.await()
}
}
}

// Make sure to wait for the item to have been created in the transaction
transactionItemCreated.await()
// Try and read while the write transaction is busy
val result = database.getAll("SELECT * FROM users") { UserRow.from(it) }
// The transaction is not commited yet, we should only read 1 user
assertEquals(result.size, 1)

// Let the transaction complete
pausedTransaction.complete(Unit)
transactionJob.await()

val afterTx = database.getAll("SELECT * FROM users") { UserRow.from(it) }
assertEquals(afterTx.size, 2)
}
fun testConcurrentReads() = helpers.testConcurrentReads()

@Test
fun transactionReads() =
runTest {
database.execute(
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
listOf(
"steven",
"s@journeyapps.com",
),
)

database.writeTransaction { tx ->
val userCount =
tx.getAll("SELECT COUNT(*) as count FROM users") { cursor -> cursor.getLong(0)!! }
assertEquals(userCount[0], 1)

tx.execute(
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
listOf(
"steven",
"s@journeyapps.com",
),
)

// Getters inside the transaction should be able to see the latest update
val userCount2 =
tx.getAll("SELECT COUNT(*) as count FROM users") { cursor -> cursor.getLong(0)!! }
assertEquals(userCount2[0], 2)
}
}
fun transactionReads() = helpers.transactionReads()

@Test
fun openDBWithDirectory() =
runTest {
val tempDir =
InstrumentationRegistry
.getInstrumentation()
.targetContext.cacheDir.canonicalPath
val dbFilename = "testdb"

val db =
PowerSyncDatabase(
factory = DatabaseDriverFactory(InstrumentationRegistry.getInstrumentation().targetContext),
schema = Schema(UserRow.table),
dbDirectory = tempDir,
dbFilename = dbFilename,
)

val path = db.get("SELECT file FROM pragma_database_list;") { it.getString(0)!! }

assertEquals(path.contains(tempDir), true)

db.close()
}
fun openDBWithDirectory() = helpers.openDBWithDirectory()

@Test
fun readConnectionsReadOnly() =
runTest {
val exception =
assertThrows(PowerSyncException::class.java) {
// This version of assertThrows does not support suspending functions
runBlocking {
database.getOptional(
"""
INSERT INTO
users (id, name, email)
VALUES
(uuid(), ?, ?)
RETURNING *
""".trimIndent(),
parameters = listOf("steven", "steven@journeyapps.com"),
) {}
}
}
// The exception messages differ slightly between drivers
assertEquals(exception.message!!.contains("write a readonly database"), true)
}
fun readConnectionsReadOnly() = helpers.readConnectionsReadOnly()

@Test
fun canUseTempStore() = runTest {
database.execute("PRAGMA temp_store = 1;") // Store temporary data as files
database.execute("CREATE TEMP TABLE foo (bar TEXT);")
val data = "new row".repeat(100);
for (i in 0..10000) {
database.execute("INSERT INTO foo VALUES (?)", parameters = listOf(data))
}
}
fun canUseTempStore() = helpers.canUseTempStore()
}
Loading