Remove runBlocking from common source sets and update coroutines for KMP

This commit is contained in:
Alinson S. Xavier 2026-04-08 07:24:28 -05:00
parent 9cf4ec417c
commit 36fad2491e
17 changed files with 85 additions and 42 deletions

View File

@ -17,7 +17,6 @@ junitJupiter = "5.10.1"
junitVersion = "4.13.2"
konfetti-xml = "2.0.2"
kotlin = "2.1.10"
kotlinxCoroutinesCoreCommon = "1.3.8"
ksp = "2.1.10-1.0.30"
ktlint-plugin = "11.6.1"
ktor = "1.6.8"
@ -54,8 +53,8 @@ konfetti-xml = { group = "nl.dionsegijn", name = "konfetti-xml", version.ref = "
kotlin-stdlib-jdk8 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "ktxCoroutine" }
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "ktxCoroutine" }
kotlinx-coroutines-core-common = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-common", version.ref = "kotlinxCoroutinesCoreCommon" }
kotlinx-coroutines-core-jvm = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", version.ref = "ktxCoroutine" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "ktxCoroutine" }
ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version.ref = "ktor" }
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
ktor-client-jackson = { group = "io.ktor", name = "ktor-client-jackson", version.ref = "ktor" }

View File

@ -31,7 +31,8 @@ kotlin {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
implementation(libs.kotlinx.coroutines.core.common)
implementation(libs.kotlinx.coroutines.core)
compileOnly(libs.kotlin.inject.runtime)
}
}
@ -39,13 +40,13 @@ kotlin {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation(libs.kotlinx.coroutines.test)
}
}
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib-jdk8"))
compileOnly(libs.kotlin.inject.runtime)
implementation(libs.guava)
implementation(libs.kotlinx.coroutines.core.jvm)
implementation(libs.annotation)

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2016-2025 Álinson Santos Xavier <git@axavier.org>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.platform
expect fun <T> runSuspend(block: suspend () -> T): T

View File

@ -18,8 +18,8 @@
*/
package org.isoron.uhabits.core.tasks
import kotlinx.coroutines.runBlocking
import org.isoron.platform.io.UserFile
import org.isoron.platform.runSuspend
import org.isoron.platform.time.getToday
import org.isoron.uhabits.core.io.HabitsCSVExporter
import org.isoron.uhabits.core.models.Habit
@ -35,10 +35,10 @@ class ExportCSVTask(
override fun doInBackground() {
try {
val exporter = HabitsCSVExporter(habitList, selectedHabits)
val bytes = runBlocking { exporter.writeArchive() }
val bytes = runSuspend { exporter.writeArchive() }
val date = getToday().toCSVString()
val zipFile = outputDir.resolve("Loop Habits CSV $date.zip")
runBlocking { zipFile.writeBytes(bytes) }
runSuspend { zipFile.writeBytes(bytes) }
archiveFilename = zipFile.pathString
} catch (e: Exception) {
e.printStackTrace()

View File

@ -1,6 +1,6 @@
package org.isoron.platform.io
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@ -32,7 +32,7 @@ class MigrationTest {
}
@Test
fun testMigrateIdempotent() = runBlocking {
fun testMigrateIdempotent() = runTest {
val db = TestDatabaseHelper.createEmptyDatabase()
val version = db.getVersion()
db.migrateTo(version) { v -> TestDatabaseHelper.loadMigrationSQL(v) }

View File

@ -18,7 +18,6 @@
*/
package org.isoron.uhabits.core
import kotlinx.coroutines.runBlocking
import org.isoron.platform.io.Database
import org.isoron.platform.io.DatabaseOpener
import org.isoron.platform.io.FileOpener
@ -26,6 +25,7 @@ import org.isoron.platform.io.TestDatabaseHelper
import org.isoron.platform.io.UserFile
import org.isoron.platform.io.createTestDatabaseOpener
import org.isoron.platform.io.createTestFileOpener
import org.isoron.platform.runSuspend
import org.isoron.platform.time.LocalDate
import org.isoron.platform.time.setToday
import org.isoron.uhabits.core.commands.CommandRunner
@ -56,20 +56,20 @@ open class BaseUnitTest {
commandRunner = CommandRunner(taskRunner)
}
protected fun createTempDir(): UserFile = runBlocking {
protected fun createTempDir(): UserFile = runSuspend {
val dir = fileOpener.openUserFile("test-temp-dir-${tempFileCounter++}")
dir.mkdirs()
dir
}
protected fun copyResourceToTempFile(resourcePath: String): UserFile = runBlocking {
protected fun copyResourceToTempFile(resourcePath: String): UserFile = runSuspend {
val cleanPath = resourcePath.removePrefix("/")
val tempFile = fileOpener.openUserFile("test-temp-${tempFileCounter++}")
fileOpener.openResourceFile(cleanPath).copyTo(tempFile)
tempFile
}
protected fun openDatabaseResource(resourcePath: String): Database = runBlocking {
protected fun openDatabaseResource(resourcePath: String): Database = runSuspend {
val tempFile = copyResourceToTempFile(resourcePath)
databaseOpener.open(tempFile.pathString)
}

View File

@ -18,11 +18,11 @@
*/
package org.isoron.uhabits.core.database.migrations
import kotlinx.coroutines.runBlocking
import org.isoron.platform.io.Database
import org.isoron.platform.io.migrateTo
import org.isoron.platform.io.querySingle
import org.isoron.platform.io.run
import org.isoron.platform.runSuspend
import org.isoron.uhabits.core.BaseUnitTest
import kotlin.test.Test
import kotlin.test.assertContains
@ -37,7 +37,7 @@ class Version22Test : BaseUnitTest() {
db = openDatabaseResource("/databases/021.db")
}
private fun migrateTo(version: Int) = runBlocking {
private fun migrateTo(version: Int) = runSuspend {
db.migrateTo(version) { v ->
val path = "migrations/%02d.sql".format(v)
fileOpener.openResourceFile(path).lines().joinToString("\n")

View File

@ -19,10 +19,10 @@
package org.isoron.uhabits.core.database.migrations
import kotlinx.coroutines.runBlocking
import org.isoron.platform.io.Database
import org.isoron.platform.io.migrateTo
import org.isoron.platform.io.query
import org.isoron.platform.runSuspend
import org.isoron.uhabits.core.BaseUnitTest
import kotlin.test.Test
import kotlin.test.assertEquals
@ -36,7 +36,7 @@ class Version23Test : BaseUnitTest() {
db = openDatabaseResource("/databases/022.db")
}
private fun migrateTo(version: Int) = runBlocking {
private fun migrateTo(version: Int) = runSuspend {
db.migrateTo(version) { v ->
val path = "migrations/%02d.sql".format(v)
fileOpener.openResourceFile(path).lines().joinToString("\n")

View File

@ -18,7 +18,7 @@
*/
package org.isoron.uhabits.core.io
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.isoron.platform.io.ZipReader
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Habit
@ -36,7 +36,7 @@ class HabitsCSVExporterTest : BaseUnitTest() {
}
@Test
fun testExportCSV() = runBlocking {
fun testExportCSV() = runTest {
val selected: MutableList<Habit> = mutableListOf()
for (h in habitList) selected.add(h)
val exporter = HabitsCSVExporter(habitList, selected)

View File

@ -18,7 +18,7 @@
*/
package org.isoron.uhabits.core.io
import kotlinx.coroutines.runBlocking
import org.isoron.platform.runSuspend
import org.isoron.platform.time.LocalDate
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Entry
@ -157,7 +157,7 @@ class ImportTest : BaseUnitTest() {
return h.originalEntries.get(LocalDate(year, month, day)).notes == notes
}
private fun importFromFile(assetFilename: String) = runBlocking {
private fun importFromFile(assetFilename: String) = runSuspend {
val userFile = copyResourceToTempFile(assetFilename)
assertTrue(userFile.exists())
val importer = GenericImporter(

View File

@ -26,8 +26,8 @@ import dev.mokkery.matcher.any
import dev.mokkery.mock
import dev.mokkery.spy
import dev.mokkery.verify
import kotlinx.coroutines.runBlocking
import org.isoron.platform.io.UserFile
import org.isoron.platform.runSuspend
import org.isoron.platform.time.getToday
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Entry
@ -94,7 +94,7 @@ class ListHabitsBehaviorTest : BaseUnitTest() {
every { dirFinder.getCSVOutputDir() } returns outputDir
behavior.onExportCSV()
verify { screen.showSendFileScreen(any()) }
val files = runBlocking { outputDir.listFiles() }
val files = runSuspend { outputDir.listFiles() }
assertEquals(1, files!!.size)
}

View File

@ -22,7 +22,7 @@ import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.mock
import dev.mokkery.verify
import kotlinx.coroutines.runBlocking
import org.isoron.platform.runSuspend
import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Habit
import kotlin.test.Test
@ -60,7 +60,7 @@ class ShowHabitMenuPresenterTest : BaseUnitTest() {
val outputDir = createTempDir()
every { system.getCSVOutputDir() } returns outputDir
menu.onExportCSV()
val files = runBlocking { outputDir.listFiles() }
val files = runSuspend { outputDir.listFiles() }
assertEquals(1, files!!.size)
}
}

View File

@ -19,7 +19,7 @@
package org.isoron.uhabits.core.ui.views
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.isoron.platform.gui.assertRenders
import org.isoron.platform.io.createTestDateFormatter
import org.isoron.platform.time.LocalDate
@ -41,24 +41,24 @@ class BarChartTest {
}
@Test
fun testDraw() = runBlocking {
fun testDraw() = runTest {
assertRenders(300, 200, "$base/base.png", component)
}
@Test
fun testDrawDarkTheme() = runBlocking {
fun testDrawDarkTheme() = runTest {
component.theme = DarkTheme()
assertRenders(300, 200, "$base/themeDark.png", component)
}
@Test
fun testDrawWidgetTheme() = runBlocking {
fun testDrawWidgetTheme() = runTest {
component.theme = WidgetTheme()
assertRenders(300, 200, "$base/themeWidget.png", component)
}
@Test
fun testDrawWithOffset() = runBlocking {
fun testDrawWithOffset() = runTest {
component.dataOffset = 5
assertRenders(300, 200, "$base/offset.png", component)
}

View File

@ -23,7 +23,7 @@ import dev.mokkery.mock
import dev.mokkery.resetCalls
import dev.mokkery.verify
import dev.mokkery.verifyNoMoreCalls
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.isoron.platform.gui.assertRenders
import org.isoron.platform.io.createTestDateFormatter
import org.isoron.platform.time.DayOfWeek
@ -78,12 +78,12 @@ class HistoryChartTest {
)
@Test
fun testDraw() = runBlocking {
fun testDraw() = runTest {
assertRenders(400, 200, "$base/base.png", view)
}
@Test
fun testClick() = runBlocking {
fun testClick() = runTest {
assertRenders(400, 200, "$base/base.png", view)
// Click top left date
@ -114,7 +114,7 @@ class HistoryChartTest {
}
@Test
fun testLongClick() = runBlocking {
fun testLongClick() = runTest {
assertRenders(400, 200, "$base/base.png", view)
// Click top left date
@ -145,30 +145,30 @@ class HistoryChartTest {
}
@Test
fun testDrawWeekDay() = runBlocking {
fun testDrawWeekDay() = runTest {
view.firstWeekday = DayOfWeek.MONDAY
assertRenders(400, 200, "$base/weekday.png", view)
}
@Test
fun testDrawDifferentSize() = runBlocking {
fun testDrawDifferentSize() = runTest {
assertRenders(200, 200, "$base/small.png", view)
}
@Test
fun testDrawDarkTheme() = runBlocking {
fun testDrawDarkTheme() = runTest {
view.theme = DarkTheme()
assertRenders(400, 200, "$base/themeDark.png", view)
}
@Test
fun testDrawWidgetTheme() = runBlocking {
fun testDrawWidgetTheme() = runTest {
view.theme = WidgetTheme()
assertRenders(400, 200, "$base/themeWidget.png", view)
}
@Test
fun testDrawOffset() = runBlocking {
fun testDrawOffset() = runTest {
view.dataOffset = 2
assertRenders(400, 200, "$base/scroll.png", view)
}

View File

@ -1,6 +1,6 @@
package org.isoron.uhabits.core.utils
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.isoron.uhabits.core.BaseUnitTest
import kotlin.test.Test
import kotlin.test.assertTrue
@ -8,7 +8,7 @@ import kotlin.test.assertTrue
class FileExtensionsTest : BaseUnitTest() {
@Test
fun testIsSQLite3File() = runBlocking {
fun testIsSQLite3File() = runTest {
val userFile = copyResourceToTempFile("loop.db")
val isSqlite3File = isSQLite3File(userFile)
assertTrue(isSqlite3File)

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2016-2025 Álinson Santos Xavier <git@axavier.org>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.platform
import kotlinx.coroutines.runBlocking
actual fun <T> runSuspend(block: suspend () -> T): T = runBlocking { block() }

View File

@ -57,5 +57,4 @@ class MidnightTimerTest : BaseUnitTest() {
assertEquals(true, suspendedListener)
}
}
}