diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8dbc1cc3..147f741b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,4 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { @@ -100,6 +101,13 @@ tasks.withType().configureEach { integrationTestClasses.forEach { excludeTestsMatching(it) } } } + testLogging { + exceptionFormat = TestExceptionFormat.FULL + events("failed") + showStackTraces = true + showExceptions = true + showCauses = true + } } tasks.register("integrationTest") { diff --git a/app/src/main/kotlin/com/subtracks/data/download/DownloadNotifier.kt b/app/src/main/kotlin/com/subtracks/data/download/DownloadNotifier.kt index 4c81b7d4..c30f26f8 100644 --- a/app/src/main/kotlin/com/subtracks/data/download/DownloadNotifier.kt +++ b/app/src/main/kotlin/com/subtracks/data/download/DownloadNotifier.kt @@ -13,9 +13,11 @@ import com.subtracks.R import com.subtracks.data.repo.DownloadRepository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking /** * Our own aggregated progress notification for the downloads the platform engine runs. The engine's @@ -44,7 +46,9 @@ class DownloadNotifier( } internal fun close() { + val job = scope.coroutineContext[Job] scope.cancel() + runBlocking { job?.join() } } private fun build(content: DownloadNotification): Notification = diff --git a/app/src/main/kotlin/com/subtracks/data/repo/SourceRepository.kt b/app/src/main/kotlin/com/subtracks/data/repo/SourceRepository.kt index f47a03e7..9b8786f4 100644 --- a/app/src/main/kotlin/com/subtracks/data/repo/SourceRepository.kt +++ b/app/src/main/kotlin/com/subtracks/data/repo/SourceRepository.kt @@ -16,6 +16,7 @@ import com.subtracks.data.source.streamLengthSuffix import com.subtracks.data.source.subsonic.SubsonicClient import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow @@ -25,6 +26,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient @@ -100,7 +102,9 @@ class SourceRepository( } fun close() { + val job = scope.coroutineContext[Job] scope.cancel() + runBlocking { job?.join() } } fun sources(): Flow> = db.sourcesDao().sources() diff --git a/app/src/main/kotlin/com/subtracks/data/sync/SyncManager.kt b/app/src/main/kotlin/com/subtracks/data/sync/SyncManager.kt index 75c69e86..91ddf68d 100644 --- a/app/src/main/kotlin/com/subtracks/data/sync/SyncManager.kt +++ b/app/src/main/kotlin/com/subtracks/data/sync/SyncManager.kt @@ -7,11 +7,14 @@ import com.subtracks.data.repo.SourceRepository import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking sealed interface SyncStatus { data object Idle : SyncStatus @@ -46,6 +49,12 @@ class SyncManager( requests.trySend(Unit) } + fun close() { + val job = scope.coroutineContext[Job] + scope.cancel() + runBlocking { job?.join() } + } + private suspend fun runSync() { if (sourceRepository.offline.value) { showMessage("Offline mode is on") diff --git a/app/src/test/kotlin/com/subtracks/data/db/ArtworkSeedDaoTest.kt b/app/src/test/kotlin/com/subtracks/data/db/ArtworkSeedDaoTest.kt index cacf6417..bcb80b9f 100644 --- a/app/src/test/kotlin/com/subtracks/data/db/ArtworkSeedDaoTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/db/ArtworkSeedDaoTest.kt @@ -9,7 +9,6 @@ import com.subtracks.data.model.ArtworkSeed import com.subtracks.data.model.Source import com.subtracks.data.repo.ArtworkSeedRepository import kotlinx.coroutines.test.runTest -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Before @@ -30,11 +29,6 @@ class ArtworkSeedDaoTest { .build() } - @After - fun tearDown() { - db.close() - } - @Test fun deletingASourceCascadesToItsSeeds() = runTest { diff --git a/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt b/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt index 253105f2..ffc96f8a 100644 --- a/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt @@ -17,7 +17,6 @@ import com.subtracks.data.model.SongDownload import com.subtracks.data.model.Source import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -39,11 +38,6 @@ class LibraryDaoTest { .build() } - @After - fun tearDown() { - db.close() - } - @Test fun albumSortsAndStarredFilter() = runTest { diff --git a/app/src/test/kotlin/com/subtracks/data/repo/DownloadRepositoryTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/DownloadRepositoryTest.kt index 0c97abfc..2bf36d3b 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/DownloadRepositoryTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/DownloadRepositoryTest.kt @@ -93,7 +93,6 @@ class DownloadRepositoryTest { fun tearDown() { repository.close() sources.close() - db.close() dir.deleteRecursively() } diff --git a/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt index a007b322..18419087 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/LibraryRepositoryTest.kt @@ -51,7 +51,6 @@ class LibraryRepositoryTest { @After fun tearDown() { sourceRepository.close() - db.close() } @Test diff --git a/app/src/test/kotlin/com/subtracks/data/repo/QueueKeysetTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/QueueKeysetTest.kt index b1ba328e..d3bf7c26 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/QueueKeysetTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/QueueKeysetTest.kt @@ -12,7 +12,6 @@ import com.subtracks.data.model.PlaylistSong import com.subtracks.data.model.Song import com.subtracks.data.model.Source import kotlinx.coroutines.test.runTest -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -35,11 +34,6 @@ class QueueKeysetTest { repository = QueueRepository(db) } - @After - fun tearDown() { - db.close() - } - @Test fun albumKeysetFromMatchesOffsetWindowAtADeepPosition() = runTest { diff --git a/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt index 5e22631a..08ccc52e 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/QueueRepositoryTest.kt @@ -15,7 +15,6 @@ import com.subtracks.data.model.QueueKind import com.subtracks.data.model.Song import com.subtracks.data.model.Source import kotlinx.coroutines.test.runTest -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -39,11 +38,6 @@ class QueueRepositoryTest { repository = QueueRepository(db) } - @After - fun tearDown() { - db.close() - } - @Test fun wholeAlbumEntryResolvesEveryTrackInOrder() = runTest { diff --git a/app/src/test/kotlin/com/subtracks/data/repo/SourceRepositoryTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/SourceRepositoryTest.kt index 64f83b2b..72e1ee1b 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/SourceRepositoryTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/SourceRepositoryTest.kt @@ -61,7 +61,6 @@ class SourceRepositoryTest { @After fun tearDown() { repository.close() - db.close() } @Test @@ -268,7 +267,13 @@ class SourceRepositoryTest { repository.setOfflineMode(true) withTimeout(5_000) { repository.offline.first { it } } - val ref = repository.coverArt("art-1") + var ref: CoverArtRef? = repository.coverArt("art-1") + withTimeout(5_000) { + while (ref?.url?.startsWith("file:") != true) { + delay(10) + ref = repository.coverArt("art-1") + } + } assertTrue("expected the stored file, was ${ref?.url}", ref?.url?.startsWith("file:") == true) } diff --git a/app/src/test/kotlin/com/subtracks/data/sync/SyncManagerTest.kt b/app/src/test/kotlin/com/subtracks/data/sync/SyncManagerTest.kt index d01b15e6..bd8fd3a5 100644 --- a/app/src/test/kotlin/com/subtracks/data/sync/SyncManagerTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/sync/SyncManagerTest.kt @@ -57,8 +57,8 @@ class SyncManagerTest { @After fun tearDown() { + manager.close() sourceRepository.close() - db.close() server.shutdown() } diff --git a/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt b/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt index 4e89b896..9f078f1b 100644 --- a/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt @@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.runTest -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -41,11 +40,6 @@ class SyncServiceTest { .build() } - @After - fun tearDown() { - db.close() - } - @Test fun syncInsertsAndPrunesRemovedRows() = runTest { diff --git a/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt b/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt index fccf8591..b962a347 100644 --- a/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt +++ b/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt @@ -103,7 +103,6 @@ class PlaybackControllerTest { controller.close() sources.close() downloads.close() - db.close() dispatcher.close() } diff --git a/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt b/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt index 8e33bf2e..2c9e99ed 100644 --- a/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt @@ -24,8 +24,9 @@ import com.subtracks.playback.FakePlayerHandle import com.subtracks.playback.PlaybackController import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.cancel +import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain @@ -90,11 +91,10 @@ class DownloadsViewModelTest { @After fun tearDown() { - viewModel.viewModelScope.cancel() + runBlocking { viewModel.viewModelScope.coroutineContext[Job]?.cancelAndJoin() } controller.close() downloadRepository.close() sourceRepository.close() - db.close() Dispatchers.resetMain() dispatcher.close() } diff --git a/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt b/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt index 0fe05163..34a26054 100644 --- a/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt @@ -24,8 +24,9 @@ import com.subtracks.playback.FakePlayerHandle import com.subtracks.playback.PlaybackController import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.cancel +import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.resetMain @@ -84,11 +85,10 @@ class QueueViewModelTest { @After fun tearDown() { - viewModel.viewModelScope.cancel() + runBlocking { viewModel.viewModelScope.coroutineContext[Job]?.cancelAndJoin() } controller.close() downloads.close() sources.close() - db.close() Dispatchers.resetMain() dispatcher.close() } diff --git a/app/src/test/kotlin/com/subtracks/ui/settings/AddSourceViewModelTest.kt b/app/src/test/kotlin/com/subtracks/ui/settings/AddSourceViewModelTest.kt index d732cb2e..6ad1bc80 100644 --- a/app/src/test/kotlin/com/subtracks/ui/settings/AddSourceViewModelTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/settings/AddSourceViewModelTest.kt @@ -16,8 +16,9 @@ import com.subtracks.data.repo.SourceRepository import com.subtracks.data.sync.SyncManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.cancel +import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain @@ -72,11 +73,10 @@ class AddSourceViewModelTest { @After fun tearDown() { - createdViewModels.forEach { it.viewModelScope.cancel() } + createdViewModels.forEach { runBlocking { it.viewModelScope.coroutineContext[Job]?.cancelAndJoin() } } server.shutdown() downloadRepository.close() sourceRepository.close() - db.close() Dispatchers.resetMain() dispatcher.close() }