From 422d9cbc9b92be573d86dc3defd25cef817c699c Mon Sep 17 00:00:00 2001 From: clementetb Date: Tue, 4 Jun 2024 17:02:33 +0200 Subject: [PATCH 01/10] [RKOTLIN-1096] Map `is_fatal` to `UnrecoverableSyncException` (#1768) --- CHANGELOG.md | 27 +++++++++ .../kotlin/mongodb/internal/RealmSyncUtils.kt | 44 ++++++++------ .../test/mongodb/common/SyncedRealmTests.kt | 60 ++++++++++++++++++- 3 files changed, 110 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe1dcac82c..840126c351 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +## 2.0.1 (YYYY-MM-DD) + +### Breaking changes +* None. + +### Enhancements +* None. + +### Fixed +* [Sync] Fatal sync exceptions are now thrown as `UnrecoverableSyncException`. (Issue [#1767](https://github.com/realm/realm-kotlin/issues/1767) [RKOTLIN-1096](https://jira.mongodb.org/browse/RKOTLIN-1096)). + +### Compatibility +* File format: Generates Realms with file format v24 (reads and upgrades file format v10 or later). +* Realm Studio 15.0.0 or above is required to open Realms created by this version. +* This release is compatible with the following Kotlin releases: + * Kotlin 2.0.0 and above. Support for experimental K2-compilation with `kotlin.experimental.tryK2=true`. + * Ktor 2.1.2 and above. + * Coroutines 1.7.0 and above. + * AtomicFu 0.18.3 and above. + * The new memory model only. See https://github.com/realm/realm-kotlin#kotlin-memory-model-and-coroutine-compatibility +* Minimum Kbson 0.4.0. +* Minimum Gradle version: 7.2. +* Minimum Android Gradle Plugin version: 7.1.3. +* Minimum Android SDK: 16. +* Minimum R8: 8.0.34. + + ## 2.0.0 (2024-06-03) > [!NOTE] diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncUtils.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncUtils.kt index 73fb222306..5ecb7b0766 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncUtils.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncUtils.kt @@ -78,26 +78,34 @@ internal fun channelResultCallback( internal fun convertSyncError(syncError: SyncError): SyncException { val errorCode = syncError.errorCode val message = createMessageFromSyncError(errorCode) - return when (errorCode.errorCode) { - ErrorCode.RLM_ERR_WRONG_SYNC_TYPE -> WrongSyncTypeException(message) + return if (syncError.isFatal) { + // An unrecoverable exception happened + UnrecoverableSyncException(message) + } else { + when (errorCode.errorCode) { + ErrorCode.RLM_ERR_WRONG_SYNC_TYPE -> WrongSyncTypeException(message) - ErrorCode.RLM_ERR_INVALID_SUBSCRIPTION_QUERY -> { - // Flexible Sync Query was rejected by the server - BadFlexibleSyncQueryException(message) - } - ErrorCode.RLM_ERR_SYNC_COMPENSATING_WRITE -> CompensatingWriteException(message, syncError.compensatingWrites) + ErrorCode.RLM_ERR_INVALID_SUBSCRIPTION_QUERY -> { + // Flexible Sync Query was rejected by the server + BadFlexibleSyncQueryException(message) + } - ErrorCode.RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED, - ErrorCode.RLM_ERR_SYNC_PROTOCOL_NEGOTIATION_FAILED, - ErrorCode.RLM_ERR_SYNC_PERMISSION_DENIED -> { - // Permission denied errors should be unrecoverable according to Core, i.e. the - // client will disconnect sync and transition to the "inactive" state - UnrecoverableSyncException(message) - } - else -> { - // An error happened we are not sure how to handle. Just report as a generic - // SyncException. - SyncException(message) + ErrorCode.RLM_ERR_SYNC_COMPENSATING_WRITE -> CompensatingWriteException( + message, + syncError.compensatingWrites + ) + ErrorCode.RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED, + ErrorCode.RLM_ERR_SYNC_PROTOCOL_NEGOTIATION_FAILED, + ErrorCode.RLM_ERR_SYNC_PERMISSION_DENIED -> { + // Permission denied errors should be unrecoverable according to Core, i.e. the + // client will disconnect sync and transition to the "inactive" state + UnrecoverableSyncException(message) + } + else -> { + // An error happened we are not sure how to handle. Just report as a generic + // SyncException. + SyncException(message) + } } } } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index 51f3c65f3a..c9393a25de 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:Suppress("invisible_reference", "invisible_member") +@file:Suppress("invisible_member", "invisible_reference") package io.realm.kotlin.test.mongodb.common @@ -28,15 +28,19 @@ import io.realm.kotlin.entities.sync.flx.FlexChildObject import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject import io.realm.kotlin.entities.sync.flx.FlexParentObject import io.realm.kotlin.ext.query +import io.realm.kotlin.internal.interop.ErrorCode +import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.platform.fileExists import io.realm.kotlin.internal.platform.pathOf import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.log.RealmLog import io.realm.kotlin.mongodb.App +import io.realm.kotlin.mongodb.Credentials import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.exceptions.DownloadingRealmTimeOutException import io.realm.kotlin.mongodb.exceptions.SyncException import io.realm.kotlin.mongodb.exceptions.UnrecoverableSyncException +import io.realm.kotlin.mongodb.internal.SyncSessionImpl import io.realm.kotlin.mongodb.subscriptions import io.realm.kotlin.mongodb.sync.InitialSubscriptionsCallback import io.realm.kotlin.mongodb.sync.SubscriptionSetState @@ -313,6 +317,56 @@ class SyncedRealmTests { realm3.close() } + @Test + fun errorHandlerProcessFatalSyncErrors() { + val channel = TestChannel() + val user = runBlocking { + app.login(Credentials.anonymous()) + } + + val config = SyncConfiguration.Builder( + schema = setOf(ParentPk::class, ChildPk::class), + user = user, + partitionValue = partitionValue + ).errorHandler { _, error -> + channel.trySendOrFail(error) + }.build() + + runBlocking { + val deferred = async { + Realm.open(config).use { realm -> + RealmInterop.realm_sync_session_handle_error_for_testing( + syncSession = (realm.syncSession as SyncSessionImpl).nativePointer, + error = ErrorCode.RLM_ERR_ACCOUNT_NAME_IN_USE, + errorMessage = "Non fatal error", + isFatal = true, // flipped https://jira.mongodb.org/browse/RCORE-2146 + ) + + RealmInterop.realm_sync_session_handle_error_for_testing( + syncSession = (realm.syncSession as SyncSessionImpl).nativePointer, + error = ErrorCode.RLM_ERR_INTERNAL_SERVER_ERROR, + errorMessage = "Fatal error", + isFatal = false, // flipped https://jira.mongodb.org/browse/RCORE-2146 + ) + } + } + + // First error + channel.receiveOrFail().let { error -> + assertNotNull(error.message) + assertIs(error) + } + + // Second + channel.receiveOrFail().let { error -> + assertNotNull(error.message) + assertIs(error) + } + + deferred.cancel() + } + } + @Test fun errorHandlerReceivesPermissionDeniedSyncError() { val channel = TestChannel() @@ -336,15 +390,15 @@ class SyncedRealmTests { Realm.open(config).use { // Make sure that the test eventually fail. Coroutines can cancel a delay // so this doesn't always block the test for 10 seconds. - delay(10 * 1000) + delay(10_000) channel.send(AssertionError("Realm was successfully opened")) } } val error = channel.receiveOrFail() - assertTrue(error is UnrecoverableSyncException, "Was $error") val message = error.message assertNotNull(message) + assertTrue(error is UnrecoverableSyncException, "Was $error") assertTrue( message.lowercase().contains("permission denied"), "The error should be 'PermissionDenied' but it was: $message" From c8e37806d740e12ae2c0deddce8e1df60253d5f6 Mon Sep 17 00:00:00 2001 From: clementetb Date: Fri, 7 Jun 2024 15:52:16 +0200 Subject: [PATCH 02/10] [RKOTLIN-1102] Remove nullability check in `SubscriptionSetImpl.waitForSynchronization` (#1778) --- .github/workflows/include-check-cache.yml | 2 +- CHANGELOG.md | 1 + .../kotlin/io/realm/kotlin/exceptions/RealmException.kt | 6 +++--- .../io/realm/kotlin/mongodb/exceptions/AppException.kt | 2 +- .../io/realm/kotlin/mongodb/exceptions/SyncExceptions.kt | 4 ++-- .../io/realm/kotlin/mongodb/internal/SubscriptionSetImpl.kt | 2 +- .../io/realm/kotlin/test/mongodb/common/CredentialsTests.kt | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/include-check-cache.yml b/.github/workflows/include-check-cache.yml index 5ce3783432..c7bfb275e4 100644 --- a/.github/workflows/include-check-cache.yml +++ b/.github/workflows/include-check-cache.yml @@ -93,7 +93,7 @@ jobs: # This also include changes to Realm Core as they are hashed as part of `/packages/external/core` - name: Calculate ./packages SHAs id: packages-cache-key - run: echo "sha=${{ hashFiles('./packages/**', './buildSrc/**', '!./packages/test-base/**', '!./packages/test-sync/**') }}" >> $GITHUB_OUTPUT + run: echo "sha=${{ hashFiles('./packages/**', './buildSrc/**') }}" >> $GITHUB_OUTPUT - name: Calculate ./benchmarks SHAs id: calculate-benchmarks-cache-key diff --git a/CHANGELOG.md b/CHANGELOG.md index 840126c351..4828478bd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed * [Sync] Fatal sync exceptions are now thrown as `UnrecoverableSyncException`. (Issue [#1767](https://github.com/realm/realm-kotlin/issues/1767) [RKOTLIN-1096](https://jira.mongodb.org/browse/RKOTLIN-1096)). +* [Sync] Fix `NullPointerException` in `SubscriptionSet.waitForSynchronization`. (Issue [#1777](https://github.com/realm/realm-kotlin/issues/1777) [RKOTLIN-1102](https://jira.mongodb.org/browse/RKOTLIN-1102)). ### Compatibility * File format: Generates Realms with file format v24 (reads and upgrades file format v10 or later). diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/exceptions/RealmException.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/exceptions/RealmException.kt index 761e7c88c1..bc0f9d945d 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/exceptions/RealmException.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/exceptions/RealmException.kt @@ -9,7 +9,7 @@ package io.realm.kotlin.exceptions */ public open class RealmException : RuntimeException { public constructor() : super() - public constructor(message: String) : super(message) - public constructor(message: String, cause: Throwable) : super(message, cause) - public constructor(cause: Throwable) : super(cause) + public constructor(message: String?) : super(message) + public constructor(message: String?, cause: Throwable?) : super(message, cause) + public constructor(cause: Throwable?) : super(cause) } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/AppException.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/AppException.kt index 54eba9b83f..63dcf9e363 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/AppException.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/AppException.kt @@ -94,5 +94,5 @@ import io.realm.kotlin.exceptions.RealmException * @see SyncException */ public open class AppException internal constructor( - message: String, + message: String?, ) : RealmException(message) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/SyncExceptions.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/SyncExceptions.kt index 2e6806062d..ce4da552f7 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/SyncExceptions.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/SyncExceptions.kt @@ -31,7 +31,7 @@ import io.realm.kotlin.types.RealmAny * * @see io.realm.kotlin.mongodb.sync.SyncConfiguration.Builder.errorHandler */ -public open class SyncException internal constructor(message: String) : AppException(message) +public open class SyncException internal constructor(message: String?) : AppException(message) /** * Thrown when something has gone wrong with Device Sync in a way that is not recoverable. @@ -60,7 +60,7 @@ public class WrongSyncTypeException internal constructor(message: String) : Sync * Thrown when the server does not support one or more of the queries defined in the * [io.realm.kotlin.mongodb.sync.SubscriptionSet]. */ -public class BadFlexibleSyncQueryException internal constructor(message: String) : +public class BadFlexibleSyncQueryException internal constructor(message: String?) : SyncException(message) /** diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionSetImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionSetImpl.kt index 9a82592ec0..6f0baef67b 100644 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionSetImpl.kt +++ b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionSetImpl.kt @@ -127,7 +127,7 @@ internal class SubscriptionSetImpl( if (result) { return true } else { - throw BadFlexibleSyncQueryException(errorMessage!!) + throw BadFlexibleSyncQueryException(errorMessage) } } else -> throw IllegalStateException("Unexpected value: $result") diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/CredentialsTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/CredentialsTests.kt index d76391c980..53f3bf8fb8 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/CredentialsTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/CredentialsTests.kt @@ -369,7 +369,7 @@ class CredentialsTests { payload = mapOf("mail" to TestHelper.randomEmail(), "id" to 0) ) - assertFailsWithMessage("unauthorized") { + assertFailsWithMessage("Authentication failed") { runBlocking { app.login(credentials) } From 18b4def39ca7c301c85dba8bef46b1dc69f31542 Mon Sep 17 00:00:00 2001 From: clementetb Date: Thu, 27 Jun 2024 11:04:14 +0200 Subject: [PATCH 03/10] Add kotlin problem matchers (#1775) --- .github/problem-matchers/detekt.json | 17 + .github/problem-matchers/kotlin.json | 40 ++ .github/problem-matchers/ktlint.json | 17 + .github/workflows/auto-merge-branches.yml | 2 +- .github/workflows/include-check-cache.yml | 461 +++++++++--------- .github/workflows/include-deploy-release.yml | 22 +- .github/workflows/include-deploy-snapshot.yml | 6 +- .../workflows/include-integration-tests.yml | 60 +-- .github/workflows/include-static-analysis.yml | 44 +- .github/workflows/pr.yml | 284 ++++++----- .../src/main/kotlin/realm-lint.gradle.kts | 1 + 11 files changed, 521 insertions(+), 433 deletions(-) create mode 100644 .github/problem-matchers/detekt.json create mode 100644 .github/problem-matchers/kotlin.json create mode 100644 .github/problem-matchers/ktlint.json diff --git a/.github/problem-matchers/detekt.json b/.github/problem-matchers/detekt.json new file mode 100644 index 0000000000..9911b4c1a2 --- /dev/null +++ b/.github/problem-matchers/detekt.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "detekt", + "severity": "error", + "pattern": [ + { + "regexp": "(.+\\.kt):(\\d+):(\\d+):\\s(.*)", + "file": 1, + "line": 2, + "column": 3, + "message": 4 + } + ] + } + ] +} diff --git a/.github/problem-matchers/kotlin.json b/.github/problem-matchers/kotlin.json new file mode 100644 index 0000000000..a5cdc3ab7c --- /dev/null +++ b/.github/problem-matchers/kotlin.json @@ -0,0 +1,40 @@ +{ + "problemMatcher": [ + { + "owner": "kotlin", + "severity": "error", + "pattern": [ + { + "regexp": "^e\\:\\s(?:(?:(.*):(?:\\s\\()?(\\d+)(?:(?:\\,\\s)|\\:)(\\d+))(?:\\))?\\:\\s)?(.*)$", + "file": 1, + "line": 2, + "column": 3, + "message": 4 + } + ] + }, + { + "owner": "kotlin-warning", + "severity": "warning", + "pattern": [ + { + "regexp": "^w\\:\\s(?:(?:(.*):(?:\\s\\()?(\\d+)(?:(?:\\,\\s)|\\:)(\\d+))(?:\\))?\\:\\s)?(.*)$", + "file": 1, + "line": 2, + "column": 3, + "message": 4 + } + ] + }, + { + "owner": "gradle-warning", + "severity": "warning", + "pattern": [ + { + "regexp": "^WARNING:(.*(?:\\n(?!w|e\\:|>|FAILURE|BUILD SUCCESSFUL).*)*)$", + "message": 1 + } + ] + } + ] +} diff --git a/.github/problem-matchers/ktlint.json b/.github/problem-matchers/ktlint.json new file mode 100644 index 0000000000..924000e815 --- /dev/null +++ b/.github/problem-matchers/ktlint.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "ktlint", + "severity": "error", + "pattern": [ + { + "regexp": "(.+\\.kt):(\\d+):(\\d+):\\s(.*)", + "file": 1, + "line": 2, + "column": 3, + "message": 4 + } + ] + } + ] +} diff --git a/.github/workflows/auto-merge-branches.yml b/.github/workflows/auto-merge-branches.yml index cd16801f2e..9df6218f79 100644 --- a/.github/workflows/auto-merge-branches.yml +++ b/.github/workflows/auto-merge-branches.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Set the branch we want to merge changes into as step output. This step should list each branch name found in the `push` # trigger in the top of this file. diff --git a/.github/workflows/include-check-cache.yml b/.github/workflows/include-check-cache.yml index c7bfb275e4..98cb33a9ed 100644 --- a/.github/workflows/include-check-cache.yml +++ b/.github/workflows/include-check-cache.yml @@ -19,68 +19,54 @@ on: workflow_call: outputs: version-label: - value: ${{ jobs.check-cache.outputs.version-label }} + value: ${{ jobs.checksum.outputs.version-label }} + packages-sha: + value: ${{ jobs.checksum.outputs.packages-sha }} + benchmarks-sha: + value: ${{ jobs.checksum.outputs.benchmarks-sha }} + core-commit-sha: + value: ${{ jobs.checksum.outputs.core-commit-sha }} + packages-metadata-cache-hit: - value: ${{ jobs.check-cache.outputs.packages-metadata-cache-hit }} + value: ${{ jobs.metadata-compiler-plugin.outputs.packages-metadata-cache-hit }} packages-jvm-cache-hit: - value: ${{ jobs.check-cache.outputs.packages-jvm-cache-hit }} + value: ${{ jobs.jvm-all.outputs.packages-jvm-cache-hit }} packages-android-cache-hit: - value: ${{ jobs.check-cache.outputs.packages-android-cache-hit }} + value: ${{ jobs.android.outputs.packages-android-cache-hit }} android-test-base-apk-cache-hit: - value: ${{ jobs.check-cache.outputs.android-test-base-apk-cache-hit }} + value: ${{ jobs.android-test-base.outputs.android-test-base-apk-cache-hit }} android-test-sync-apk-cache-hit: - value: ${{ jobs.check-cache.outputs.android-test-sync-apk-cache-hit }} + value: ${{ jobs.android-test-sync.outputs.android-test-sync-apk-cache-hit }} packages-macos-x64-cache-hit: - value: ${{ jobs.check-cache.outputs.packages-macos-x64-cache-hit }} + value: ${{ jobs.macos-x64.outputs.packages-macos-x64-cache-hit }} packages-macos-arm64-cache-hit: - value: ${{ jobs.check-cache.outputs.packages-macos-arm64-cache-hit }} + value: ${{ jobs.macos-arm.outputs.packages-macos-arm64-cache-hit }} packages-ios-x64-cache-hit: - value: ${{ jobs.check-cache.outputs.packages-ios-x64-cache-hit }} + value: ${{ jobs.ios-x64.outputs.packages-ios-x64-cache-hit }} packages-ios-arm64-cache-hit: - value: ${{ jobs.check-cache.outputs.packages-ios-arm64-cache-hit }} + value: ${{ jobs.ios-arm.outputs.packages-ios-arm64-cache-hit }} jni-swig-stub-cache-hit: - value: ${{ jobs.check-cache.outputs.jni-swig-stub-cache-hit }} + value: ${{ jobs.jni-stub.outputs.jni-swig-stub-cache-hit }} jni-linux-lib-cache-hit: - value: ${{ jobs.check-cache.outputs.jni-linux-lib-cache-hit }} + value: ${{ jobs.jni-linux-lib.outputs.jni-linux-lib-cache-hit }} jni-macos-lib-cache-hit: - value: ${{ jobs.check-cache.outputs.jni-macos-lib-cache-hit }} + value: ${{ jobs.jni-macos-lib.outputs.jni-macos-lib-cache-hit }} jni-windows-lib-cache-hit: - value: ${{ jobs.check-cache.outputs.jni-windows-lib-cache-hit }} - packages-sha: - value: ${{ jobs.check-cache.outputs.packages-sha }} - benchmarks-sha: - value: ${{ jobs.check-cache.outputs.benchmarks-sha }} - core-commit-sha: - value: ${{ jobs.check-cache.outputs.core-commit-sha }} + value: ${{ jobs.jni-windows-lib.outputs.jni-windows-lib-cache-hit }} jobs: - check-cache: + checksum: runs-on: ubuntu-latest - name: Check cache - env: - CACHE_SKIP_SAVE: true + name: Cache SHA outputs: version-label: ${{ steps.find-library-version.outputs.label }} - packages-metadata-cache-hit: ${{ steps.kotlin-metadata-cache.outputs.cache-hit }} - packages-jvm-cache-hit: ${{ steps.jvm-cache.outputs.cache-hit }} - packages-android-cache-hit: ${{ steps.android-cache.outputs.cache-hit }} - android-test-base-apk-cache-hit: ${{ steps.android-test-base-apk.outputs.cache-hit }} - android-test-sync-apk-cache-hit: ${{ steps.android-test-sync-apk.outputs.cache-hit }} - packages-macos-x64-cache-hit: ${{ steps.macos-x64-cache.outputs.cache-hit }} - packages-macos-arm64-cache-hit: ${{ steps.macos-arm64-cache.outputs.cache-hit }} - packages-ios-x64-cache-hit: ${{ steps.ios-x64-cache.outputs.cache-hit }} - packages-ios-arm64-cache-hit: ${{ steps.ios-arm64-cache.outputs.cache-hit }} - jni-swig-stub-cache-hit: ${{ steps.jni-swig-stub-cache.outputs.cache-hit }} - jni-linux-lib-cache-hit: ${{ steps.jni-linux-lib-cache.outputs.cache-hit }} - jni-macos-lib-cache-hit: ${{ steps.jni-macos-lib-cache.outputs.cache-hit }} - jni-windows-lib-cache-hit: ${{ steps.jni-windows-lib-cache.outputs.cache-hit }} packages-sha: ${{ steps.packages-cache-key.outputs.sha }} benchmarks-sha: ${{ steps.calculate-benchmarks-cache-key.outputs.sha }} - core-commit-sha: ${{ steps.calculate-core-commmit-sha.outputs.commit }} + core-commit-sha: ${{ steps.calculate-core-versioncommmit-sha.outputs.commit }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" @@ -88,6 +74,7 @@ jobs: id: find-library-version run: | version=$(grep "const val version" buildSrc/src/main/kotlin/Config.kt | cut -d \" -f2) + echo "Label: $version" echo "label=$version" >> $GITHUB_OUTPUT # This also include changes to Realm Core as they are hashed as part of `/packages/external/core` @@ -104,349 +91,341 @@ jobs: working-directory: packages/external/core run: echo "commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - # - # For each specific package we need to perform 3 steps: - # - # 1. Check if a cache is available and download it if it is. - # 2. If (1), store this cache as an artifact for jobs downstream to use. - # 3. Cleanup the build folder. This is required so we can download the next - # platform into a fresh cache location. It does not look possible to download - # a cache into a different location. - # - # TODO There doesn't seem to be a good way to check if a cache key exists without download it. - # https://github.com/actions/cache/issues/321 - # - # TODO Create a custom action for this until we have a work-around? - # - - # - # Kotlin Metadata and Gradle/Compiler Plugin - # + # + # Kotlin Metadata and Gradle/Compiler Plugin + # + metadata-compiler-plugin: + runs-on: ubuntu-latest + name: Metadata & Compiler plugin + needs: checksum + outputs: + packages-metadata-cache-hit: ${{ steps.kotlin-metadata-cache.outputs.cache-hit }} + steps: - name: Check Kotlin Metadata cache id: kotlin-metadata-cache - uses: cmelchior/cache@main + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo - key: packages-m2-metadata-${{ steps.packages-cache-key.outputs.sha }} + key: packages-m2-metadata-${{ needs.checksum.outputs.packages-sha }} - name: Save Kotlin Metadata packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.kotlin-metadata-cache.outputs.cache-hit == 'true' with: - name: packages-metadata-${{ steps.find-library-version.outputs.label }} + name: packages-metadata-${{ needs.checksum.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* retention-days: 1 - - name: Delete downloaded JVM cache files - id: delete-cache-metadata - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.kotlin-metadata-cache.outputs.cache-hit == 'true' - with: - path: ./packages/build/m2-buildrepo + # + # JVM (All platforms) + # + jvm-all: + runs-on: ubuntu-latest + needs: checksum + name: JVM (All platforms) + outputs: + packages-jvm-cache-hit: ${{ steps.jvm-cache.outputs.cache-hit }} - # - # JVM (All platforms) - # + steps: - name: Check JVM cache id: jvm-cache - uses: cmelchior/cache@main + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo - key: packages-m2-jvm-sync-${{ steps.packages-cache-key.outputs.sha }} + key: packages-m2-jvm-sync-${{ needs.checksum.outputs.packages-sha }} - name: Save JVM packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.jvm-cache.outputs.cache-hit == 'true' with: - name: packages-jvm-${{ steps.find-library-version.outputs.label }} + name: packages-jvm-${{ needs.checksum.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* retention-days: 1 - - name: Delete downloaded JVM cache files - id: delete-cache-jvm - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.jvm-cache.outputs.cache-hit == 'true' - with: - path: ./packages/build/m2-buildrepo - - # - # JNI Stub (JVM) - # + # + # JNI Stub (JVM) + # + jni-stub: + runs-on: ubuntu-latest + needs: checksum + name: JNI Stub (JVM) + outputs: + jni-swig-stub-cache-hit: ${{ steps.jni-swig-stub-cache.outputs.cache-hit }} + + steps: - name: Check JNI Swig stub cache id: jni-swig-stub-cache - uses: cmelchior/cache@main + uses: actions/cache@v4 with: path: ./packages/jni-swig-stub/build/generated/sources/jni - key: jni-swig-stubs-${{ steps.packages-cache-key.outputs.sha }} + key: jni-swig-stubs-${{ needs.checksum.outputs.packages-sha }} - name: Save JNI Stub packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.jni-swig-stub-cache.outputs.cache-hit == 'true' with: - name: jni-stub-${{ steps.find-library-version.outputs.label }} + name: jni-stub-${{ needs.checksum.outputs.version-label }} path: ./packages/jni-swig-stub/build/generated/sources/jni/* retention-days: 1 - - name: Delete downloaded JVM cache files - id: delete-cache-jni-stub - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.jni-swig-stub-cache.outputs.cache-hit == 'true' - with: - path: ./packages/jni-swig-stub/build/generated/sources/jni + # + # JNI MacOS Lib + # + jni-macos-lib: + runs-on: ubuntu-latest + needs: checksum + name: JNI MacOS Lib + outputs: + jni-macos-lib-cache-hit: ${{ steps.jni-macos-lib-cache.outputs.cache-hit }} - # - # JNI MacOS Lib - # + steps: - name: Check JNI MacOS lib cache id: jni-macos-lib-cache - uses: cmelchior/cache@main + uses: actions/cache@v4 with: path: ./packages/cinterop/build/realmMacOsBuild - key: jni-macos-lib-${{ steps.packages-cache-key.outputs.sha }} - + key: jni-macos-lib-${{ needs.checksum.outputs.packages-sha }} - name: Save JNI MacOS lib package - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.jni-macos-lib-cache.outputs.cache-hit == 'true' with: - name: jni-macos-lib-${{ steps.find-library-version.outputs.label }} + name: jni-macos-lib-${{ needs.checksum.outputs.version-label }} path: ./packages/cinterop/build/realmMacOsBuild/**/* retention-days: 1 - - name: Delete downloaded JVM cache files - id: delete-cache-macos-lib - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.jni-macos-lib-cache.outputs.cache-hit == 'true' - with: - path: ./packages/cinterop/build/realmMacOsBuild + # + # JNI Linux Lib + # + jni-linux-lib: + runs-on: ubuntu-latest + needs: checksum + name: JNI Linux Lib + outputs: + jni-linux-lib-cache-hit: ${{ steps.jni-linux-lib-cache.outputs.cache-hit }} - # - # JNI Linux Lib - # + steps: - name: Check JNI Linux lib cache id: jni-linux-lib-cache - uses: cmelchior/cache@main + uses: actions/cache@v4 with: path: ./packages/cinterop/build/realmLinuxBuild - key: jni-linux-lib-${{ steps.packages-cache-key.outputs.sha }} + key: jni-linux-lib-${{ needs.checksum.outputs.packages-sha }} - name: Save JNI Linux lib package - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.jni-linux-lib-cache.outputs.cache-hit == 'true' with: - name: jni-linux-lib-${{ steps.find-library-version.outputs.label }} + name: jni-linux-lib-${{ needs.checksum.outputs.version-label }} path: ./packages/cinterop/build/realmLinuxBuild/**/* retention-days: 1 - - name: Delete downloaded JVM cache files - id: delete-cache-linux-lib - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.jni-linux-lib-cache.outputs.cache-hit == 'true' + # + # JNI Windows Lib + # + jni-windows-lib: + runs-on: ubuntu-latest + needs: checksum + name: JNI Windows Lib + outputs: + jni-windows-lib-cache-hit: ${{ steps.jni-windows-lib-cache.outputs.cache-hit }} + steps: + - name: Check JNI Windows lib cache + id: jni-windows-lib-cache + uses: actions/cache@v4 with: - path: ./packages/cinterop/build/realmLinuxBuild + path: ./packages/cinterop/build/realmWindowsBuild + key: jni-windows-lib-${{ needs.checksum.outputs.packages-sha }} + enableCrossOsArchive: true + + - name: Save JNI Windows lib package + uses: actions/upload-artifact@v4 + if: always() && !cancelled() && steps.jni-windows-lib-cache.outputs.cache-hit == 'true' + with: + name: jni-windows-lib-${{ needs.checksum.outputs.version-label }} + path: ./packages/cinterop/build/realmWindowsBuild/Release/realmc.dll + retention-days: 1 + # + # Android + # + android: + runs-on: ubuntu-latest + needs: checksum + name: Android + outputs: + packages-android-cache-hit: ${{ steps.android-cache.outputs.cache-hit }} - # - # Android - # + steps: - name: Check Android cache id: android-cache - uses: cmelchior/cache@main + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo - key: packages-m2-android-sync-${{ steps.packages-cache-key.outputs.sha }} + key: packages-m2-android-sync-${{ needs.checksum.outputs.packages-sha }} - name: Save Android packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.android-cache.outputs.cache-hit == 'true' with: - name: packages-android-${{ steps.find-library-version.outputs.label }} + name: packages-android-${{ needs.checksum.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* retention-days: 1 - - name: Delete downloaded Android cache files - id: delete-cache-android - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.android-cache.outputs.cache-hit == 'true' - with: - path: ./packages/build/m2-buildrepo + # + # Android Base Test APK + # + android-test-base: + runs-on: ubuntu-latest + needs: checksum + name: Android Base Test APK + outputs: + android-test-base-apk-cache-hit: ${{ steps.android-test-base-apk.outputs.cache-hit }} - # - # Android Base Test APK - # + steps: - name: Check Android Base Test APK id: android-test-base-apk - uses: cmelchior/cache@main + uses: actions/cache@v4 with: path: ./packages/test-base/build/outputs/apk/androidTest/debug/test-base-debug-androidTest.apk - key: android-base-test-apk-key-${{ steps.packages-cache-key.outputs.sha }} - + key: android-base-test-apk-key-${{ needs.checksum.outputs.packages-sha }} - name: Save Android Base Test APK - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.android-test-base-apk.outputs.cache-hit == 'true' with: - name: android-base-test-apk-${{ steps.find-library-version.outputs.label }} + name: android-base-test-apk-${{ needs.checksum.outputs.version-label }} path: ./packages/test-base/build/outputs/apk/androidTest/debug/test-base-debug-androidTest.apk retention-days: 1 - - name: Delete Android Base Test APK cache files - id: delete-cache-android-base-test-apk - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.android-test-base-apk.outputs.cache-hit == 'true' - with: - path: ./packages/test-base/build/outputs/apk/androidTest/debug/test-base-debug-androidTest.apk + # + # Android Sync Test APK + # + android-test-sync: + runs-on: ubuntu-latest + needs: checksum + name: Android Sync Test APK + outputs: + android-test-sync-apk-cache-hit: ${{ steps.android-test-sync-apk.outputs.cache-hit }} - # - # Android Sync Test APK - # + steps: - name: Check Android Sync Test APK id: android-test-sync-apk - uses: cmelchior/cache@main + uses: actions/cache@v4 with: - key: android-sync-test-apk-key-${{ steps.packages-cache-key.outputs.sha }} + key: android-sync-test-apk-key-${{ needs.checksum.outputs.packages-sha }} path: | ./packages/test-sync/build/outputs/apk/androidTest/debug/test-sync-debug-androidTest.apk ./packages/test-sync/build/outputs/apk/debug/test-sync-debug.apk - name: Save Android Sync Test APK - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.android-test-sync-apk.outputs.cache-hit == 'true' with: - name: android-sync-test-apk-${{ steps.find-library-version.outputs.label }} + name: android-sync-test-apk-${{ needs.checksum.outputs.version-label }} retention-days: 1 path: | ./packages/test-sync/build/outputs/apk/androidTest/debug/test-sync-debug-androidTest.apk ./packages/test-sync/build/outputs/apk/debug/test-sync-debug.apk - - name: Delete Android Sync Test APK cache files - id: delete-cache-android-sync-test-apk - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.android-test-sync-apk.outputs.cache-hit == 'true' - with: - path: | - ./packages/test-sync/build/outputs/apk/androidTest/debug/test-sync-debug-androidTest.apk - ./packages/test-sync/build/outputs/apk/debug/test-sync-debug.apk + # + # MacOS arm64 + # + macos-arm: + runs-on: ubuntu-latest + needs: checksum + name: MacOS arm64 + outputs: + packages-macos-arm64-cache-hit: ${{ steps.macos-arm64-cache.outputs.cache-hit }} - # - # MacOS arm64 - # + steps: - name: Check MacOS arm64 cache id: macos-arm64-cache - uses: cmelchior/cache@main + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo - key: packages-m2-macos-arm64-sync-${{ steps.packages-cache-key.outputs.sha }} + key: packages-m2-macos-arm64-sync-${{ needs.checksum.outputs.packages-sha }} - name: Save MacOS arm64 packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.macos-arm64-cache.outputs.cache-hit == 'true' with: - name: packages-macos-arm64-${{ steps.find-library-version.outputs.label }} + name: packages-macos-arm64-${{ needs.checksum.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* retention-days: 1 - - name: Delete downloaded MacOS arm64 cache files - id: delete-cache-macos-arm64 - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.macos-arm64-cache.outputs.cache-hit == 'true' - with: - path: ./packages/build/m2-buildrepo + # + # MacOS x64 + # + macos-x64: + runs-on: ubuntu-latest + needs: checksum + name: MacOS x64 + outputs: + packages-macos-x64-cache-hit: ${{ steps.macos-x64-cache.outputs.cache-hit }} - # - # MacOS x64 - # + steps: - name: Check MacOS X64 cache id: macos-x64-cache - uses: cmelchior/cache@main + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo - key: packages-m2-macos-x64-sync-${{ steps.packages-cache-key.outputs.sha }} + key: packages-m2-macos-x64-sync-${{ needs.checksum.outputs.packages-sha }} - name: Save MacOS x64 packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.macos-x64-cache.outputs.cache-hit == 'true' with: - name: packages-macos-x64-${{ steps.find-library-version.outputs.label }} + name: packages-macos-x64-${{ needs.checksum.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* retention-days: 1 - - name: Delete downloaded MacOS x64 cache files - id: delete-cache-macos-x64 - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.macos-x64-cache.outputs.cache-hit == 'true' - with: - path: ./packages/build/m2-buildrepo - # - # iOS arm64 - # + # + # iOS arm64 + # + ios-arm: + runs-on: ubuntu-latest + needs: checksum + name: iOS arm64 + outputs: + packages-ios-arm64-cache-hit: ${{ steps.ios-arm64-cache.outputs.cache-hit }} + + steps: - name: Check iOS arm64 cache id: ios-arm64-cache - uses: cmelchior/cache@main + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo - key: packages-m2-ios-arm64-sync-${{ steps.packages-cache-key.outputs.sha }} + key: packages-m2-ios-arm64-sync-${{ needs.checksum.outputs.packages-sha }} - name: Save iOS arm64 packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.ios-arm64-cache.outputs.cache-hit == 'true' with: - name: packages-ios-arm64-${{ steps.find-library-version.outputs.label }} + name: packages-ios-arm64-${{ needs.checksum.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* retention-days: 1 - - name: Delete downloaded iOS arm64 cache files - id: delete-cache-ios-arm64 - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.ios-arm64-cache.outputs.cache-hit == 'true' - with: - path: ./packages/build/m2-buildrepo + # + # iOS x64 + # + ios-x64: + runs-on: ubuntu-latest + needs: checksum + name: iOS x64 + outputs: + packages-ios-x64-cache-hit: ${{ steps.ios-x64-cache.outputs.cache-hit }} - # - # iOS x64 - # + steps: - name: Check iOS X64 cache id: ios-x64-cache - uses: cmelchior/cache@main + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo - key: packages-m2-ios-x64-sync-${{ steps.packages-cache-key.outputs.sha }} + key: packages-m2-ios-x64-sync-${{ needs.checksum.outputs.packages-sha }} - name: Save iOS x64 packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() && !cancelled() && steps.ios-x64-cache.outputs.cache-hit == 'true' with: - name: packages-ios-x64-${{ steps.find-library-version.outputs.label }} + name: packages-ios-x64-${{ needs.checksum.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* retention-days: 1 - - - name: Delete downloaded iOS x64 cache files - id: delete-cache-ios-x64 - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.ios-x64-cache.outputs.cache-hit == 'true' - with: - path: ./packages/build/m2-buildrepo - - # - # JNI Windows Lib - # - - name: Check JNI Windows lib cache - id: jni-windows-lib-cache - uses: cmelchior/cache@main - with: - path: ./packages/cinterop/build/realmWindowsBuild - key: jni-windows-lib-${{ steps.packages-cache-key.outputs.sha }} - enableCrossOsArchive: true - - - name: Save JNI Windows lib package - uses: actions/upload-artifact@v3 - if: always() && !cancelled() && steps.jni-windows-lib-cache.outputs.cache-hit == 'true' - with: - name: jni-windows-lib-${{ steps.find-library-version.outputs.label }} - path: ./packages/cinterop/build/realmWindowsBuild/Release/realmc.dll - retention-days: 1 - - - name: Delete downloaded JNI Windows lib cache files - id: delete-cache-windows-lib - uses: JesseTG/rm@v1.0.3 - if: always() && !cancelled() && steps.jni-windows-lib-cache.outputs.cache-hit == 'true' - with: - path: ./packages/cinterop/build/realmWindowsBuild diff --git a/.github/workflows/include-deploy-release.yml b/.github/workflows/include-deploy-release.yml index 4dc1d2d163..7e5bd70819 100644 --- a/.github/workflows/include-deploy-release.yml +++ b/.github/workflows/include-deploy-release.yml @@ -18,33 +18,33 @@ jobs: steps: - name: git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2 with: cmake-version: ${{ vars.VERSION_CMAKE }} - name: Setup ninja - uses: cmelchior/setup-ninja@master + uses: clementetb/setup-ninja@master with: version: ${{ vars.VERSION_NINJA }} - name: Install ccache - uses: hendrikmuhs/ccache-action@v1.2.2 + uses: hendrikmuhs/ccache-action@v1.2.13 with: key: 'jvm-package' max-size: '2.0G' @@ -71,31 +71,31 @@ jobs: # We cannot use artifacts as they cannot be shared between workflows, so use cache instead. - name: Setup build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo key: packages-m2-jvm-sync-${{ inputs.packages-sha-label }} - name: Restore Linux JNI lib - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jni-linux-lib-${{ inputs.version-label }} path: ./packages/cinterop/build/realmLinuxBuild - name: Restore Windows JNI lib - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jni-windows-lib-${{ inputs.version-label }} path: ./packages/cinterop/build/realmWindowsBuild/Release - name: Restore MacOS JNI lib - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jni-macos-lib-${{ inputs.version-label }} path: ./packages/cinterop/build/realmMacOsBuild - name: Restore m2-buildrepo - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: all-packages-${{ inputs.version-label }} path: ./packages/build/m2-buildrepo diff --git a/.github/workflows/include-deploy-snapshot.yml b/.github/workflows/include-deploy-snapshot.yml index d50f9abbdc..83dbf0fbd3 100644 --- a/.github/workflows/include-deploy-snapshot.yml +++ b/.github/workflows/include-deploy-snapshot.yml @@ -19,12 +19,12 @@ jobs: sudo rm -rf "$AGENT_TOOLSDIRECTORY" - name: git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} @@ -35,7 +35,7 @@ jobs: version: ${{ vars.VERSION_KOTLIN_COMMANDLINE_TOOLS }} - name: Restore m2-buildrepo - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: all-packages-${{ inputs.version-label }} path: ./packages/build/m2-buildrepo diff --git a/.github/workflows/include-integration-tests.yml b/.github/workflows/include-integration-tests.yml index 6f23b27bbb..2d3349ec8e 100644 --- a/.github/workflows/include-integration-tests.yml +++ b/.github/workflows/include-integration-tests.yml @@ -19,21 +19,21 @@ jobs: # runs-on: macos-12 # steps: # - name: Checkout code - # uses: actions/checkout@v3 + # uses: actions/checkout@v4 # - name: Setup Java 11 - # uses: actions/setup-java@v3 + # uses: actions/setup-java@v4 # with: # distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} # java-version: 11 # - name: Setup Gradle and task/dependency caching - # uses: gradle/gradle-build-action@v2 + # uses: gradle/actions/setup-gradle@v3 # with: # cache-read-only: false # - name: Restore m2-buildrepo - # uses: actions/download-artifact@v3 + # uses: actions/download-artifact@v4 # with: # name: all-packages-${{ needs.check-cache.outputs.version-label }} # path: ./packages/build/m2-buildrepo @@ -60,21 +60,21 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Restore m2-buildrepo - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: all-packages-${{ inputs.version-label }} path: ./packages/build/m2-buildrepo @@ -90,27 +90,27 @@ jobs: runs-on: macos-12 steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Restore m2-buildrepo - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: all-packages-${{ inputs.version-label }} path: ./packages/build/m2-buildrepo # - name: AVD cache - # uses: actions/cache@v3 + # uses: actions/cache@v4 # id: avd-cache # with: # path: | @@ -171,24 +171,24 @@ jobs: build-benchmarks: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Restore m2-buildrepo - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: all-packages-${{ inputs.version-label }} path: ./packages/build/m2-buildrepo @@ -213,30 +213,30 @@ jobs: arguments: integrationTest runs-on: macos-12 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Restore m2-buildrepo - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: all-packages-${{ inputs.version-label }} path: ./packages/build/m2-buildrepo # - name: AVD cache - # uses: actions/cache@v3 + # uses: actions/cache@v4 # id: avd-cache # with: # path: | @@ -294,30 +294,30 @@ jobs: arguments: integrationTest runs-on: macos-12 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: 17 - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Restore m2-buildrepo - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: all-packages-${{ inputs.version-label }} path: ./packages/build/m2-buildrepo # - name: AVD cache - # uses: actions/cache@v3 + # uses: actions/cache@v4 # id: avd-cache # with: # path: | diff --git a/.github/workflows/include-static-analysis.yml b/.github/workflows/include-static-analysis.yml index 4f456ecae0..e30fd81978 100644 --- a/.github/workflows/include-static-analysis.yml +++ b/.github/workflows/include-static-analysis.yml @@ -9,17 +9,21 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" + - name: Register problem matchers + run: |- + echo "::add-matcher::.github/problem-matchers/ktlint.json" + - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false # TODO How to configure caching here? - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} @@ -29,9 +33,9 @@ jobs: - name: Run Ktlint run: ./gradlew ktlintCheck + continue-on-error: true - name: Stash Ktlint results - if: always() run: | rm -rf /tmp/ktlint rm -rf /tmp/detekt @@ -45,30 +49,33 @@ jobs: rsync -a --delete --ignore-errors packages/gradle-plugin/build/reports/ktlint/ /tmp/ktlint/plugin-gradle/ || true rsync -a --delete --ignore-errors benchmarks/build/reports/ktlint/ /tmp/ktlint/benchmarks/ || true - - name: Publish Ktlint results - uses: cmelchior/checkstyle-github-action@master - if: always() + - name: Publish Ktlint results + uses: actions/upload-artifact@v4 with: - name: Ktlint Results - title: Ktlint Analyzer report - path: '/tmp/ktlint/**/*.xml' + name: Ktlint Analyzer report + path: /tmp/ktlint/* + retention-days: 1 detekt: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" + - name: Register problem matchers + run: |- + echo "::add-matcher::.github/problem-matchers/detekt.json" + - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false # TODO How to configure caching here? - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2 with: cmake-version: ${{ vars.VERSION_CMAKE }} @@ -78,9 +85,9 @@ jobs: - name: Run Detekt run: ./gradlew detekt + continue-on-error: true - name: Stash Detekt results - if: always() run: | rm -rf /tmp/detekt mkdir /tmp/detekt @@ -93,9 +100,8 @@ jobs: rsync -a --delete --ignore-errors benchmarks/build/reports/detekt/ /tmp/detekt/benchmarks/ || true - name: Publish Detekt results - uses: cmelchior/checkstyle-github-action@master - if: always() + uses: actions/upload-artifact@v4 with: - name: Detekt Results - title: Detekt Analyzer report - path: '/tmp/detekt/**/*.xml' + name: Detekt Analyzer report + path: /tmp/detekt/* + retention-days: 1 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index cf1ec975d8..98249ff7c4 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -37,29 +37,33 @@ jobs: if: always() && !cancelled() && needs.check-cache.outputs.jni-swig-stub-cache-hit != 'true' steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" + - name: Register problem matchers + run: |- + echo "::add-matcher::.github/problem-matchers/kotlin.json" + - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Load build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/jni-swig-stub/build/generated/sources/jni key: jni-swig-stubs-${{ needs.check-cache.outputs.packages-sha }} - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} @@ -86,7 +90,7 @@ jobs: run: ./gradlew :jni-swig-stub:assemble -Prealm.kotlin.buildRealmCore=false -Prealm.kotlin.mainHost=false - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: jni-stub-${{ needs.check-cache.outputs.version-label }} path: ./packages/jni-swig-stub/build/generated/sources/jni/* @@ -104,29 +108,33 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" + - name: Register problem matchers + run: |- + echo "::add-matcher::.github/problem-matchers/kotlin.json" + - name: Setup build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/cinterop/build/realmLinuxBuild key: jni-linux-lib-${{ needs.check-cache.outputs.packages-sha }} - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} - name: Restore JNI Swig Stubs - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jni-stub-${{ needs.check-cache.outputs.version-label }} path: ./packages/jni-swig-stub/build/generated/sources/jni - name: Build Docker image - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: tags: jvm-native-lib-linux:latest file: ./packages/cinterop/src/jvmMain/generic.Dockerfile @@ -153,7 +161,7 @@ jobs: make -j8 - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: jni-linux-lib-${{ needs.check-cache.outputs.version-label }} path: ./packages/cinterop/build/realmLinuxBuild/librealmc.so @@ -171,7 +179,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # TODO See https://github.com/microsoft/vcpkg/issues/25349 which might describe the error here https://github.com/realm/realm-kotlin/runs/8099890473?check_suite_focus=true # -- Building for: Visual Studio 17 2022 @@ -185,19 +193,19 @@ jobs: submodules: "recursive" - name: Setup build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/cinterop/build/realmWindowsBuild key: jni-windows-lib-${{ needs.check-cache.outputs.packages-sha }} enableCrossOsArchive: true - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} - name: Restore JNI Swig Stubs - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jni-stub-${{ needs.check-cache.outputs.version-label }} path: ${{ github.workspace }}/packages/jni-swig-stub/build/generated/sources/jni @@ -222,7 +230,7 @@ jobs: cmake --build . --config Release - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: jni-windows-lib-${{ needs.check-cache.outputs.version-label }} path: ./packages/cinterop/build/realmWindowsBuild/Release/realmc.dll @@ -240,33 +248,33 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} - name: Setup ninja - uses: cmelchior/setup-ninja@master + uses: clementetb/setup-ninja@master with: version: ${{ vars.VERSION_NINJA }} - name: Install ccache - uses: hendrikmuhs/ccache-action@v1.2.2 + uses: hendrikmuhs/ccache-action@v1.2.13 with: key: 'jvm-macos-native-lib' max-size: '2.0G' @@ -292,13 +300,13 @@ jobs: # We cannot use artifacts as they cannot be shared between workflows, so use cache instead. - name: Setup build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/cinterop/build/realmMacOsBuild key: jni-macos-lib-${{ needs.check-cache.outputs.packages-sha }} - name: Restore JNI Swig Stubs - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jni-stub-${{ needs.check-cache.outputs.version-label }} path: ${{ github.workspace }}/packages/jni-swig-stub/build/generated/sources/jni @@ -307,8 +315,12 @@ jobs: working-directory: packages run: ./gradlew buildJVMSharedLibs -Prealm.kotlin.mainHost=false + - name: Show ccache size + run: | + echo `du -sh ~/.ccache` + - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: jni-macos-lib-${{ needs.check-cache.outputs.version-label }} path: ./packages/cinterop/build/realmMacOsBuild/librealmc.dylib @@ -328,33 +340,37 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" + - name: Register problem matchers + run: |- + echo "::add-matcher::.github/problem-matchers/kotlin.json" + - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} - name: Setup ninja - uses: cmelchior/setup-ninja@master + uses: clementetb/setup-ninja@master with: version: ${{ vars.VERSION_NINJA }} - name: Install ccache - uses: hendrikmuhs/ccache-action@v1.2.2 + uses: hendrikmuhs/ccache-action@v1.2.13 with: key: 'metadata-package' max-size: '2.0G' @@ -377,7 +393,7 @@ jobs: ndk-version: r23c - name: Setup build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo key: packages-m2-metadata-${{ needs.check-cache.outputs.packages-sha }} @@ -387,7 +403,7 @@ jobs: run: ./gradlew publishCIPackages -Prealm.kotlin.targets=gradlePlugin,compilerPlugin -Prealm.kotlin.buildRealmCore=false -Prealm.kotlin.mainHost=true - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packages-metadata-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* @@ -410,33 +426,37 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" + - name: Register problem matchers + run: |- + echo "::add-matcher::.github/problem-matchers/kotlin.json" + - name: Setup Java 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: '17' - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} - name: Setup ninja - uses: cmelchior/setup-ninja@master + uses: clementetb/setup-ninja@master with: version: ${{ vars.VERSION_NINJA }} - name: Install ccache - uses: hendrikmuhs/ccache-action@v1.2.2 + uses: hendrikmuhs/ccache-action@v1.2.13 with: key: 'jvm-package' max-size: '2.0G' @@ -463,25 +483,25 @@ jobs: # We cannot use artifacts as they cannot be shared between workflows, so use cache instead. - name: Setup build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo key: packages-m2-jvm-sync-${{ needs.check-cache.outputs.packages-sha }} - name: Restore Linux JNI lib - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jni-linux-lib-${{ needs.check-cache.outputs.version-label }} path: ./packages/cinterop/build/realmLinuxBuild - name: Restore Windows JNI lib - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jni-windows-lib-${{ needs.check-cache.outputs.version-label }} path: ./packages/cinterop/build/realmWindowsBuild/Release - name: Restore MacOS JNI lib - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: jni-macos-lib-${{ needs.check-cache.outputs.version-label }} path: ./packages/cinterop/build/realmMacOsBuild @@ -490,8 +510,12 @@ jobs: working-directory: packages run: ./gradlew publishCIPackages -Prealm.kotlin.targets=jvm -Prealm.kotlin.buildRealmCore=false -Prealm.kotlin.copyNativeJvmLibs=linux,windows,macos -Prealm.kotlin.mainHost=false + - name: Show ccache size + run: | + echo `du -sh ~/.ccache` + - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packages-jvm-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* @@ -518,18 +542,18 @@ jobs: sudo rm -rf "$AGENT_TOOLSDIRECTORY" - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false @@ -554,17 +578,17 @@ jobs: run: brew install jq - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} - name: Setup ninja - uses: cmelchior/setup-ninja@master + uses: clementetb/setup-ninja@master with: version: ${{ vars.VERSION_NINJA }} - name: Install ccache - uses: hendrikmuhs/ccache-action@v1.2.2 + uses: hendrikmuhs/ccache-action@v1.2.13 with: key: 'android-package' max-size: '2.0G' @@ -599,20 +623,20 @@ jobs: run: ./gradlew publishCIPackages -Prealm.kotlin.targets=android -Prealm.kotlin.buildRealmCore=false -Prealm.kotlin.mainHost=false - name: Store build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo key: packages-m2-android-sync-${{ needs.check-cache.outputs.packages-sha }} - name: Store build cache for Android Test APK - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/test-base/build/outputs/apk/androidTest/debug/test-base-debug-androidTest.apk key: android-base-test-apk-key-${{ needs.check-cache.outputs.packages-sha }} # Must match naming found in include-check-cache.yml - name: Store build cache for Android Sync Test APK - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: android-sync-test-apk-key-${{ needs.check-cache.outputs.packages-sha }} path: | @@ -622,21 +646,21 @@ jobs: # Must match naming found in include-check-cache.yml # Must match naming found in include-check-cache.yml - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packages-android-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* retention-days: 1 - name: Upload Android Base Test APK - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: android-base-test-apk-${{ needs.check-cache.outputs.version-label }} path: ./packages/test-base/build/outputs/apk/androidTest/debug/test-base-debug-androidTest.apk retention-days: 1 - name: Upload Android Sync Test APK - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: android-sync-test-apk-${{ needs.check-cache.outputs.version-label }} retention-days: 1 @@ -653,33 +677,33 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} - name: Setup ninja - uses: cmelchior/setup-ninja@master + uses: clementetb/setup-ninja@master with: version: ${{ vars.VERSION_NINJA }} - name: Install ccache - uses: hendrikmuhs/ccache-action@v1.2.2 + uses: hendrikmuhs/ccache-action@v1.2.13 with: key: 'macos-x64-package' max-size: '2.0G' @@ -702,16 +726,20 @@ jobs: working-directory: packages run: ./gradlew publishCIPackages -Prealm.kotlin.targets=macosX64 -Prealm.kotlin.mainHost=false + - name: Show ccache size + run: | + echo `du -sh ~/.ccache` + # We cannot use artifacts as they cannot be shared between workflows, so use cache instead. - name: Store build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo key: packages-m2-macos-x64-sync-${{ needs.check-cache.outputs.packages-sha }} # Must match naming found in include-check-cache.yml - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packages-macos-x64-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* @@ -725,33 +753,33 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} - name: Setup ninja - uses: cmelchior/setup-ninja@master + uses: clementetb/setup-ninja@master with: version: ${{ vars.VERSION_NINJA }} - name: Install ccache - uses: hendrikmuhs/ccache-action@v1.2.2 + uses: hendrikmuhs/ccache-action@v1.2.13 with: key: 'macos-arm64-package' max-size: '2.0G' @@ -775,14 +803,14 @@ jobs: run: ./gradlew publishCIPackages -Prealm.kotlin.targets=macosArm64 -Prealm.kotlin.mainHost=false - name: Store build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo key: packages-m2-macos-arm64-sync-${{ needs.check-cache.outputs.packages-sha }} # Must match naming found in include-check-cache.yml - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packages-macos-arm64-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* @@ -796,33 +824,33 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} - name: Setup ninja - uses: cmelchior/setup-ninja@master + uses: clementetb/setup-ninja@master with: version: ${{ vars.VERSION_NINJA }} - name: Install ccache - uses: hendrikmuhs/ccache-action@v1.2.2 + uses: hendrikmuhs/ccache-action@v1.2.13 with: key: 'ios-x64-package' max-size: '2.0G' @@ -847,14 +875,14 @@ jobs: # We cannot use artifacts as they cannot be shared between workflows, so use cache instead. - name: Store build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo key: packages-m2-ios-x64-sync-${{ needs.check-cache.outputs.packages-sha }} # Must match naming found in include-check-cache.yml - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packages-ios-x64-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* @@ -868,33 +896,33 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v2.0.2 with: cmake-version: ${{ vars.VERSION_CMAKE }} - name: Setup ninja - uses: cmelchior/setup-ninja@master + uses: clementetb/setup-ninja@master with: version: ${{ vars.VERSION_NINJA }} - name: Install ccache - uses: hendrikmuhs/ccache-action@v1.2.2 + uses: hendrikmuhs/ccache-action@v1.2.13 with: key: 'ios-arm64-package' max-size: '2.0G' @@ -919,14 +947,14 @@ jobs: # We cannot use artifacts as they cannot be shared between workflows, so use cache instead. - name: Store build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./packages/build/m2-buildrepo key: packages-m2-ios-arm64-sync-${{ needs.check-cache.outputs.packages-sha }} # Must match naming found in include-check-cache.yml - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packages-ios-arm64-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo/**/* @@ -962,7 +990,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" @@ -988,30 +1016,30 @@ jobs: echo "baas_container_id=$OUTPUT" >> $GITHUB_OUTPUT - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Restore Kotlin metadata artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-metadata-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Restore m2-buildrepo (Android) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-android-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Restore m2-buildrepo (JVM) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-jvm-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo @@ -1048,7 +1076,7 @@ jobs: cd packages && ./gradlew :test-${{ matrix.type }}:connectedCheck -PsyncUsePlatformNetworking=true -PsyncTestUrl=${{ steps.baas_cli_poll.outputs.baas_container_hostname }} -PincludeSdkModules=false --info --no-daemon - name: Archive LogCat data - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() || failure() with: name: logcat-${{ matrix.type }}-emulator.txt @@ -1091,16 +1119,16 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Restore Android Test APK - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: android-base-test-apk-${{ needs.check-cache.outputs.version-label }} path: ./packages/test-base/build/outputs/apk/androidTest/debug - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_DEVICEFARM_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_DEVICEFARM_SECRET_ACCESS_KEY }} @@ -1128,7 +1156,7 @@ jobs: !contains(needs.*.result, 'cancelled') steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # checkout BAAS CLI repo - name: Checkout BAAS repo @@ -1148,13 +1176,13 @@ jobs: echo "baas_container_id=$OUTPUT" >> $GITHUB_OUTPUT - name: Restore Android Sync Test APK - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: android-sync-test-apk-${{ needs.check-cache.outputs.version-label }} path: ./packages/test-sync/build/outputs/apk/ - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_DEVICEFARM_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_DEVICEFARM_SECRET_ACCESS_KEY }} @@ -1232,7 +1260,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # checkout BAAS CLI repo - name: Checkout BAAS repo @@ -1256,24 +1284,24 @@ jobs: echo "baas_container_id=$OUTPUT" >> $GITHUB_OUTPUT - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Restore m2-buildrepo - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-${{ matrix.package-prefix }}-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Restore Kotlin metadata artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-metadata-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo @@ -1361,7 +1389,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # checkout BAAS CLI repo - name: Checkout BAAS repo @@ -1385,24 +1413,24 @@ jobs: echo "baas_container_id=$OUTPUT" >> $GITHUB_OUTPUT - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Restore m2-buildrepo (ios) - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-ios-${{ matrix.package-prefix }}-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Restore Kotlin metadata artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-metadata-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo @@ -1503,7 +1531,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # checkout BAAS CLI repo - name: Checkout BAAS repo @@ -1527,24 +1555,24 @@ jobs: echo "baas_container_id=$OUTPUT" >> $GITHUB_OUTPUT - name: Setup Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} java-version: ${{ vars.VERSION_JAVA }} - name: Setup Gradle and task/dependency caching - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: cache-read-only: false - name: Restore Kotlin metadata artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-metadata-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Restore m2-buildrepo - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-jvm-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo @@ -1602,54 +1630,54 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 # The Metadata artifact contain broken JVM publications, so it needs to be # restored first, it so they can be overidden with the correct ones. - name: Restore Kotlin metadata artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-metadata-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Restore Android artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-android-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Restore JVM artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-jvm-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Restore MacOS x64 artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-macos-x64-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Restore MacOS arm64 artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-macos-arm64-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Restore iOS x64 artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-ios-x64-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Restore iOS arm64 artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: packages-ios-arm64-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - name: Upload artifacts bundle - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: all-packages-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo @@ -1700,7 +1728,7 @@ jobs: is_publish_build: ${{ steps.check_release.outputs.is_publish_build }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Check if release build id: check_release @@ -1755,4 +1783,4 @@ jobs: secrets: inherit with: version-label: ${{ needs.check-cache.outputs.version-label }} - packages-sha-label: ${{ needs.check-cache.outputs.packages-sha }} + packages-sha-label: ${{ needs.check-cache.outputs.packages-sha }} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/realm-lint.gradle.kts b/buildSrc/src/main/kotlin/realm-lint.gradle.kts index aed516a6ac..3020dfc01a 100644 --- a/buildSrc/src/main/kotlin/realm-lint.gradle.kts +++ b/buildSrc/src/main/kotlin/realm-lint.gradle.kts @@ -66,6 +66,7 @@ allprojects { "!src/**/generated/**", "!src/**/resources/**", "--reporter=plain", + "--reporter=html,output=${project.buildDir}/reports/ktlint/ktlint.html", "--reporter=checkstyle,output=${project.buildDir}/reports/ktlint/ktlint.xml", "--editorconfig=${configDir}/ktlint/.editorconfig" ) From 17e92c222163c0bdefa164c7bd2392e3ccf21f71 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Thu, 4 Jul 2024 10:02:13 +0100 Subject: [PATCH 04/10] Always unpacking JVM native lib when using SNAPSHOT version (#1786) --- CHANGELOG.md | 2 +- .../cinterop/src/jvmMain/kotlin/io/realm/kotlin/jvm/SoLoader.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8514c747d..92524284db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - None. ### Enhancements -- None. +- Improved mechanism for unpacking of JVM native libs suitable for local development. (Issue [#1715](https://github.com/realm/realm-kotlin/issues/1715) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1065)). ### Fixed - None. diff --git a/packages/cinterop/src/jvmMain/kotlin/io/realm/kotlin/jvm/SoLoader.kt b/packages/cinterop/src/jvmMain/kotlin/io/realm/kotlin/jvm/SoLoader.kt index 2bf019be67..e30197adf4 100644 --- a/packages/cinterop/src/jvmMain/kotlin/io/realm/kotlin/jvm/SoLoader.kt +++ b/packages/cinterop/src/jvmMain/kotlin/io/realm/kotlin/jvm/SoLoader.kt @@ -61,7 +61,7 @@ class SoLoader { // path should be /io.realm.kotlin/libraryVersion]/librealmffi.so // if the full path exists then load it otherwise unpack and load it. val libraryInstallationLocation: File = defaultAbsolutePath(libraryName) - if (!libraryInstallationLocation.exists()) { + if (!libraryInstallationLocation.exists() || SDK_VERSION.endsWith("-SNAPSHOT", ignoreCase = true)) { unpackAndInstall(libraryName, libraryInstallationLocation) } @Suppress("UnsafeDynamicallyLoadedCode") From 74e24c2a9f58e7099d2d334cfeafa327740c6e54 Mon Sep 17 00:00:00 2001 From: clementetb Date: Thu, 4 Jul 2024 18:06:21 +0200 Subject: [PATCH 05/10] Remove centos 7 docker image (#1791) --- .github/workflows/pr.yml | 19 +- buildSrc/src/main/kotlin/Config.kt | 4 +- .../cinterop/src/jvmMain/generic.Dockerfile | 31 -- packages/test-sync/build.gradle.kts | 2 + .../io/realm/kotlin/test/mongodb/TestApp.kt | 26 -- .../kotlin/test/mongodb/util/AppAdmin.kt | 21 -- .../test/mongodb/util/AppServicesClient.kt | 288 ++++++++++++++++-- .../test/mongodb/util/SchemaProcessor.kt | 224 ++++++++++++++ .../test/mongodb/util/TestAppInitializer.kt | 111 ++----- .../mongodb/common/SchemaProcessorTests.kt | 70 +++++ 10 files changed, 594 insertions(+), 202 deletions(-) delete mode 100644 packages/cinterop/src/jvmMain/generic.Dockerfile create mode 100644 packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt create mode 100644 packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SchemaProcessorTests.kt diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 4a67c1f0a4..beeb9d53a1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -80,7 +80,6 @@ jobs: cd ~ curl -L ${{ vars.VERSION_SWIG}} > swig.rb && HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true brew install swig.rb - - name: Build JNI Stub working-directory: ./packages run: ./gradlew :jni-swig-stub:assemble -Prealm.kotlin.buildRealmCore=false -Prealm.kotlin.mainHost=false @@ -93,7 +92,7 @@ jobs: retention-days: 1 build-jvm-linux-native-lib: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [check-cache, build-jni-swig-stub] if: | always() && @@ -125,21 +124,9 @@ jobs: name: jni-stub-${{ needs.check-cache.outputs.version-label }} path: ./packages/jni-swig-stub/build/generated/sources/jni - - name: Build Docker image - uses: docker/build-push-action@v3 - with: - tags: jvm-native-lib-linux:latest - file: ./packages/cinterop/src/jvmMain/generic.Dockerfile - push: false - - name: Build native lib - uses: addnab/docker-run-action@v3 - with: - image: jvm-native-lib-linux:latest - shell: bash - options: -v ${{ github.workspace }}:/work - run: | - cd /work/packages/cinterop + working-directory: ./packages/cinterop + run: | mkdir build cd build rm -rf realmLinuxBuild diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index a00c462825..c2a1b9ae59 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -128,12 +128,12 @@ object Versions { const val latestKotlin = "2.0.0" // https://kotlinlang.org/docs/eap.html#build-details const val kotlinCompileTesting = "0.5.0-alpha07" // https://github.com/zacsweers/kotlin-compile-testing const val ktlint = "0.45.2" // https://github.com/pinterest/ktlint - const val ktor = "2.3.7" // https://github.com/ktorio/ktor + const val ktor = "2.3.12" // https://github.com/ktorio/ktor const val multidex = "2.0.1" // https://developer.android.com/jetpack/androidx/releases/multidex const val nexusPublishPlugin = "1.1.0" // https://github.com/gradle-nexus/publish-plugin const val okio = "3.2.0" // https://square.github.io/okio/#releases const val relinker = "1.4.5" // https://github.com/KeepSafe/ReLinker - const val serialization = "1.6.0" // https://kotlinlang.org/docs/releases.html#release-details + const val serialization = "1.7.1" // https://kotlinlang.org/docs/releases.html#release-details const val shadowJar = "6.1.0" // https://mvnrepository.com/artifact/com.github.johnrengelman.shadow/com.github.johnrengelman.shadow.gradle.plugin?repo=gradle-plugins const val snakeYaml = "1.33" // https://github.com/snakeyaml/snakeyaml val sourceCompatibilityVersion = JavaVersion.VERSION_1_8 // Language level of any Java source code. diff --git a/packages/cinterop/src/jvmMain/generic.Dockerfile b/packages/cinterop/src/jvmMain/generic.Dockerfile deleted file mode 100644 index 26da693918..0000000000 --- a/packages/cinterop/src/jvmMain/generic.Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM centos:7 - -# Install EPEL & devtoolset -RUN yum install -y \ - epel-release \ - centos-release-scl-rh \ - && yum-config-manager --enable rhel-server-rhscl-7-rpms - -RUN yum install -y \ - chrpath \ - devtoolset-9 \ - jq \ - libconfig-devel \ - openssh-clients \ - rh-git218 \ - zlib-devel \ - java-1.8.0-openjdk-devel \ - && yum clean all - -ENV PATH /opt/cmake/bin:/opt/rh/rh-git218/root/usr/bin:/opt/rh/devtoolset-9/root/usr/bin:$PATH -ENV LD_LIBRARY_PATH /opt/rh/devtoolset-9/root/usr/lib64:/opt/rh/devtoolset-9/root/usr/lib:/opt/rh/devtoolset-9/root/usr/lib64/dyninst:/opt/rh/devtoolset-9/root/usr/lib/dyninst:/opt/rh/devtoolset-9/root/usr/lib64:/opt/rh/devtoolset-9/root/usr/lib - -RUN mkdir -p /opt/cmake \ - && curl https://cmake.org/files/v3.27/cmake-3.27.7-linux-x86_64.sh -o /cmake.sh \ - && sh /cmake.sh --prefix=/opt/cmake --skip-license \ - && rm /cmake.sh - -RUN mkdir -p /etc/ssh && \ - echo "Host github.com\n\tStrictHostKeyChecking no\n" >> /etc/ssh/ssh_config && \ - ssh-keyscan github.com >> /etc/ssh/ssh_known_hosts - diff --git a/packages/test-sync/build.gradle.kts b/packages/test-sync/build.gradle.kts index 67f7c2d414..6e52dd160a 100644 --- a/packages/test-sync/build.gradle.kts +++ b/packages/test-sync/build.gradle.kts @@ -100,6 +100,8 @@ kotlin { implementation("io.ktor:ktor-serialization-kotlinx-json:${Versions.ktor}") implementation("io.ktor:ktor-client-content-negotiation:${Versions.ktor}") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.serialization}") + implementation("com.squareup.okio:okio:${Versions.okio}") } } diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt index 4af35452e4..ff92ad92bf 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt @@ -19,7 +19,6 @@ package io.realm.kotlin.test.mongodb -import io.realm.kotlin.Realm import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.SynchronizableObject @@ -31,8 +30,6 @@ import io.realm.kotlin.mongodb.AppConfiguration import io.realm.kotlin.mongodb.Credentials import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.internal.AppConfigurationImpl -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA import io.realm.kotlin.test.mongodb.util.AppAdmin import io.realm.kotlin.test.mongodb.util.AppAdminImpl import io.realm.kotlin.test.mongodb.util.AppServicesClient @@ -41,7 +38,6 @@ import io.realm.kotlin.test.mongodb.util.Service import io.realm.kotlin.test.mongodb.util.TestAppInitializer.initializeDefault import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.use import kotlinx.coroutines.CloseableCoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher import org.mongodb.kbson.ExperimentalKBsonSerializerApi @@ -118,28 +114,6 @@ open class TestApp private constructor( ) ) - init { - // For apps with Flexible Sync, we need to bootstrap all the schemas to work around - // https://github.com/realm/realm-core/issues/7297. - // So we create a dummy Realm, upload all the schemas and close the Realm again. - if (app.configuration.appId.startsWith(TEST_APP_FLEX, ignoreCase = false)) { - runBlocking { - val user = app.login(Credentials.anonymous()) - val config = SyncConfiguration.create(user, FLEXIBLE_SYNC_SCHEMA) - try { - Realm.open(config).use { - // Using syncSession.uploadAllLocalChanges() seems to just hang forever. - // This is tracked by the above Core issue. Instead use the Sync Progress - // endpoint to signal when the schemas are ready. - pairAdminApp.second.waitForSyncBootstrap() - } - } finally { - user.delete() - } - } - } - } - fun createUserAndLogin(): User = runBlocking { val (email, password) = TestHelper.randomEmail() to "password1234" emailPasswordAuth.registerUser(email, password).run { diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt index cbef34d0d3..e15939520e 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt @@ -18,9 +18,7 @@ package io.realm.kotlin.test.mongodb.util import io.realm.kotlin.mongodb.sync.SyncMode import io.realm.kotlin.mongodb.sync.SyncSession -import kotlinx.coroutines.delay import kotlinx.serialization.json.JsonObject -import kotlin.time.Duration.Companion.seconds /** * Wrapper around App Services Server Admin functions needed for tests. @@ -103,11 +101,6 @@ interface AppAdmin { */ suspend fun deleteDocuments(database: String, clazz: String, query: String): JsonObject? - /** - * Wait for Sync bootstrap to complete for all model classes. - */ - suspend fun waitForSyncBootstrap() - fun closeClient() } @@ -208,20 +201,6 @@ class AppAdminImpl( app.deleteDocument(database, clazz, query) } - override suspend fun waitForSyncBootstrap() { - baasClient.run { - var limit = 300 - var i = 0 - while (!app.initialSyncComplete() && i < limit) { - delay(1.seconds) - i++ - } - if (!app.initialSyncComplete()) { - throw IllegalStateException("Test server did not finish bootstrapping sync in time: $limit s.") - } - } - } - override fun closeClient() { baasClient.closeClient() } diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt index 58b770d1a5..eb6e02c732 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt @@ -35,19 +35,26 @@ import io.ktor.http.HttpMethod.Companion.Post import io.ktor.http.contentType import io.ktor.http.isSuccess import io.ktor.serialization.kotlinx.json.json +import io.realm.kotlin.internal.interop.PropertyType import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.internal.schema.RealmClassImpl import io.realm.kotlin.mongodb.sync.SyncMode +import io.realm.kotlin.schema.RealmClassKind import io.realm.kotlin.test.mongodb.SyncServerConfig import io.realm.kotlin.test.mongodb.TEST_APP_CLUSTER_NAME -import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA_COUNT import io.realm.kotlin.test.mongodb.util.TestAppInitializer.initialize +import io.realm.kotlin.types.BaseRealmObject import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.delay import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.ClassDiscriminatorMode import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement @@ -58,12 +65,16 @@ import kotlinx.serialization.json.add import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlinx.serialization.serializer +import kotlin.reflect.KClass +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds private const val ADMIN_PATH = "/api/admin/v3.0" @@ -72,6 +83,209 @@ data class SyncPermissions( val write: Boolean ) +@OptIn(ExperimentalSerializationApi::class) +private val json = Json { + classDiscriminatorMode = ClassDiscriminatorMode.NONE + encodeDefaults = true +} + +@Serializable +data class Schema( + val metadata: SchemaMetadata = SchemaMetadata( + database = "database", + collection = "title" + ), + val schema: SchemaData, + val relationships: Map = emptyMap(), +) { + constructor( + database: String, + schema: SchemaData, + relationships: Map, + ) : this( + metadata = SchemaMetadata( + database = database, + collection = schema.title + ), + schema = schema, + relationships = relationships + ) +} + +@Serializable +data class SchemaMetadata( + var database: String = "", + @SerialName("data_source") + var dataSource: String = "BackingDB", + var collection: String = "SyncDog", +) + +@Serializable +data class SchemaRelationship( + @SerialName("source_key") + val sourceKey: String, + @SerialName("foreign_key") + val foreignKey: String, + @SerialName("is_list") + val isList: Boolean, + val ref: String = "", +) { + constructor( + target: String, + database: String, + sourceKey: String, + foreignKey: String, + isList: Boolean, + ) : this( + sourceKey = sourceKey, + foreignKey = foreignKey, + isList = isList, + ref = "#/relationship/BackingDB/$database/$target" + ) +} + +@Serializable +sealed interface SchemaPropertyType { + @Transient val isRequired: Boolean +} + +@Serializable +class ObjectReferenceType( + @Transient val sourceKey: String = "", + @Transient val targetKey: String = "", + @Transient val target: String = "", + @Transient val isList: Boolean = false, + val bsonType: PrimitivePropertyType.Type, +) : SchemaPropertyType { + constructor(sourceKey: String, targetSchema: RealmClassImpl, isCollection: Boolean) : this( + sourceKey = sourceKey, + targetKey = targetSchema.cinteropClass.primaryKey, + target = targetSchema.name, + bsonType = targetSchema.cinteropProperties + .first { it.name == targetSchema.cinteropClass.primaryKey } + .type + .toSchemaType(), + isList = isCollection + ) + + @Transient + override val isRequired: Boolean = false +} + +@Serializable +data class SchemaData( + var title: String = "", + var properties: Map = mutableMapOf(), + val required: List = mutableListOf(), + @Transient val kind: RealmClassKind = RealmClassKind.STANDARD, + val type: PrimitivePropertyType.Type = PrimitivePropertyType.Type.OBJECT, +) : SchemaPropertyType { + @Transient + override val isRequired: Boolean = false +} + +@Serializable +data class CollectionPropertyType( + val items: SchemaPropertyType, + val uniqueItems: Boolean = false, +) : SchemaPropertyType { + val bsonType = PrimitivePropertyType.Type.ARRAY + @Transient + override val isRequired: Boolean = false +} + +@Serializable +data class MapPropertyType( + val additionalProperties: SchemaPropertyType, +) : SchemaPropertyType { + val bsonType = PrimitivePropertyType.Type.OBJECT + @Transient + override val isRequired: Boolean = false +} + +@Serializable +open class PrimitivePropertyType( + val bsonType: Type, + @Transient override val isRequired: Boolean = false, +) : SchemaPropertyType { + + enum class Type { + @SerialName("string") + STRING, + + @SerialName("object") + OBJECT, + + @SerialName("array") + ARRAY, + + @SerialName("objectId") + OBJECT_ID, + + @SerialName("boolean") + BOOLEAN, + + @SerialName("bool") + BOOL, + + @SerialName("null") + NULL, + + @SerialName("regex") + REGEX, + + @SerialName("date") + DATE, + + @SerialName("timestamp") + TIMESTAMP, + + @SerialName("int") + INT, + + @SerialName("long") + LONG, + + @SerialName("decimal") + DECIMAL, + + @SerialName("double") + DOUBLE, + + @SerialName("number") + NUMBER, + + @SerialName("binData") + BIN_DATA, + + @SerialName("uuid") + UUID, + + @SerialName("mixed") + MIXED, + + @SerialName("float") + FLOAT; + } +} + +fun PropertyType.toSchemaType() = + when (this) { + PropertyType.RLM_PROPERTY_TYPE_BOOL -> PrimitivePropertyType.Type.BOOL + PropertyType.RLM_PROPERTY_TYPE_INT -> PrimitivePropertyType.Type.INT + PropertyType.RLM_PROPERTY_TYPE_STRING -> PrimitivePropertyType.Type.STRING + PropertyType.RLM_PROPERTY_TYPE_BINARY -> PrimitivePropertyType.Type.BIN_DATA + PropertyType.RLM_PROPERTY_TYPE_OBJECT -> PrimitivePropertyType.Type.OBJECT + PropertyType.RLM_PROPERTY_TYPE_FLOAT -> PrimitivePropertyType.Type.FLOAT + PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> PrimitivePropertyType.Type.DOUBLE + PropertyType.RLM_PROPERTY_TYPE_DECIMAL128 -> PrimitivePropertyType.Type.DECIMAL + PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP -> PrimitivePropertyType.Type.DATE + PropertyType.RLM_PROPERTY_TYPE_OBJECT_ID -> PrimitivePropertyType.Type.OBJECT_ID + PropertyType.RLM_PROPERTY_TYPE_UUID -> PrimitivePropertyType.Type.UUID + PropertyType.RLM_PROPERTY_TYPE_MIXED -> PrimitivePropertyType.Type.MIXED + else -> throw IllegalArgumentException("Unsupported type") + } + @Serializable data class LoginResponse(val access_token: String) @@ -207,6 +421,8 @@ class AppServicesClient( unauthorizedClient.close() val httpClient = defaultClient("realm-baas-authorized", debug) { + expectSuccess = true + defaultRequest { headers { append("Authorization", "Bearer $accessToken") @@ -279,6 +495,45 @@ class AppServicesClient( } } + suspend fun BaasApp.setSchema( + schema: Set>, + extraProperties: Map = emptyMap() + ) { + val schemas = SchemaProcessor.process( + databaseName = clientAppId, + classes = schema, + extraProperties = extraProperties + ) + + // First we create the schemas without the relationships + val ids: Map = schemas.entries + .associate { (name, schema: Schema) -> + name to addSchema(schema = schema.copy(relationships = emptyMap())) + } + + // then we update the schema to add the relationships + schemas.forEach { (name, schema) -> + updateSchema( + id = ids[name]!!, + schema = schema + ) + } + } + + suspend fun BaasApp.updateSchema( + id: String, + schema: Schema, + ): HttpResponse = + withContext(dispatcher) { + httpClient.request( + "$url/schemas/$id" + ) { + this.method = HttpMethod.Put + setBody(json.encodeToJsonElement(schema)) + contentType(ContentType.Application.Json) + } + } + val BaasApp.url: String get() = "$groupUrl/apps/${this._id}" @@ -293,15 +548,17 @@ class AppServicesClient( } } - suspend fun BaasApp.addSchema(schema: String): JsonObject = + suspend fun BaasApp.addSchema(schema: Schema): String = withContext(dispatcher) { httpClient.typedRequest( Post, "$url/schemas" ) { - setBody(Json.parseToJsonElement(schema)) + setBody(json.encodeToJsonElement(schema)) contentType(ContentType.Application.Json) } + }.let { jsonObject: JsonObject -> + jsonObject["_id"]!!.jsonPrimitive.content } suspend fun BaasApp.addService(service: String): Service = @@ -641,6 +898,14 @@ class AppServicesClient( ) } + suspend fun BaasApp.waitUntilInitialSyncCompletes() { + withTimeout(5.minutes) { + while (!initialSyncComplete()) { + delay(1.seconds) + } + } + } + suspend fun BaasApp.initialSyncComplete(): Boolean { return withContext(dispatcher) { try { @@ -648,22 +913,7 @@ class AppServicesClient( Get, "$url/sync/progress" ).let { obj: JsonObject -> - val statuses: JsonElement = obj["progress"]!! - when (statuses) { - is JsonObject -> { - if (statuses.keys.isEmpty()) { - // It might take a few seconds to register the Schemas, so treat - // "empty" progress as initial sync not being complete (as we always - // have at least one pre-defined schema). - false - } - val bootstrapComplete: List = statuses.keys.map { schemaClass -> - statuses[schemaClass]!!.jsonObject["complete"]?.jsonPrimitive?.boolean == true - } - bootstrapComplete.all { it } && statuses.size == FLEXIBLE_SYNC_SCHEMA_COUNT - } - else -> false - } + obj["accepting_clients"]?.jsonPrimitive?.boolean ?: false } } catch (ex: IllegalStateException) { if (ex.message!!.contains("there are no mongodb/atlas services with provided sync state")) { diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt new file mode 100644 index 0000000000..9cc9b17bf6 --- /dev/null +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt @@ -0,0 +1,224 @@ +/* + * Copyright 2024 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:Suppress("invisible_member", "invisible_reference") + +package io.realm.kotlin.test.mongodb.util + +import io.realm.kotlin.internal.interop.CollectionType +import io.realm.kotlin.internal.interop.PropertyInfo +import io.realm.kotlin.internal.interop.PropertyType +import io.realm.kotlin.internal.realmObjectCompanionOrNull +import io.realm.kotlin.internal.schema.RealmClassImpl +import io.realm.kotlin.schema.RealmClassKind +import io.realm.kotlin.types.BaseRealmObject +import kotlin.reflect.KClass + +class SchemaProcessor private constructor( + classes: Set>, + private val databaseName: String, + private val extraProperties: Map, +) { + companion object { + + fun process( + databaseName: String, + classes: Set>, + extraProperties: Map = emptyMap(), + ): Map { + val processor = SchemaProcessor(classes, databaseName, extraProperties) + + return processor.processedSchemas + .entries + .filterNot { (_, schema) -> schema.kind == RealmClassKind.EMBEDDED } + .associate { (name, schema) -> + // add metadata + name to Schema( + databaseName, + schema, + processor.processedRelationships[name]!! + ) + } + } + } + + private val realmSchemas: Map = classes.associate { clazz -> + val companion = clazz.realmObjectCompanionOrNull()!! + val realmSchema = companion.io_realm_kotlin_schema() + realmSchema.cinteropClass.name to realmSchema + } + + val processedSchemas: MutableMap = mutableMapOf() + val processedRelationships: MutableMap> = mutableMapOf() + + init { + checkCycles() + generateSchemas() + generateRelationships() + } + + private fun checkCycles() { + realmSchemas.values.filter { + it.kind == RealmClassKind.EMBEDDED + }.forEach { schema -> + checkCycles(schema) + } + } + + private fun checkCycles(schema: RealmClassImpl, visited: Array = emptyArray()) { + if (visited.contains(schema.cinteropClass.name)) + throw IllegalStateException("Cycles in embedded object schemas are not supported") + + schema.cinteropProperties + .filter { targetSchema -> + targetSchema.type == PropertyType.RLM_PROPERTY_TYPE_OBJECT + } + .forEach { targetSchema -> + checkCycles(realmSchemas[targetSchema.linkTarget]!!, visited + schema.name) + } + } + + private fun generateRelationships() { + processedSchemas.values.forEach { schema -> + processedRelationships[schema.title] = + findRelationships(schema.properties).associateBy { it.sourceKey } + } + } + + private fun findRelationships( + properties: Map, + path: String = "", + ): List = + properties.entries + .filterNot { (_, value) -> + value is PrimitivePropertyType + } + .flatMap { (key, value: SchemaPropertyType) -> + value.toSchemaRelationships(key, path) + } + + private fun SchemaPropertyType.toSchemaRelationships( + key: String, + path: String = "", + ): List { + return when (this) { + is ObjectReferenceType -> listOf(toSchemaRelationship(path)) + is CollectionPropertyType -> items.toSchemaRelationships("$path$key.[]") + is MapPropertyType -> additionalProperties.toSchemaRelationships("$path$key.[]") + is SchemaData -> findRelationships(properties, "$path$key.") + else -> emptyList() + } + } + + private fun ObjectReferenceType.toSchemaRelationship(path: String = "") = + SchemaRelationship( + database = databaseName, + target = target, + sourceKey = "$path$sourceKey", + foreignKey = targetKey, + isList = isList + ) + + private fun generateSchemas() { + realmSchemas.forEach { entry -> + if (entry.key !in processedSchemas) + entry.value.toSchema() + } + } + + private fun RealmClassImpl.toSchema() { + val name = cinteropClass.name + + val properties: Map = cinteropProperties + .filterNot { + it.isComputed + } + .associate { property: PropertyInfo -> + property.name to property.toSchemaProperty() + } + when (kind) { + RealmClassKind.STANDARD -> + extraProperties.entries.associate { + it.key to PrimitivePropertyType( + bsonType = it.value, + isRequired = false, + ) + } + + RealmClassKind.EMBEDDED -> emptyMap() + RealmClassKind.ASYMMETRIC -> emptyMap() + } + + val required: List = properties.entries + .filter { (_, value) -> + value.isRequired + } + .map { (name, _) -> name } + + processedSchemas[name] = SchemaData( + title = name, + properties = properties, + required = required, + kind = kind + ) + } + + private fun PropertyInfo.toSchemaProperty(): SchemaPropertyType = + when (collectionType) { + CollectionType.RLM_COLLECTION_TYPE_NONE -> propertyValueType() + CollectionType.RLM_COLLECTION_TYPE_LIST -> CollectionPropertyType( + items = propertyValueType(isCollection = true), + uniqueItems = false + ) + + CollectionType.RLM_COLLECTION_TYPE_SET -> CollectionPropertyType( + items = propertyValueType(isCollection = true), + uniqueItems = true + ) + + CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> MapPropertyType( + additionalProperties = propertyValueType(isCollection = true) + ) + + else -> throw IllegalStateException("Unsupported $collectionType") + } + + private fun PropertyInfo.propertyValueType(isCollection: Boolean = false): SchemaPropertyType = + if (type == PropertyType.RLM_PROPERTY_TYPE_OBJECT) + realmSchemas[linkTarget]!! + .let { targetSchema: RealmClassImpl -> + when (targetSchema.kind) { + RealmClassKind.STANDARD -> ObjectReferenceType( + name, + targetSchema, + isCollection + ) + + RealmClassKind.EMBEDDED -> getSchema(targetSchema.name) + RealmClassKind.ASYMMETRIC -> TODO() + } + } + else + PrimitivePropertyType( + bsonType = type.toSchemaType(), + isRequired = !isNullable + ) + + private fun getSchema(name: String): SchemaData { + if (name !in processedSchemas) + realmSchemas[name]!!.toSchema() + + return processedSchemas[name]!! + } +} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt index 60d9dfb223..5116d6e406 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt @@ -18,7 +18,8 @@ package io.realm.kotlin.test.mongodb.util import io.realm.kotlin.test.mongodb.TEST_APP_CLUSTER_NAME import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION -import kotlinx.serialization.decodeFromString +import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA +import io.realm.kotlin.test.mongodb.common.PARTITION_BASED_SCHEMA import kotlinx.serialization.json.Json object TestAppInitializer { @@ -36,9 +37,12 @@ object TestAppInitializer { suspend fun AppServicesClient.initializeFlexibleSync( app: BaasApp, service: Service, - recoveryDisabled: Boolean = false // TODO + recoveryDisabled: Boolean = false ) { val databaseName = app.clientAppId + + app.setSchema(FLEXIBLE_SYNC_SCHEMA) + service.setSyncConfig( """ { @@ -48,12 +52,21 @@ object TestAppInitializer { "is_recovery_mode_disabled": $recoveryDisabled, "queryable_fields_names": [ "name", - "section" + "section", + "stringField", + "location", + "selector" + ], + "asymmetric_tables": [ + "AsymmetricA", + "Measurement" ] } } """.trimIndent() ) + + app.waitUntilInitialSyncCompletes() } @Suppress("LongMethod") @@ -67,6 +80,13 @@ object TestAppInitializer { app.addFunction(canReadPartition) app.addFunction(canWritePartition) + app.setSchema( + schema = PARTITION_BASED_SCHEMA, + extraProperties = mapOf( + "realm_id" to PrimitivePropertyType.Type.STRING + ) + ) + service.setSyncConfig( """ { @@ -105,88 +125,7 @@ object TestAppInitializer { """.trimIndent() ) - app.addSchema( - """ - { - "metadata": { - "data_source": "BackingDB", - "database": "$databaseName", - "collection": "SyncDog" - }, - "schema": { - "properties": { - "_id": { - "bsonType": "objectId" - }, - "breed": { - "bsonType": "string" - }, - "name": { - "bsonType": "string" - }, - "realm_id": { - "bsonType": "string" - } - }, - "required": [ - "name" - ], - "title": "SyncDog" - } - } - """.trimIndent() - ) - - app.addSchema( - """ - { - "metadata": { - "data_source": "BackingDB", - "database": "$databaseName", - "collection": "SyncPerson" - }, - "relationships": { - "dogs": { - "ref": "#/relationship/BackingDB/$databaseName/SyncDog", - "source_key": "dogs", - "foreign_key": "_id", - "is_list": true - } - }, - "schema": { - "properties": { - "_id": { - "bsonType": "objectId" - }, - "age": { - "bsonType": "int" - }, - "dogs": { - "bsonType": "array", - "items": { - "bsonType": "objectId" - } - }, - "firstName": { - "bsonType": "string" - }, - "lastName": { - "bsonType": "string" - }, - "realm_id": { - "bsonType": "string" - } - }, - "required": [ - "firstName", - "lastName", - "age" - ], - "title": "SyncPerson" - } - } - """.trimIndent() - ) + app.waitUntilInitialSyncCompletes() } suspend fun AppServicesClient.addEmailProvider( @@ -295,8 +234,6 @@ object TestAppInitializer { block(app, service) } - - setDevelopmentMode(true) } private val insertDocument = Function( diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SchemaProcessorTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SchemaProcessorTests.kt new file mode 100644 index 0000000000..f7b19a18f7 --- /dev/null +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SchemaProcessorTests.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2024 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.kotlin.test.mongodb.common + +import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.test.mongodb.util.SchemaProcessor +import io.realm.kotlin.types.EmbeddedRealmObject +import kotlin.test.Test + +class CycleEmbeddedObject1 : EmbeddedRealmObject { + var name: String = "" + var o1: CycleEmbeddedObject2 = CycleEmbeddedObject2() +} + +class CycleEmbeddedObject2 : EmbeddedRealmObject { + var name: String = "" + var o1: CycleEmbeddedObject3 = CycleEmbeddedObject3() +} + +class CycleEmbeddedObject3 : EmbeddedRealmObject { + var name: String = "" + var o1: CycleEmbeddedObject1 = CycleEmbeddedObject1() +} + +class NoCycleEmbeddedObject1 : EmbeddedRealmObject { + var name: String = "" + var o1: NoCycleEmbeddedObject2 = NoCycleEmbeddedObject2() +} + +class NoCycleEmbeddedObject2 : EmbeddedRealmObject { + var name: String = "" +} + +class SchemaProcessorTests { + + @Test + fun cyclesThrow() { + assertFailsWithMessage("Cycles in embedded object schemas are not supported") { + SchemaProcessor.process( + "", + classes = setOf( + CycleEmbeddedObject1::class, + CycleEmbeddedObject2::class, + CycleEmbeddedObject3::class + ) + ) + } + } + + @Test + fun noCyclesDoesntThrow() { + SchemaProcessor.process( + "", + classes = setOf(NoCycleEmbeddedObject1::class, NoCycleEmbeddedObject2::class) + ) + } +} From 195b221739fc11c49d5c26a11af70e39ff8b11a6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 5 Jul 2024 11:31:15 +0200 Subject: [PATCH 06/10] [Automated] Merge releases into main (#1794) --- .github/workflows/pr.yml | 19 +- buildSrc/src/main/kotlin/Config.kt | 4 +- .../cinterop/src/jvmMain/generic.Dockerfile | 31 -- packages/test-sync/build.gradle.kts | 2 + .../io/realm/kotlin/test/mongodb/TestApp.kt | 26 -- .../kotlin/test/mongodb/util/AppAdmin.kt | 21 -- .../test/mongodb/util/AppServicesClient.kt | 288 ++++++++++++++++-- .../test/mongodb/util/SchemaProcessor.kt | 224 ++++++++++++++ .../test/mongodb/util/TestAppInitializer.kt | 111 ++----- .../mongodb/common/SchemaProcessorTests.kt | 70 +++++ 10 files changed, 594 insertions(+), 202 deletions(-) delete mode 100644 packages/cinterop/src/jvmMain/generic.Dockerfile create mode 100644 packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt create mode 100644 packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SchemaProcessorTests.kt diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 98249ff7c4..40494fde38 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -84,7 +84,6 @@ jobs: cd ~ curl -L ${{ vars.VERSION_SWIG}} > swig.rb && HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true brew install swig.rb - - name: Build JNI Stub working-directory: ./packages run: ./gradlew :jni-swig-stub:assemble -Prealm.kotlin.buildRealmCore=false -Prealm.kotlin.mainHost=false @@ -97,7 +96,7 @@ jobs: retention-days: 1 build-jvm-linux-native-lib: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [check-cache, build-jni-swig-stub] if: | always() && @@ -133,21 +132,9 @@ jobs: name: jni-stub-${{ needs.check-cache.outputs.version-label }} path: ./packages/jni-swig-stub/build/generated/sources/jni - - name: Build Docker image - uses: docker/build-push-action@v5 - with: - tags: jvm-native-lib-linux:latest - file: ./packages/cinterop/src/jvmMain/generic.Dockerfile - push: false - - name: Build native lib - uses: addnab/docker-run-action@v3 - with: - image: jvm-native-lib-linux:latest - shell: bash - options: -v ${{ github.workspace }}:/work - run: | - cd /work/packages/cinterop + working-directory: ./packages/cinterop + run: | mkdir build cd build rm -rf realmLinuxBuild diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 2e5864113d..749866c7c2 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -128,12 +128,12 @@ object Versions { const val latestKotlin = "2.0.0" // https://kotlinlang.org/docs/eap.html#build-details const val kotlinCompileTesting = "0.5.0-alpha07" // https://github.com/zacsweers/kotlin-compile-testing const val ktlint = "0.45.2" // https://github.com/pinterest/ktlint - const val ktor = "2.3.7" // https://github.com/ktorio/ktor + const val ktor = "2.3.12" // https://github.com/ktorio/ktor const val multidex = "2.0.1" // https://developer.android.com/jetpack/androidx/releases/multidex const val nexusPublishPlugin = "1.1.0" // https://github.com/gradle-nexus/publish-plugin const val okio = "3.2.0" // https://square.github.io/okio/#releases const val relinker = "1.4.5" // https://github.com/KeepSafe/ReLinker - const val serialization = "1.6.0" // https://kotlinlang.org/docs/releases.html#release-details + const val serialization = "1.7.1" // https://kotlinlang.org/docs/releases.html#release-details const val shadowJar = "6.1.0" // https://mvnrepository.com/artifact/com.github.johnrengelman.shadow/com.github.johnrengelman.shadow.gradle.plugin?repo=gradle-plugins const val snakeYaml = "1.33" // https://github.com/snakeyaml/snakeyaml val sourceCompatibilityVersion = JavaVersion.VERSION_1_8 // Language level of any Java source code. diff --git a/packages/cinterop/src/jvmMain/generic.Dockerfile b/packages/cinterop/src/jvmMain/generic.Dockerfile deleted file mode 100644 index 26da693918..0000000000 --- a/packages/cinterop/src/jvmMain/generic.Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM centos:7 - -# Install EPEL & devtoolset -RUN yum install -y \ - epel-release \ - centos-release-scl-rh \ - && yum-config-manager --enable rhel-server-rhscl-7-rpms - -RUN yum install -y \ - chrpath \ - devtoolset-9 \ - jq \ - libconfig-devel \ - openssh-clients \ - rh-git218 \ - zlib-devel \ - java-1.8.0-openjdk-devel \ - && yum clean all - -ENV PATH /opt/cmake/bin:/opt/rh/rh-git218/root/usr/bin:/opt/rh/devtoolset-9/root/usr/bin:$PATH -ENV LD_LIBRARY_PATH /opt/rh/devtoolset-9/root/usr/lib64:/opt/rh/devtoolset-9/root/usr/lib:/opt/rh/devtoolset-9/root/usr/lib64/dyninst:/opt/rh/devtoolset-9/root/usr/lib/dyninst:/opt/rh/devtoolset-9/root/usr/lib64:/opt/rh/devtoolset-9/root/usr/lib - -RUN mkdir -p /opt/cmake \ - && curl https://cmake.org/files/v3.27/cmake-3.27.7-linux-x86_64.sh -o /cmake.sh \ - && sh /cmake.sh --prefix=/opt/cmake --skip-license \ - && rm /cmake.sh - -RUN mkdir -p /etc/ssh && \ - echo "Host github.com\n\tStrictHostKeyChecking no\n" >> /etc/ssh/ssh_config && \ - ssh-keyscan github.com >> /etc/ssh/ssh_known_hosts - diff --git a/packages/test-sync/build.gradle.kts b/packages/test-sync/build.gradle.kts index 8bf974864c..d8ad09ebe1 100644 --- a/packages/test-sync/build.gradle.kts +++ b/packages/test-sync/build.gradle.kts @@ -102,6 +102,8 @@ kotlin { implementation("io.ktor:ktor-serialization-kotlinx-json:${Versions.ktor}") implementation("io.ktor:ktor-client-content-negotiation:${Versions.ktor}") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.serialization}") + implementation("com.squareup.okio:okio:${Versions.okio}") } } diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt index 9dc98af5b9..ab221414d6 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt @@ -19,7 +19,6 @@ package io.realm.kotlin.test.mongodb -import io.realm.kotlin.Realm import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi import io.realm.kotlin.internal.interop.RealmInterop import io.realm.kotlin.internal.interop.SynchronizableObject @@ -31,8 +30,6 @@ import io.realm.kotlin.mongodb.AppConfiguration import io.realm.kotlin.mongodb.Credentials import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.internal.AppConfigurationImpl -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA import io.realm.kotlin.test.mongodb.util.AppAdmin import io.realm.kotlin.test.mongodb.util.AppAdminImpl import io.realm.kotlin.test.mongodb.util.AppServicesClient @@ -41,7 +38,6 @@ import io.realm.kotlin.test.mongodb.util.Service import io.realm.kotlin.test.mongodb.util.TestAppInitializer.initializeDefault import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.use import kotlinx.coroutines.CloseableCoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -119,28 +115,6 @@ open class TestApp private constructor( ) ) - init { - // For apps with Flexible Sync, we need to bootstrap all the schemas to work around - // https://github.com/realm/realm-core/issues/7297. - // So we create a dummy Realm, upload all the schemas and close the Realm again. - if (app.configuration.appId.startsWith(TEST_APP_FLEX, ignoreCase = false)) { - runBlocking { - val user = app.login(Credentials.anonymous()) - val config = SyncConfiguration.create(user, FLEXIBLE_SYNC_SCHEMA) - try { - Realm.open(config).use { - // Using syncSession.uploadAllLocalChanges() seems to just hang forever. - // This is tracked by the above Core issue. Instead use the Sync Progress - // endpoint to signal when the schemas are ready. - pairAdminApp.second.waitForSyncBootstrap() - } - } finally { - user.delete() - } - } - } - } - fun createUserAndLogin(): User = runBlocking { val (email, password) = TestHelper.randomEmail() to "password1234" emailPasswordAuth.registerUser(email, password).run { diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt index cbef34d0d3..e15939520e 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt @@ -18,9 +18,7 @@ package io.realm.kotlin.test.mongodb.util import io.realm.kotlin.mongodb.sync.SyncMode import io.realm.kotlin.mongodb.sync.SyncSession -import kotlinx.coroutines.delay import kotlinx.serialization.json.JsonObject -import kotlin.time.Duration.Companion.seconds /** * Wrapper around App Services Server Admin functions needed for tests. @@ -103,11 +101,6 @@ interface AppAdmin { */ suspend fun deleteDocuments(database: String, clazz: String, query: String): JsonObject? - /** - * Wait for Sync bootstrap to complete for all model classes. - */ - suspend fun waitForSyncBootstrap() - fun closeClient() } @@ -208,20 +201,6 @@ class AppAdminImpl( app.deleteDocument(database, clazz, query) } - override suspend fun waitForSyncBootstrap() { - baasClient.run { - var limit = 300 - var i = 0 - while (!app.initialSyncComplete() && i < limit) { - delay(1.seconds) - i++ - } - if (!app.initialSyncComplete()) { - throw IllegalStateException("Test server did not finish bootstrapping sync in time: $limit s.") - } - } - } - override fun closeClient() { baasClient.closeClient() } diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt index 58b770d1a5..eb6e02c732 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt @@ -35,19 +35,26 @@ import io.ktor.http.HttpMethod.Companion.Post import io.ktor.http.contentType import io.ktor.http.isSuccess import io.ktor.serialization.kotlinx.json.json +import io.realm.kotlin.internal.interop.PropertyType import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.internal.schema.RealmClassImpl import io.realm.kotlin.mongodb.sync.SyncMode +import io.realm.kotlin.schema.RealmClassKind import io.realm.kotlin.test.mongodb.SyncServerConfig import io.realm.kotlin.test.mongodb.TEST_APP_CLUSTER_NAME -import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA_COUNT import io.realm.kotlin.test.mongodb.util.TestAppInitializer.initialize +import io.realm.kotlin.types.BaseRealmObject import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.delay import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.ClassDiscriminatorMode import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement @@ -58,12 +65,16 @@ import kotlinx.serialization.json.add import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlinx.serialization.serializer +import kotlin.reflect.KClass +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds private const val ADMIN_PATH = "/api/admin/v3.0" @@ -72,6 +83,209 @@ data class SyncPermissions( val write: Boolean ) +@OptIn(ExperimentalSerializationApi::class) +private val json = Json { + classDiscriminatorMode = ClassDiscriminatorMode.NONE + encodeDefaults = true +} + +@Serializable +data class Schema( + val metadata: SchemaMetadata = SchemaMetadata( + database = "database", + collection = "title" + ), + val schema: SchemaData, + val relationships: Map = emptyMap(), +) { + constructor( + database: String, + schema: SchemaData, + relationships: Map, + ) : this( + metadata = SchemaMetadata( + database = database, + collection = schema.title + ), + schema = schema, + relationships = relationships + ) +} + +@Serializable +data class SchemaMetadata( + var database: String = "", + @SerialName("data_source") + var dataSource: String = "BackingDB", + var collection: String = "SyncDog", +) + +@Serializable +data class SchemaRelationship( + @SerialName("source_key") + val sourceKey: String, + @SerialName("foreign_key") + val foreignKey: String, + @SerialName("is_list") + val isList: Boolean, + val ref: String = "", +) { + constructor( + target: String, + database: String, + sourceKey: String, + foreignKey: String, + isList: Boolean, + ) : this( + sourceKey = sourceKey, + foreignKey = foreignKey, + isList = isList, + ref = "#/relationship/BackingDB/$database/$target" + ) +} + +@Serializable +sealed interface SchemaPropertyType { + @Transient val isRequired: Boolean +} + +@Serializable +class ObjectReferenceType( + @Transient val sourceKey: String = "", + @Transient val targetKey: String = "", + @Transient val target: String = "", + @Transient val isList: Boolean = false, + val bsonType: PrimitivePropertyType.Type, +) : SchemaPropertyType { + constructor(sourceKey: String, targetSchema: RealmClassImpl, isCollection: Boolean) : this( + sourceKey = sourceKey, + targetKey = targetSchema.cinteropClass.primaryKey, + target = targetSchema.name, + bsonType = targetSchema.cinteropProperties + .first { it.name == targetSchema.cinteropClass.primaryKey } + .type + .toSchemaType(), + isList = isCollection + ) + + @Transient + override val isRequired: Boolean = false +} + +@Serializable +data class SchemaData( + var title: String = "", + var properties: Map = mutableMapOf(), + val required: List = mutableListOf(), + @Transient val kind: RealmClassKind = RealmClassKind.STANDARD, + val type: PrimitivePropertyType.Type = PrimitivePropertyType.Type.OBJECT, +) : SchemaPropertyType { + @Transient + override val isRequired: Boolean = false +} + +@Serializable +data class CollectionPropertyType( + val items: SchemaPropertyType, + val uniqueItems: Boolean = false, +) : SchemaPropertyType { + val bsonType = PrimitivePropertyType.Type.ARRAY + @Transient + override val isRequired: Boolean = false +} + +@Serializable +data class MapPropertyType( + val additionalProperties: SchemaPropertyType, +) : SchemaPropertyType { + val bsonType = PrimitivePropertyType.Type.OBJECT + @Transient + override val isRequired: Boolean = false +} + +@Serializable +open class PrimitivePropertyType( + val bsonType: Type, + @Transient override val isRequired: Boolean = false, +) : SchemaPropertyType { + + enum class Type { + @SerialName("string") + STRING, + + @SerialName("object") + OBJECT, + + @SerialName("array") + ARRAY, + + @SerialName("objectId") + OBJECT_ID, + + @SerialName("boolean") + BOOLEAN, + + @SerialName("bool") + BOOL, + + @SerialName("null") + NULL, + + @SerialName("regex") + REGEX, + + @SerialName("date") + DATE, + + @SerialName("timestamp") + TIMESTAMP, + + @SerialName("int") + INT, + + @SerialName("long") + LONG, + + @SerialName("decimal") + DECIMAL, + + @SerialName("double") + DOUBLE, + + @SerialName("number") + NUMBER, + + @SerialName("binData") + BIN_DATA, + + @SerialName("uuid") + UUID, + + @SerialName("mixed") + MIXED, + + @SerialName("float") + FLOAT; + } +} + +fun PropertyType.toSchemaType() = + when (this) { + PropertyType.RLM_PROPERTY_TYPE_BOOL -> PrimitivePropertyType.Type.BOOL + PropertyType.RLM_PROPERTY_TYPE_INT -> PrimitivePropertyType.Type.INT + PropertyType.RLM_PROPERTY_TYPE_STRING -> PrimitivePropertyType.Type.STRING + PropertyType.RLM_PROPERTY_TYPE_BINARY -> PrimitivePropertyType.Type.BIN_DATA + PropertyType.RLM_PROPERTY_TYPE_OBJECT -> PrimitivePropertyType.Type.OBJECT + PropertyType.RLM_PROPERTY_TYPE_FLOAT -> PrimitivePropertyType.Type.FLOAT + PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> PrimitivePropertyType.Type.DOUBLE + PropertyType.RLM_PROPERTY_TYPE_DECIMAL128 -> PrimitivePropertyType.Type.DECIMAL + PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP -> PrimitivePropertyType.Type.DATE + PropertyType.RLM_PROPERTY_TYPE_OBJECT_ID -> PrimitivePropertyType.Type.OBJECT_ID + PropertyType.RLM_PROPERTY_TYPE_UUID -> PrimitivePropertyType.Type.UUID + PropertyType.RLM_PROPERTY_TYPE_MIXED -> PrimitivePropertyType.Type.MIXED + else -> throw IllegalArgumentException("Unsupported type") + } + @Serializable data class LoginResponse(val access_token: String) @@ -207,6 +421,8 @@ class AppServicesClient( unauthorizedClient.close() val httpClient = defaultClient("realm-baas-authorized", debug) { + expectSuccess = true + defaultRequest { headers { append("Authorization", "Bearer $accessToken") @@ -279,6 +495,45 @@ class AppServicesClient( } } + suspend fun BaasApp.setSchema( + schema: Set>, + extraProperties: Map = emptyMap() + ) { + val schemas = SchemaProcessor.process( + databaseName = clientAppId, + classes = schema, + extraProperties = extraProperties + ) + + // First we create the schemas without the relationships + val ids: Map = schemas.entries + .associate { (name, schema: Schema) -> + name to addSchema(schema = schema.copy(relationships = emptyMap())) + } + + // then we update the schema to add the relationships + schemas.forEach { (name, schema) -> + updateSchema( + id = ids[name]!!, + schema = schema + ) + } + } + + suspend fun BaasApp.updateSchema( + id: String, + schema: Schema, + ): HttpResponse = + withContext(dispatcher) { + httpClient.request( + "$url/schemas/$id" + ) { + this.method = HttpMethod.Put + setBody(json.encodeToJsonElement(schema)) + contentType(ContentType.Application.Json) + } + } + val BaasApp.url: String get() = "$groupUrl/apps/${this._id}" @@ -293,15 +548,17 @@ class AppServicesClient( } } - suspend fun BaasApp.addSchema(schema: String): JsonObject = + suspend fun BaasApp.addSchema(schema: Schema): String = withContext(dispatcher) { httpClient.typedRequest( Post, "$url/schemas" ) { - setBody(Json.parseToJsonElement(schema)) + setBody(json.encodeToJsonElement(schema)) contentType(ContentType.Application.Json) } + }.let { jsonObject: JsonObject -> + jsonObject["_id"]!!.jsonPrimitive.content } suspend fun BaasApp.addService(service: String): Service = @@ -641,6 +898,14 @@ class AppServicesClient( ) } + suspend fun BaasApp.waitUntilInitialSyncCompletes() { + withTimeout(5.minutes) { + while (!initialSyncComplete()) { + delay(1.seconds) + } + } + } + suspend fun BaasApp.initialSyncComplete(): Boolean { return withContext(dispatcher) { try { @@ -648,22 +913,7 @@ class AppServicesClient( Get, "$url/sync/progress" ).let { obj: JsonObject -> - val statuses: JsonElement = obj["progress"]!! - when (statuses) { - is JsonObject -> { - if (statuses.keys.isEmpty()) { - // It might take a few seconds to register the Schemas, so treat - // "empty" progress as initial sync not being complete (as we always - // have at least one pre-defined schema). - false - } - val bootstrapComplete: List = statuses.keys.map { schemaClass -> - statuses[schemaClass]!!.jsonObject["complete"]?.jsonPrimitive?.boolean == true - } - bootstrapComplete.all { it } && statuses.size == FLEXIBLE_SYNC_SCHEMA_COUNT - } - else -> false - } + obj["accepting_clients"]?.jsonPrimitive?.boolean ?: false } } catch (ex: IllegalStateException) { if (ex.message!!.contains("there are no mongodb/atlas services with provided sync state")) { diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt new file mode 100644 index 0000000000..9cc9b17bf6 --- /dev/null +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt @@ -0,0 +1,224 @@ +/* + * Copyright 2024 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:Suppress("invisible_member", "invisible_reference") + +package io.realm.kotlin.test.mongodb.util + +import io.realm.kotlin.internal.interop.CollectionType +import io.realm.kotlin.internal.interop.PropertyInfo +import io.realm.kotlin.internal.interop.PropertyType +import io.realm.kotlin.internal.realmObjectCompanionOrNull +import io.realm.kotlin.internal.schema.RealmClassImpl +import io.realm.kotlin.schema.RealmClassKind +import io.realm.kotlin.types.BaseRealmObject +import kotlin.reflect.KClass + +class SchemaProcessor private constructor( + classes: Set>, + private val databaseName: String, + private val extraProperties: Map, +) { + companion object { + + fun process( + databaseName: String, + classes: Set>, + extraProperties: Map = emptyMap(), + ): Map { + val processor = SchemaProcessor(classes, databaseName, extraProperties) + + return processor.processedSchemas + .entries + .filterNot { (_, schema) -> schema.kind == RealmClassKind.EMBEDDED } + .associate { (name, schema) -> + // add metadata + name to Schema( + databaseName, + schema, + processor.processedRelationships[name]!! + ) + } + } + } + + private val realmSchemas: Map = classes.associate { clazz -> + val companion = clazz.realmObjectCompanionOrNull()!! + val realmSchema = companion.io_realm_kotlin_schema() + realmSchema.cinteropClass.name to realmSchema + } + + val processedSchemas: MutableMap = mutableMapOf() + val processedRelationships: MutableMap> = mutableMapOf() + + init { + checkCycles() + generateSchemas() + generateRelationships() + } + + private fun checkCycles() { + realmSchemas.values.filter { + it.kind == RealmClassKind.EMBEDDED + }.forEach { schema -> + checkCycles(schema) + } + } + + private fun checkCycles(schema: RealmClassImpl, visited: Array = emptyArray()) { + if (visited.contains(schema.cinteropClass.name)) + throw IllegalStateException("Cycles in embedded object schemas are not supported") + + schema.cinteropProperties + .filter { targetSchema -> + targetSchema.type == PropertyType.RLM_PROPERTY_TYPE_OBJECT + } + .forEach { targetSchema -> + checkCycles(realmSchemas[targetSchema.linkTarget]!!, visited + schema.name) + } + } + + private fun generateRelationships() { + processedSchemas.values.forEach { schema -> + processedRelationships[schema.title] = + findRelationships(schema.properties).associateBy { it.sourceKey } + } + } + + private fun findRelationships( + properties: Map, + path: String = "", + ): List = + properties.entries + .filterNot { (_, value) -> + value is PrimitivePropertyType + } + .flatMap { (key, value: SchemaPropertyType) -> + value.toSchemaRelationships(key, path) + } + + private fun SchemaPropertyType.toSchemaRelationships( + key: String, + path: String = "", + ): List { + return when (this) { + is ObjectReferenceType -> listOf(toSchemaRelationship(path)) + is CollectionPropertyType -> items.toSchemaRelationships("$path$key.[]") + is MapPropertyType -> additionalProperties.toSchemaRelationships("$path$key.[]") + is SchemaData -> findRelationships(properties, "$path$key.") + else -> emptyList() + } + } + + private fun ObjectReferenceType.toSchemaRelationship(path: String = "") = + SchemaRelationship( + database = databaseName, + target = target, + sourceKey = "$path$sourceKey", + foreignKey = targetKey, + isList = isList + ) + + private fun generateSchemas() { + realmSchemas.forEach { entry -> + if (entry.key !in processedSchemas) + entry.value.toSchema() + } + } + + private fun RealmClassImpl.toSchema() { + val name = cinteropClass.name + + val properties: Map = cinteropProperties + .filterNot { + it.isComputed + } + .associate { property: PropertyInfo -> + property.name to property.toSchemaProperty() + } + when (kind) { + RealmClassKind.STANDARD -> + extraProperties.entries.associate { + it.key to PrimitivePropertyType( + bsonType = it.value, + isRequired = false, + ) + } + + RealmClassKind.EMBEDDED -> emptyMap() + RealmClassKind.ASYMMETRIC -> emptyMap() + } + + val required: List = properties.entries + .filter { (_, value) -> + value.isRequired + } + .map { (name, _) -> name } + + processedSchemas[name] = SchemaData( + title = name, + properties = properties, + required = required, + kind = kind + ) + } + + private fun PropertyInfo.toSchemaProperty(): SchemaPropertyType = + when (collectionType) { + CollectionType.RLM_COLLECTION_TYPE_NONE -> propertyValueType() + CollectionType.RLM_COLLECTION_TYPE_LIST -> CollectionPropertyType( + items = propertyValueType(isCollection = true), + uniqueItems = false + ) + + CollectionType.RLM_COLLECTION_TYPE_SET -> CollectionPropertyType( + items = propertyValueType(isCollection = true), + uniqueItems = true + ) + + CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> MapPropertyType( + additionalProperties = propertyValueType(isCollection = true) + ) + + else -> throw IllegalStateException("Unsupported $collectionType") + } + + private fun PropertyInfo.propertyValueType(isCollection: Boolean = false): SchemaPropertyType = + if (type == PropertyType.RLM_PROPERTY_TYPE_OBJECT) + realmSchemas[linkTarget]!! + .let { targetSchema: RealmClassImpl -> + when (targetSchema.kind) { + RealmClassKind.STANDARD -> ObjectReferenceType( + name, + targetSchema, + isCollection + ) + + RealmClassKind.EMBEDDED -> getSchema(targetSchema.name) + RealmClassKind.ASYMMETRIC -> TODO() + } + } + else + PrimitivePropertyType( + bsonType = type.toSchemaType(), + isRequired = !isNullable + ) + + private fun getSchema(name: String): SchemaData { + if (name !in processedSchemas) + realmSchemas[name]!!.toSchema() + + return processedSchemas[name]!! + } +} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt index 60d9dfb223..5116d6e406 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt @@ -18,7 +18,8 @@ package io.realm.kotlin.test.mongodb.util import io.realm.kotlin.test.mongodb.TEST_APP_CLUSTER_NAME import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION -import kotlinx.serialization.decodeFromString +import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA +import io.realm.kotlin.test.mongodb.common.PARTITION_BASED_SCHEMA import kotlinx.serialization.json.Json object TestAppInitializer { @@ -36,9 +37,12 @@ object TestAppInitializer { suspend fun AppServicesClient.initializeFlexibleSync( app: BaasApp, service: Service, - recoveryDisabled: Boolean = false // TODO + recoveryDisabled: Boolean = false ) { val databaseName = app.clientAppId + + app.setSchema(FLEXIBLE_SYNC_SCHEMA) + service.setSyncConfig( """ { @@ -48,12 +52,21 @@ object TestAppInitializer { "is_recovery_mode_disabled": $recoveryDisabled, "queryable_fields_names": [ "name", - "section" + "section", + "stringField", + "location", + "selector" + ], + "asymmetric_tables": [ + "AsymmetricA", + "Measurement" ] } } """.trimIndent() ) + + app.waitUntilInitialSyncCompletes() } @Suppress("LongMethod") @@ -67,6 +80,13 @@ object TestAppInitializer { app.addFunction(canReadPartition) app.addFunction(canWritePartition) + app.setSchema( + schema = PARTITION_BASED_SCHEMA, + extraProperties = mapOf( + "realm_id" to PrimitivePropertyType.Type.STRING + ) + ) + service.setSyncConfig( """ { @@ -105,88 +125,7 @@ object TestAppInitializer { """.trimIndent() ) - app.addSchema( - """ - { - "metadata": { - "data_source": "BackingDB", - "database": "$databaseName", - "collection": "SyncDog" - }, - "schema": { - "properties": { - "_id": { - "bsonType": "objectId" - }, - "breed": { - "bsonType": "string" - }, - "name": { - "bsonType": "string" - }, - "realm_id": { - "bsonType": "string" - } - }, - "required": [ - "name" - ], - "title": "SyncDog" - } - } - """.trimIndent() - ) - - app.addSchema( - """ - { - "metadata": { - "data_source": "BackingDB", - "database": "$databaseName", - "collection": "SyncPerson" - }, - "relationships": { - "dogs": { - "ref": "#/relationship/BackingDB/$databaseName/SyncDog", - "source_key": "dogs", - "foreign_key": "_id", - "is_list": true - } - }, - "schema": { - "properties": { - "_id": { - "bsonType": "objectId" - }, - "age": { - "bsonType": "int" - }, - "dogs": { - "bsonType": "array", - "items": { - "bsonType": "objectId" - } - }, - "firstName": { - "bsonType": "string" - }, - "lastName": { - "bsonType": "string" - }, - "realm_id": { - "bsonType": "string" - } - }, - "required": [ - "firstName", - "lastName", - "age" - ], - "title": "SyncPerson" - } - } - """.trimIndent() - ) + app.waitUntilInitialSyncCompletes() } suspend fun AppServicesClient.addEmailProvider( @@ -295,8 +234,6 @@ object TestAppInitializer { block(app, service) } - - setDevelopmentMode(true) } private val insertDocument = Function( diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SchemaProcessorTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SchemaProcessorTests.kt new file mode 100644 index 0000000000..f7b19a18f7 --- /dev/null +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SchemaProcessorTests.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2024 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.kotlin.test.mongodb.common + +import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.test.mongodb.util.SchemaProcessor +import io.realm.kotlin.types.EmbeddedRealmObject +import kotlin.test.Test + +class CycleEmbeddedObject1 : EmbeddedRealmObject { + var name: String = "" + var o1: CycleEmbeddedObject2 = CycleEmbeddedObject2() +} + +class CycleEmbeddedObject2 : EmbeddedRealmObject { + var name: String = "" + var o1: CycleEmbeddedObject3 = CycleEmbeddedObject3() +} + +class CycleEmbeddedObject3 : EmbeddedRealmObject { + var name: String = "" + var o1: CycleEmbeddedObject1 = CycleEmbeddedObject1() +} + +class NoCycleEmbeddedObject1 : EmbeddedRealmObject { + var name: String = "" + var o1: NoCycleEmbeddedObject2 = NoCycleEmbeddedObject2() +} + +class NoCycleEmbeddedObject2 : EmbeddedRealmObject { + var name: String = "" +} + +class SchemaProcessorTests { + + @Test + fun cyclesThrow() { + assertFailsWithMessage("Cycles in embedded object schemas are not supported") { + SchemaProcessor.process( + "", + classes = setOf( + CycleEmbeddedObject1::class, + CycleEmbeddedObject2::class, + CycleEmbeddedObject3::class + ) + ) + } + } + + @Test + fun noCyclesDoesntThrow() { + SchemaProcessor.process( + "", + classes = setOf(NoCycleEmbeddedObject1::class, NoCycleEmbeddedObject2::class) + ) + } +} From 2eaaf92175f5caf957317a2bf62a4fb4fa778de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 5 Jul 2024 14:42:46 +0200 Subject: [PATCH 07/10] Guard decoding issues when core logs invalid utf-8 messages (#1792) --- CHANGELOG.md | 1 + packages/cinterop/src/jvm/jni/utils.cpp | 7 +++---- .../jni-swig-stub/src/main/jni/realm_api_helpers.cpp | 10 +++++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4828478bd7..8e11806db1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * None. ### Fixed +* Fix crashes when core tries to log invalid utf-8 messages. (Issue [#1760](https://github.com/realm/realm-kotlin/issues/1760) [RKOTLIN-1089](https://jira.mongodb.org/browse/RKOTLIN-1089)). * [Sync] Fatal sync exceptions are now thrown as `UnrecoverableSyncException`. (Issue [#1767](https://github.com/realm/realm-kotlin/issues/1767) [RKOTLIN-1096](https://jira.mongodb.org/browse/RKOTLIN-1096)). * [Sync] Fix `NullPointerException` in `SubscriptionSet.waitForSynchronization`. (Issue [#1777](https://github.com/realm/realm-kotlin/issues/1777) [RKOTLIN-1102](https://jira.mongodb.org/browse/RKOTLIN-1102)). diff --git a/packages/cinterop/src/jvm/jni/utils.cpp b/packages/cinterop/src/jvm/jni/utils.cpp index 50cb51b80a..9ff5a234cb 100644 --- a/packages/cinterop/src/jvm/jni/utils.cpp +++ b/packages/cinterop/src/jvm/jni/utils.cpp @@ -82,13 +82,12 @@ static std::string string_to_hex(const std::string& message, realm::StringData& ret << "error_code = " << error_code << "; "; ret << "retcode = " << retcode << "; "; ret << "StringData.size = " << str.size() << "; "; - ret << "StringData.data = " << str << "; "; - ret << "StringData as hex = "; + ret << "StringData as hex ="; for (std::string::size_type i = 0; i < str.size(); ++i) ret << " 0x" << std::hex << std::setfill('0') << std::setw(2) << (int)s[i]; ret << "; "; - ret << "in_begin = " << in_begin << "; "; - ret << "in_end = " << in_end << "; "; + ret << "in_begin = " << (void*) in_begin << "; "; + ret << "in_end = " << (void*) in_end << "; "; ret << "out_curr = " << out_curr << "; "; ret << "out_end = " << out_end << ";"; return ret.str(); diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index c62c0757b2..240babf5f7 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -973,7 +973,15 @@ realm_set_log_callback([](void *userdata, const char *category, realm_log_level_ "(SLjava/lang/String;Ljava/lang/String;)V"); push_local_frame(jenv, 2); - jenv->CallVoidMethod(log_callback, log_method, java_level, to_jstring(jenv, category), to_jstring(jenv, message)); + jstring j_message = NULL; + try { + j_message = to_jstring(jenv, message); + } catch (RuntimeError exception) { + std::ostringstream ret; + ret << "Invalid data: " << exception.reason(); + j_message = to_jstring(jenv, ret.str()); + } + jenv->CallVoidMethod(log_callback, log_method, java_level, to_jstring(jenv, category), j_message); jni_check_exception(jenv); jenv->PopLocalFrame(NULL); }, From 5e41c6588d7e6594a1f273b2ed5740bc22e532bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 5 Jul 2024 15:34:41 +0200 Subject: [PATCH 08/10] Test app initializer rework (#1785) --- CHANGELOG.md | 5 +- .../kotlin/entities/JsonStyleRealmObject.kt | 5 +- .../common/RealmAnyNestedCollectionTests.kt | 43 +- ...ealmAnyNestedCollectionNotificationTest.kt | 1 - ...ealmAnyNestedDictionaryNotificationTest.kt | 1 - .../RealmAnyNestedListNotificationTest.kt | 3 +- .../io/realm/kotlin/test/mongodb/TestApp.kt | 19 +- .../test/mongodb/util/AppServicesClient.kt | 288 +++++----- .../mongodb/util/KtorTestAppInitializer.kt | 71 --- .../test/mongodb/util/TestAppInitializer.kt | 502 +++++++++--------- .../test/mongodb/common/ApiKeyAuthTests.kt | 4 +- .../mongodb/common/AppConfigurationTests.kt | 11 +- .../kotlin/test/mongodb/common/AppTests.kt | 13 +- .../mongodb/common/AsymmetricSyncTests.kt | 4 +- .../test/mongodb/common/CredentialsTests.kt | 3 +- .../mongodb/common/EmailPasswordAuthTests.kt | 30 +- .../common/FLXProgressListenerTests.kt | 6 +- .../common/FlexibleSyncConfigurationTests.kt | 4 +- .../common/FlexibleSyncIntegrationTests.kt | 4 +- .../test/mongodb/common/FunctionsTests.kt | 40 +- .../test/mongodb/common/GeoSpatialTests.kt | 4 +- .../mongodb/common/HttpLogObfuscatorTests.kt | 48 +- .../common/MutableSubscriptionSetTests.kt | 4 +- .../common/PBSProgressListenerTests.kt | 6 +- .../common/SubscriptionExtensionsTests.kt | 4 +- .../mongodb/common/SubscriptionSetTests.kt | 8 +- .../test/mongodb/common/SubscriptionTests.kt | 3 +- .../common/SyncClientResetIntegrationTests.kt | 17 +- .../test/mongodb/common/SyncClientTests.kt | 3 +- .../test/mongodb/common/SyncConfigTests.kt | 3 +- .../test/mongodb/common/SyncSessionTests.kt | 3 +- .../test/mongodb/common/SyncedRealmTests.kt | 40 +- .../test/mongodb/common/UserProfileTests.kt | 2 + .../kotlin/test/mongodb/common/UserTests.kt | 3 +- .../internal/KtorNetworkTransportTest.kt | 67 ++- .../mongodb/common/mongo/MongoClientTests.kt | 4 +- .../common/mongo/MongoCollectionTests.kt | 4 +- .../common/mongo/MongoDatabaseTests.kt | 4 +- .../mongodb/common/nonlatin/NonLatinTests.kt | 3 +- .../kotlin/test/mongodb/jvm/RealmTests.kt | 3 +- 40 files changed, 667 insertions(+), 623 deletions(-) delete mode 100644 packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/KtorTestAppInitializer.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 92524284db..f6383607ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,9 +25,10 @@ * Minimum R8: 8.0.34. ### Internal -- None. +- Reworked test app initializer framework. + -## 2.0.1 (YYYY-MM-DD) +## 2.0.1-SNAPSHOT (YYYY-MM-DD) ### Breaking changes * None. diff --git a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt index 41f1afce62..bb8eb8a34a 100644 --- a/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt +++ b/packages/test-base/src/commonMain/kotlin/io/realm/kotlin/entities/JsonStyleRealmObject.kt @@ -23,9 +23,10 @@ import io.realm.kotlin.types.annotations.PrimaryKey import org.mongodb.kbson.ObjectId class JsonStyleRealmObject : RealmObject { + // Act as partition key to separate individual test runs + var selector: String = "DEFAULT" @PrimaryKey @PersistedName("_id") - var id: String = ObjectId().toHexString() - var selector: String = "DEFAULT" + var id: ObjectId = ObjectId() var value: RealmAny? = null } diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt index e13edb5c8f..f3a6fb0f7a 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/RealmAnyNestedCollectionTests.kt @@ -30,6 +30,7 @@ import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.test.common.utils.assertFailsWithMessage import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.types.RealmAny +import org.mongodb.kbson.ObjectId import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -527,26 +528,26 @@ class RealmAnyNestedCollectionTests { @Test fun query() = runBlocking { + var listId: ObjectId? = null + var dictId: ObjectId? = null + var embeddedId: ObjectId? = null realm.write { - copyToRealm( + listId = copyToRealm( JsonStyleRealmObject().apply { - id = "LIST" value = realmAnyListOf(4, 5, 6) } - ) - copyToRealm( + ).id + dictId = copyToRealm( JsonStyleRealmObject().apply { - id = "DICT" value = realmAnyDictionaryOf( "key1" to 7, "key2" to 8, "key3" to 9, ) } - ) - copyToRealm( + ).id + embeddedId = copyToRealm( JsonStyleRealmObject().apply { - id = "EMBEDDED" value = realmAnyListOf( listOf(4, 5, 6), mapOf( @@ -556,35 +557,35 @@ class RealmAnyNestedCollectionTests { ) ) } - ) + ).id } assertEquals(3, realm.query().find().size) // Matching lists realm.query("value[0] == 4").find().single().run { - assertEquals("LIST", id) + assertEquals(listId, id) } realm.query("value[*] == 4").find().single().run { - assertEquals("LIST", id) + assertEquals(listId, id) } realm.query("value[*] == {4, 5, 6}").find().single().run { - assertEquals("LIST", id) + assertEquals(listId, id) } // Matching dictionaries realm.query("value.key1 == 7").find().single().run { - assertEquals("DICT", id) + assertEquals(dictId, id) } realm.query("value['key1'] == 7").find().single().run { - assertEquals("DICT", id) + assertEquals(dictId, id) } realm.query("value[*] == 7").find().single().run { - assertEquals("DICT", id) + assertEquals(dictId, id) } assertEquals(0, realm.query("value.unknown == 3").find().size) realm.query("value.@keys == 'key1'").find().single().run { - assertEquals("DICT", id) + assertEquals(dictId, id) } assertEquals(0, realm.query("value.@keys == 'unknown'").find().size) @@ -593,19 +594,19 @@ class RealmAnyNestedCollectionTests { // Matching across all elements and in nested structures realm.query("value[*][*] == 4").find().single().run { - assertEquals("EMBEDDED", id) + assertEquals(embeddedId, id) } realm.query("value[*][*] == 7").find().single().run { - assertEquals("EMBEDDED", id) + assertEquals(embeddedId, id) } realm.query("value[*].@keys == 'key1'").find().single().run { - assertEquals("EMBEDDED", id) + assertEquals(embeddedId, id) } realm.query("value[*].key3[0] == 9").find().single().run { - assertEquals("EMBEDDED", id) + assertEquals(embeddedId, id) } realm.query("value[0][*] == {4, 5, 6}").find().single().run { - assertEquals("EMBEDDED", id) + assertEquals(embeddedId, id) } // FIXME Core issue https://github.com/realm/realm-core/issues/7393 // realm.query("value[*][*] == {4, 5, 6}").find().single().run { diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt index 9d1c69dc25..bc7e30da2d 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedCollectionNotificationTest.kt @@ -71,7 +71,6 @@ class RealmAnyNestedCollectionNotificationTest { val o: JsonStyleRealmObject = realm.write { copyToRealm( JsonStyleRealmObject().apply { - id = "SET" value = realmAnyListOf(realmAnyListOf(1, 2, 3)) } ) diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt index 0b25592dd4..baf7aee88a 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedDictionaryNotificationTest.kt @@ -84,7 +84,6 @@ class RealmAnyNestedDictionaryNotificationTest : FlowableTests, DeletableEntityN val o: JsonStyleRealmObject = realm.write { copyToRealm( JsonStyleRealmObject().apply { - id = "DICTIONARY" value = realmAnyDictionaryOf( "root" to realmAnyDictionaryOf( "key1" to 1, diff --git a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt index fab126948e..e3a263419e 100644 --- a/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt +++ b/packages/test-base/src/commonTest/kotlin/io/realm/kotlin/test/common/notifications/RealmAnyNestedListNotificationTest.kt @@ -84,7 +84,6 @@ class RealmAnyNestedListNotificationTest : FlowableTests, DeletableEntityNotific val o: JsonStyleRealmObject = realm.write { copyToRealm( JsonStyleRealmObject().apply { - id = "LIST" value = realmAnyListOf(realmAnyListOf(1, 2, 3)) } ) @@ -281,7 +280,7 @@ class RealmAnyNestedListNotificationTest : FlowableTests, DeletableEntityNotific val asList = findLatest(parent)!!.value!!.asList() println(asList.size) asList.add( - RealmAny.create(JsonStyleRealmObject().apply { id = "CHILD" }) + RealmAny.create(JsonStyleRealmObject()) ) } channel.receiveOrFail(message = "List add").let { diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt index ab221414d6..079ab3b377 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt @@ -32,10 +32,8 @@ import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.internal.AppConfigurationImpl import io.realm.kotlin.test.mongodb.util.AppAdmin import io.realm.kotlin.test.mongodb.util.AppAdminImpl +import io.realm.kotlin.test.mongodb.util.AppInitializer import io.realm.kotlin.test.mongodb.util.AppServicesClient -import io.realm.kotlin.test.mongodb.util.BaasApp -import io.realm.kotlin.test.mongodb.util.Service -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.initializeDefault import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.TestHelper import kotlinx.coroutines.CloseableCoroutineDispatcher @@ -91,27 +89,23 @@ open class TestApp private constructor( @OptIn(ExperimentalKBsonSerializerApi::class, ExperimentalCoroutinesApi::class) constructor( testId: String?, - appName: String = TEST_APP_PARTITION, + appInitializer: AppInitializer, dispatcher: CoroutineDispatcher = singleThreadDispatcher("$testId-dispatcher"), builder: (AppConfiguration.Builder) -> AppConfiguration.Builder = { - it.syncRootDirectory(PlatformUtils.createTempDir("$appName-$testId")) + it.syncRootDirectory(PlatformUtils.createTempDir("${appInitializer.name}-$testId")) }, debug: Boolean = false, networkTransport: NetworkTransport? = null, ejson: EJson = EJson, - initialSetup: suspend AppServicesClient.(app: BaasApp, service: Service) -> Unit = { app: BaasApp, service: Service -> - initializeDefault(app, service) - } ) : this( dispatcher, build( debug = debug, - appName = appName, + appInitializer = appInitializer, dispatcher = dispatcher, builder = builder, networkTransport = networkTransport, ejson = ejson, - initialSetup = initialSetup ) ) @@ -172,12 +166,11 @@ open class TestApp private constructor( @Suppress("LongParameterList") fun build( debug: Boolean, - appName: String, + appInitializer: AppInitializer, dispatcher: CoroutineDispatcher, builder: (AppConfiguration.Builder) -> AppConfiguration.Builder, networkTransport: NetworkTransport?, ejson: EJson, - initialSetup: suspend AppServicesClient.(app: BaasApp, service: Service) -> Unit ): Pair { val appAdmin: AppAdmin = runBlocking(dispatcher) { AppServicesClient.build( @@ -185,7 +178,7 @@ open class TestApp private constructor( debug = debug, dispatcher = dispatcher ).run { - val baasApp = getOrCreateApp(appName, initialSetup) + val baasApp = getOrCreateApp(appInitializer) AppAdminImpl(this, baasApp) } diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt index eb6e02c732..8f151ab5a9 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt @@ -42,7 +42,6 @@ import io.realm.kotlin.mongodb.sync.SyncMode import io.realm.kotlin.schema.RealmClassKind import io.realm.kotlin.test.mongodb.SyncServerConfig import io.realm.kotlin.test.mongodb.TEST_APP_CLUSTER_NAME -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.initialize import io.realm.kotlin.types.BaseRealmObject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.delay @@ -77,6 +76,7 @@ import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds private const val ADMIN_PATH = "/api/admin/v3.0" +private const val PRIVATE_PATH = "/api/private/v1.0" data class SyncPermissions( val read: Boolean, @@ -327,8 +327,17 @@ data class BaasApp( @SerialName("client_app_id") val clientAppId: String, val name: String, @SerialName("domain_id") val domainId: String, - @SerialName("group_id") val groupId: String -) + @SerialName("group_id") val groupId: String, + + @Transient private val _client: AppServicesClient? = null +) { + val client: AppServicesClient + get() = _client ?: TODO("App should be copy'ed with _client set to the AppServicesClient that retrieved it") + val url: String + get() = client.baseUrl + ADMIN_PATH + "/groups/${this.groupId}/apps/${this._id}" + val privateUrl: String + get() = client.baseUrl + PRIVATE_PATH + "/groups/${this.groupId}/apps/${this._id}" +} /** * Client to interact with App Services Server. It allows to create Applications and tweak their @@ -336,147 +345,34 @@ data class BaasApp( */ class AppServicesClient( val baseUrl: String, - private val groupUrl: String, - private val httpClient: HttpClient, + private val groupId: String, + internal val httpClient: HttpClient, val dispatcher: CoroutineDispatcher, ) { - companion object { - // Default serializer fails with - // InvalidMutabilityException: mutation attempt of frozen kotlin.collections.HashMap - // on native. Have tried the various workarounds from - // https://github.com/Kotlin/kotlinx.serialization/issues/1450 - // but only one that works is manual invoking the deserializer - @OptIn(InternalSerializationApi::class) - private suspend inline fun HttpClient.typedRequest( - method: HttpMethod, - url: String, - crossinline block: HttpRequestBuilder.() -> Unit = {} - ): T { - val response: HttpResponse = this@typedRequest.request(url) { - this.method = method - this.apply(block) - } - if (!response.status.isSuccess()) { - throw IllegalStateException("Http request failed: $url. ${response.status}: ${response.bodyAsText()}") - } - return response.bodyAsText() - .let { - Json { ignoreUnknownKeys = true }.decodeFromString( - T::class.serializer(), - it - ) - } - } - - // Default serializer fails with - // InvalidMutabilityException: mutation attempt of frozen kotlin.collections.HashMap - // on native. Have tried the various workarounds from - // https://github.com/Kotlin/kotlinx.serialization/issues/1450 - // but only one that works is manual invoking the deserializer - @OptIn(InternalSerializationApi::class) - private suspend inline fun HttpClient.typedListRequest( - method: HttpMethod, - url: String, - crossinline block: HttpRequestBuilder.() -> Unit = {} - ): List { - return this@typedListRequest.request(url) { - this.method = method - this.apply(block) - }.bodyAsText() - .let { - Json { ignoreUnknownKeys = true }.decodeFromString( - ListSerializer(T::class.serializer()), - it - ) - } - } - - suspend fun build( - debug: Boolean, - baseUrl: String, - dispatcher: CoroutineDispatcher, - ): AppServicesClient { - val adminUrl = baseUrl + ADMIN_PATH - // Work around issues on Native with the Ktor client being created and used - // on different threads. - // Log in using unauthorized client - val unauthorizedClient = defaultClient("realm-baas-unauthorized", debug) - - var loginMethod: String = "local-userpass" - var json: Map = mapOf("username" to SyncServerConfig.email, "password" to SyncServerConfig.password) - if (SyncServerConfig.publicApiKey.isNotEmpty()) { - loginMethod = "mongodb-cloud" - json = mapOf("username" to SyncServerConfig.publicApiKey, "apiKey" to SyncServerConfig.privateApiKey) - } - val loginResponse = unauthorizedClient.typedRequest( - HttpMethod.Post, - "$adminUrl/auth/providers/$loginMethod/login" - ) { - contentType(ContentType.Application.Json) - setBody(json) - } - - // Setup authorized client for the rest of the requests - val accessToken = loginResponse.access_token - unauthorizedClient.close() - val httpClient = defaultClient("realm-baas-authorized", debug) { - expectSuccess = true - - defaultRequest { - headers { - append("Authorization", "Bearer $accessToken") - } - } - install(ContentNegotiation) { - json( - Json { - prettyPrint = true - isLenient = true - } - ) - } - install(Logging) { - // Set to LogLevel.ALL to debug Admin API requests. All relevant - // data for each request/response will be console or LogCat. - level = LogLevel.INFO - } - } - - // Collect app group id - val groupId = httpClient.typedRequest(Get, "$adminUrl/auth/profile") - .roles - .first() - .group_id - - return AppServicesClient( - baseUrl, - "$adminUrl/groups/$groupId", - httpClient, - dispatcher - ) - } - } + val groupUrl: String + get() = baseUrl + ADMIN_PATH + "/groups/$groupId" fun closeClient() { httpClient.close() } - suspend fun getOrCreateApp( - appName: String, - initializer: suspend AppServicesClient.(app: BaasApp, service: Service) -> Unit - ): BaasApp = - getApp(appName) ?: createApp(appName) { - initialize(this, initializer) + suspend fun getOrCreateApp(appInitializer: AppInitializer): BaasApp { + val app = getApp(appInitializer.name) + return app ?: createApp(appInitializer.name) { + appInitializer.initialize(this@AppServicesClient, this) } + } - private suspend fun getApp(appName: String): BaasApp? = - withContext(dispatcher) { + private suspend fun getApp(appName: String): BaasApp? { + val withContext = withContext(dispatcher) { httpClient.typedListRequest(Get, "$groupUrl/apps") .firstOrNull { it.name == appName - } + }?.copy(_client = this@AppServicesClient) } + return withContext + } private suspend fun createApp( appName: String, @@ -489,7 +385,7 @@ class AppServicesClient( httpClient.typedRequest(Post, "$groupUrl/apps") { setBody(Json.parseToJsonElement("""{"name": $appName}""")) contentType(ContentType.Application.Json) - }.apply { + }.copy(_client = this@AppServicesClient).apply { initializer(this) } } @@ -536,6 +432,17 @@ class AppServicesClient( val BaasApp.url: String get() = "$groupUrl/apps/${this._id}" + suspend fun BaasApp.toggleFeatures(features: Set, enable: Boolean) { + withContext(dispatcher) { + httpClient.typedRequest( + Post, + "$privateUrl/features" + ) { + setBody(Json.parseToJsonElement("""{ "action": "${if (enable) "enable" else "disable"}", "feature_flags": [ ${features.joinToString { "\"$it\"" }} ] }""")) + contentType(ContentType.Application.Json) + } + } + } suspend fun BaasApp.addFunction(function: Function): Function = withContext(dispatcher) { @@ -732,7 +639,7 @@ class AppServicesClient( .first { val type = if (TEST_APP_CLUSTER_NAME.isEmpty()) "mongodb" else "mongodb-atlas" it.type == type - } + }.copy(app = this@mongodbService) } } @@ -965,4 +872,121 @@ class AppServicesClient( } } } + companion object { + suspend fun build( + debug: Boolean, + baseUrl: String, + dispatcher: CoroutineDispatcher, + ): AppServicesClient { + val adminUrl = baseUrl + ADMIN_PATH + // Work around issues on Native with the Ktor client being created and used + // on different threads. + // Log in using unauthorized client + val unauthorizedClient = defaultClient("realm-baas-unauthorized", debug) + + var loginMethod: String = "local-userpass" + var json: Map = mapOf("username" to SyncServerConfig.email, "password" to SyncServerConfig.password) + if (SyncServerConfig.publicApiKey.isNotEmpty()) { + loginMethod = "mongodb-cloud" + json = mapOf("username" to SyncServerConfig.publicApiKey, "apiKey" to SyncServerConfig.privateApiKey) + } + val loginResponse = unauthorizedClient.typedRequest( + HttpMethod.Post, + "$adminUrl/auth/providers/$loginMethod/login" + ) { + contentType(ContentType.Application.Json) + setBody(json) + } + + // Setup authorized client for the rest of the requests + val accessToken = loginResponse.access_token + unauthorizedClient.close() + + val httpClient = defaultClient("realm-baas-authorized", debug) { + expectSuccess = true + + defaultRequest { + headers { + append("Authorization", "Bearer $accessToken") + } + } + install(ContentNegotiation) { + json( + Json { + prettyPrint = true + isLenient = true + } + ) + } + install(Logging) { + // Set to LogLevel.ALL to debug Admin API requests. All relevant + // data for each request/response will be console or LogCat. + level = LogLevel.ALL + } + } + + // Collect app group id + val groupId = httpClient.typedRequest(Get, "$adminUrl/auth/profile") + .roles + .first() + .group_id ?: "null" + + return AppServicesClient( + baseUrl, + groupId, + httpClient, + dispatcher + ) + } + } +} + +// Default serializer fails with +// InvalidMutabilityException: mutation attempt of frozen kotlin.collections.HashMap +// on native. Have tried the various workarounds from +// https://github.com/Kotlin/kotlinx.serialization/issues/1450 +// but only one that works is manual invoking the deserializer +@OptIn(InternalSerializationApi::class) +private suspend inline fun HttpClient.typedRequest( + method: HttpMethod, + url: String, + crossinline block: HttpRequestBuilder.() -> Unit = {} +): T { + val response: HttpResponse = this@typedRequest.request(url) { + this.method = method + this.apply(block) + } + if (!response.status.isSuccess()) { + throw IllegalStateException("Http request failed: $url. ${response.status}: ${response.bodyAsText()}") + } + return response.bodyAsText() + .let { + Json { ignoreUnknownKeys = true }.decodeFromString( + T::class.serializer(), + it + ) + } +} + +// Default serializer fails with +// InvalidMutabilityException: mutation attempt of frozen kotlin.collections.HashMap +// on native. Have tried the various workarounds from +// https://github.com/Kotlin/kotlinx.serialization/issues/1450 +// but only one that works is manual invoking the deserializer +@OptIn(InternalSerializationApi::class) +private suspend inline fun HttpClient.typedListRequest( + method: HttpMethod, + url: String, + crossinline block: HttpRequestBuilder.() -> Unit = {} +): List { + return this@typedListRequest.request(url) { + this.method = method + this.apply(block) + }.bodyAsText() + .let { + Json { ignoreUnknownKeys = true }.decodeFromString( + ListSerializer(T::class.serializer()), + it + ) + } } diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/KtorTestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/KtorTestAppInitializer.kt deleted file mode 100644 index 7abcb1333a..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/KtorTestAppInitializer.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.realm.kotlin.test.mongodb.util - -import io.ktor.http.HttpMethod - -object KtorTestAppInitializer { - - // Setups the app with the functions and https endpoints required to run the KtorNetworkTransportTests - suspend fun AppServicesClient.initialize(app: BaasApp, methods: List) = - with(app) { - // We have to create a function per method because the request parameter does not - // has what method triggered it. - methods.forEach { httpMethod: HttpMethod -> - val method = httpMethod.value - val function = addFunction( - Function( - name = "test_network_transport_$method", - runAsSystem = true, - source = - """ - exports = async function (request, response) { - response.setHeader('Content-Type', 'text/plain'); - let isSuccess = request.query["success"] == "true"; - - if (isSuccess) { - response.setStatusCode(200); - response.setBody("$method-success"); - } else { - response.setStatusCode(500); - response.setBody("$method-failure"); - } - } - """.trimIndent() - ) - ) - - addEndpoint( - """ - { - "route": "/test_network_transport", - "function_name": "${function.name}", - "function_id": "${function._id}", - "http_method": "$method", - "validation_method": "NO_VALIDATION", - "secret_id": "", - "secret_name": "", - "create_user_on_auth": false, - "fetch_custom_user_data": false, - "respond_result": false, - "disabled": false, - "return_type": "JSON" - } - """.trimIndent() - ) - } - } -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt index 5116d6e406..5647d6f32c 100644 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt +++ b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt @@ -22,29 +22,128 @@ import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA import io.realm.kotlin.test.mongodb.common.PARTITION_BASED_SCHEMA import kotlinx.serialization.json.Json -object TestAppInitializer { - // Setups a test app - suspend fun AppServicesClient.initializeDefault(app: BaasApp, service: Service) { - addEmailProvider(app) - - when (app.name) { - TEST_APP_PARTITION -> initializePartitionSync(app, service) - TEST_APP_FLEX -> initializeFlexibleSync(app, service) +interface AppInitializer { + val name: String + suspend fun initialize(client: AppServicesClient, app: BaasApp) +} + +open class BaseAppInitializer( + override val name: String, + val block: (suspend AppServicesClient.(app: BaasApp) -> Unit)? = null, +) : AppInitializer { + + override suspend fun initialize(client: AppServicesClient, app: BaasApp) { + with(client) { + with(app) { + addFunction(insertDocument) + addFunction(queryDocument) + addFunction(deleteDocument) + addFunction(countDocuments) + + val testAuthFuncId = addFunction(testAuthFunc)._id + addAuthProvider( + """ + { + "type": "custom-function", + "config": { + "authFunctionId": "$testAuthFuncId", + "authFunctionName": "${testAuthFunc.name}" + } + } + """.trimIndent() + ) + + addAuthProvider("""{"type": "anon-user"}""") + + // Enable 'API-KEY' by updating it. It exists by default in the server so we cannot add. + getAuthProvider("api-key").run { + enable(true) + } + + val (type: String, config: String) = if (TEST_APP_CLUSTER_NAME.isEmpty()) { + "mongodb" to """{ "uri": "mongodb://localhost:26000" }""" + } else { + "mongodb-atlas" to """{ "clusterName": "$TEST_APP_CLUSTER_NAME" }""" + } + addService( + """ + { + "name": "BackingDB", + "type": "$type", + "config": $config + } + """.trimIndent() + ).let { service: Service -> + val dbName = app.clientAppId + service.addDefaultRule( + """ + { + "roles": [{ + "name": "defaultRole", + "apply_when": {}, + "document_filters": { + "read": true, + "write": true + }, + "write": true, + "read": true, + "insert": true, + "delete": true + }] + } + """.trimIndent() + ) + + app.setCustomUserData( + """ + { + "mongo_service_id": ${service._id}, + "enabled": true, + "database_name": "$dbName", + "collection_name": "UserData", + "user_id_field": "user_id" + } + """.trimIndent() + ) + } + with(client) { + block?.invoke(this, app) + } + app.setDevelopmentMode(true) + } } } +} - @Suppress("LongMethod") - suspend fun AppServicesClient.initializeFlexibleSync( - app: BaasApp, - service: Service, - recoveryDisabled: Boolean = false - ) { - val databaseName = app.clientAppId +object DefaultPartitionBasedAppInitializer : + BaseAppInitializer( + TEST_APP_PARTITION, + { app -> + addEmailProvider(app) + initializePartitionSync(app) + } + ) - app.setSchema(FLEXIBLE_SYNC_SCHEMA) +object DefaultFlexibleSyncAppInitializer : + BaseAppInitializer( + TEST_APP_FLEX, + { app -> + addEmailProvider(app) + initializeFlexibleSync(app) + } + ) + +@Suppress("LongMethod") +suspend fun AppServicesClient.initializeFlexibleSync( + app: BaasApp, + recoveryDisabled: Boolean = false, // TODO +) { + val databaseName = app.clientAppId - service.setSyncConfig( - """ + app.setSchema(FLEXIBLE_SYNC_SCHEMA) + + app.mongodbService.setSyncConfig( + """ { "flexible_sync": { "state": "enabled", @@ -63,32 +162,30 @@ object TestAppInitializer { ] } } - """.trimIndent() - ) + """.trimIndent() + ) + app.waitUntilInitialSyncCompletes() +} - app.waitUntilInitialSyncCompletes() - } +@Suppress("LongMethod") +suspend fun AppServicesClient.initializePartitionSync( + app: BaasApp, + recoveryDisabled: Boolean = false, // TODO +) { + val databaseName = app.clientAppId + + app.addFunction(canReadPartition) + app.addFunction(canWritePartition) - @Suppress("LongMethod") - suspend fun AppServicesClient.initializePartitionSync( - app: BaasApp, - service: Service, - recoveryDisabled: Boolean = false // TODO - ) { - val databaseName = app.clientAppId - - app.addFunction(canReadPartition) - app.addFunction(canWritePartition) - - app.setSchema( - schema = PARTITION_BASED_SCHEMA, - extraProperties = mapOf( - "realm_id" to PrimitivePropertyType.Type.STRING - ) + app.setSchema( + schema = PARTITION_BASED_SCHEMA, + extraProperties = mapOf( + "realm_id" to PrimitivePropertyType.Type.STRING ) + ) - service.setSyncConfig( - """ + app.mongodbService.setSyncConfig( + """ { "sync": { "state": "enabled", @@ -122,22 +219,22 @@ object TestAppInitializer { } } } - """.trimIndent() - ) + """.trimIndent() + ) - app.waitUntilInitialSyncCompletes() - } + app.waitUntilInitialSyncCompletes() +} - suspend fun AppServicesClient.addEmailProvider( - app: BaasApp, - autoConfirm: Boolean = true, - runConfirmationFunction: Boolean = false - ) = with(app) { - val confirmFuncId = addFunction(confirmFunc)._id - val resetFuncId = addFunction(resetFunc)._id +suspend fun AppServicesClient.addEmailProvider( + app: BaasApp, + autoConfirm: Boolean = true, + runConfirmationFunction: Boolean = false, +) = with(app) { + val confirmFuncId = addFunction(confirmFunc)._id + val resetFuncId = addFunction(resetFunc)._id - addAuthProvider( - """ + addAuthProvider( + """ { "type": "local-userpass", "config": { @@ -153,93 +250,14 @@ object TestAppInitializer { "runResetFunction": false } } - """.trimIndent() - ) - } - - suspend fun AppServicesClient.initialize( - app: BaasApp, - block: suspend AppServicesClient.(app: BaasApp, service: Service) -> Unit - ) = with(app) { - addFunction(insertDocument) - addFunction(queryDocument) - addFunction(deleteDocument) - addFunction(countDocuments) - - val testAuthFuncId = addFunction(testAuthFunc)._id - addAuthProvider( - """ - { - "type": "custom-function", - "config": { - "authFunctionId": "$testAuthFuncId", - "authFunctionName": "${testAuthFunc.name}" - } - } - """.trimIndent() - ) - - addAuthProvider("""{"type": "anon-user"}""") - - // Enable 'API-KEY' by updating it. It exists by default in the server so we cannot add. - getAuthProvider("api-key").run { - enable(true) - } - - val (type: String, config: String) = if (TEST_APP_CLUSTER_NAME.isEmpty()) { - "mongodb" to """{ "uri": "mongodb://localhost:26000" }""" - } else { - "mongodb-atlas" to """{ "clusterName": "$TEST_APP_CLUSTER_NAME" }""" - } - addService( - """ - { - "name": "BackingDB", - "type": "$type", - "config": $config - } - """.trimIndent() - ).let { service: Service -> - val dbName = app.clientAppId - service.addDefaultRule( - """ - { - "roles": [{ - "name": "defaultRole", - "apply_when": {}, - "document_filters": { - "read": true, - "write": true - }, - "write": true, - "read": true, - "insert": true, - "delete": true - }] - } - """.trimIndent() - ) - - app.setCustomUserData( - """ - { - "mongo_service_id": ${service._id}, - "enabled": true, - "database_name": "$dbName", - "collection_name": "UserData", - "user_id_field": "user_id" - } - """.trimIndent() - ) - - block(app, service) - } - } + """.trimIndent() + ) +} - private val insertDocument = Function( - name = "insertDocument", - source = - """ +private val insertDocument = Function( + name = "insertDocument", + source = + """ exports = function (service, db, collection, document) { const mongodb = context.services.get(service); const result = mongodb @@ -250,13 +268,13 @@ object TestAppInitializer { return result; } - """.trimIndent() - ) + """.trimIndent() +) - private val deleteDocument = Function( - name = "deleteDocument", - source = - """ +private val deleteDocument = Function( + name = "deleteDocument", + source = + """ exports = function (service, db, collection, query) { const mongodb = context.services.get(service); const result = mongodb @@ -267,13 +285,13 @@ object TestAppInitializer { return result; } - """.trimIndent() - ) + """.trimIndent() +) - private val queryDocument = Function( - name = "queryDocument", - source = - """ +private val queryDocument = Function( + name = "queryDocument", + source = + """ exports = function (service, db, collection, query) { const mongodb = context.services.get(service); const result = mongodb @@ -283,14 +301,13 @@ object TestAppInitializer { return result; } - - """.trimIndent() - ) + """.trimIndent() +) - private val countDocuments = Function( - name = "countDocuments", - source = - """ +private val countDocuments = Function( + name = "countDocuments", + source = + """ exports = function (service, db, collection) { const mongodb = context.services.get(service); const result = mongodb @@ -300,13 +317,13 @@ object TestAppInitializer { return { value: result }; } - """.trimIndent() - ) + """.trimIndent() +) - private val testAuthFunc = Function( - name = "testAuthFunc", - source = - """ +private val testAuthFunc = Function( + name = "testAuthFunc", + source = + """ exports = ({mail, id}) => { // Auth function will fail for emails with a domain different to @10gen.com // or with id lower than 666 @@ -317,14 +334,13 @@ object TestAppInitializer { return mail; } } - - """.trimIndent() - ) + """.trimIndent() +) - private val confirmFunc = Function( - name = "confirmFunc", - source = - """ +private val confirmFunc = Function( + name = "confirmFunc", + source = + """ exports = async ({ token, tokenId, username }) => { // process the confirm token, tokenId and username @@ -350,14 +366,13 @@ object TestAppInitializer { return { status: 'fail' }; } } - - """.trimIndent() - ) + """.trimIndent() +) - private val resetFunc = Function( - name = "resetFunc", - source = - """ +private val resetFunc = Function( + name = "resetFunc", + source = + """ exports = ({ token, tokenId, username, password }, customParam1, customParam2) => { if (customParam1 != "say-the-magic-word" || customParam2 != 42) { return { status: 'fail' }; @@ -365,14 +380,13 @@ object TestAppInitializer { return { status: 'success' }; } } - - """.trimIndent() - ) + """.trimIndent() +) - private val canReadPartition = Function( - name = "canReadPartition", - source = - """ +private val canReadPartition = Function( + name = "canReadPartition", + source = + """ /** * Users with an email that contains `_noread_` do not have read access, * all others do. @@ -385,90 +399,84 @@ object TestAppInitializer { return true; } } - - """.trimIndent() - ) + """.trimIndent() +) - private val canWritePartition = Function( +private val canWritePartition = + Function( name = "canWritePartition", source = """ - /** - * Users with an email that contains `_nowrite_` do not have write access, - * all others do. - */ - exports = async (partition) => { - const email = context.user.data.email; - if (email != undefined) { - return(!email.includes("_nowrite_")); - } else { - return true; - } - } - + /** + * Users with an email that contains `_nowrite_` do not have write access, + * all others do. + */ + exports = async (partition) => { + const email = context.user.data.email; + if (email != undefined) { + return(!email.includes("_nowrite_")); + } else { + return true; + } + } """.trimIndent() ) - val FIRST_ARG_FUNCTION = Function( - name = "firstArg", - source = - """ +val FIRST_ARG_FUNCTION = Function( + name = "firstArg", + source = + """ exports = function(arg){ // Returns first argument return arg }; - - """.trimIndent() - ) + """.trimIndent() +) - val SUM_FUNCTION = Function( - name = "sum", - source = - """ +val SUM_FUNCTION = Function( + name = "sum", + source = + """ exports = function(...args) { return parseInt(args.reduce((a,b) => a + b, 0)); }; - - """.trimIndent() - ) + """.trimIndent() +) - val NULL_FUNCTION = Function( - name = "null", - source = - """ +val NULL_FUNCTION = Function( + name = "null", + source = + """ exports = function(arg){ return null; }; - - """.trimIndent() - ) + """.trimIndent() +) - val ERROR_FUNCTION = Function( - name = "error", - source = - """ +val ERROR_FUNCTION = Function( + name = "error", + source = + """ exports = function(arg){ return unknown; }; - - """.trimIndent() - ) + """.trimIndent() +) - val VOID_FUNCTION = Function( - name = "void", - source = - """ +val VOID_FUNCTION = Function( + name = "void", + source = + """ exports = function(arg){ return void(0); }; - - """.trimIndent() - ) + """.trimIndent() +) - val AUTHORIZED_ONLY_FUNCTION = Function( - name = "authorizedOnly", - source = - """ +val AUTHORIZED_ONLY_FUNCTION = Function( + name = "authorizedOnly", + source = + """ exports = function(arg){ /* Accessing application's values: @@ -485,10 +493,9 @@ object TestAppInitializer { */ return {arg: context.user}; }; - - """.trimIndent(), - canEvaluate = Json.decodeFromString( - """ + """.trimIndent(), + canEvaluate = Json.decodeFromString( + """ { "%%user.data.email": { "%in": [ @@ -496,7 +503,6 @@ object TestAppInitializer { ] } } - """.trimIndent() - ) + """.trimIndent() ) -} +) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/ApiKeyAuthTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/ApiKeyAuthTests.kt index 6b4e3f0add..0c6a6e30d7 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/ApiKeyAuthTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/ApiKeyAuthTests.kt @@ -19,9 +19,9 @@ import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.auth.ApiKeyAuth import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.util.TestHelper import org.mongodb.kbson.BsonObjectId import kotlin.test.AfterTest @@ -50,7 +50,7 @@ class ApiKeyAuthTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = TEST_APP_PARTITION) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) user = app.createUserAndLogin() provider = user.apiKeyAuth } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppConfigurationTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppConfigurationTests.kt index c0c23ef492..0400b24695 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppConfigurationTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppConfigurationTests.kt @@ -34,6 +34,7 @@ import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.test.mongodb.createUserAndLogIn import io.realm.kotlin.test.mongodb.use +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.TestChannel import io.realm.kotlin.test.util.TestHelper @@ -167,9 +168,11 @@ class AppConfigurationTests { @Test fun syncRootDirectory_appendDirectoryToPath() = runBlocking { val expectedRoot = pathOf(appFilesDirectory(), "myCustomDir") - TestApp("syncRootDirectory_appendDirectoryToPath", builder = { - it.syncRootDirectory(expectedRoot) - }).use { app -> + TestApp( + "syncRootDirectory_appendDirectoryToPath", + DefaultPartitionBasedAppInitializer, + builder = { it.syncRootDirectory(expectedRoot) } + ).use { app -> val (email, password) = TestHelper.randomEmail() to "password1234" val user = app.createUserAndLogIn(email, password) assertEquals(expectedRoot, app.configuration.syncRootDirectory) @@ -387,6 +390,8 @@ class AppConfigurationTests { fun customHeadersTest() = runBlocking { TestApp( "customHeadersTest", + DefaultPartitionBasedAppInitializer, + debug = true, builder = { builder -> builder.customRequestHeaders { put(CUSTOM_HEADER_NAME, CUSTOM_HEADER_VALUE) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppTests.kt index ee9a0e90bc..373678ac1c 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppTests.kt @@ -36,12 +36,13 @@ import io.realm.kotlin.mongodb.exceptions.AuthException import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.test.mongodb.SyncServerConfig -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.test.mongodb.createUserAndLogIn import io.realm.kotlin.test.mongodb.use +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.TestChannel import io.realm.kotlin.test.util.TestHelper @@ -68,7 +69,7 @@ class AppTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) } @AfterTest @@ -382,7 +383,7 @@ class AppTests { val key = TestHelper.getRandomKey() TestApp( "encryptedMetadataRealm", - appName = TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, builder = { it .encryptionKey(key) @@ -421,7 +422,7 @@ class AppTests { val correctKey = TestHelper.getRandomKey() TestApp( "encryptedMetadataRealm_openWithWrongKeyThrows", - appName = TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, builder = { it .encryptionKey(correctKey) @@ -461,7 +462,7 @@ class AppTests { // Create new test app with a random encryption key TestApp( "encryptedMetadataRealm_openWithoutKeyThrows", - appName = TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, builder = { it .encryptionKey(TestHelper.getRandomKey()) @@ -502,6 +503,7 @@ class AppTests { fun changeBaseUrl() { TestApp( testId = "changeBaseUrl", + DefaultPartitionBasedAppInitializer, builder = { builder -> // We create a test app that points to the default base url // this app is not going to be validated yet. @@ -527,6 +529,7 @@ class AppTests { fun changeBaseUrl_null() { TestApp( testId = "changeBaseUrl", + DefaultPartitionBasedAppInitializer, ).use { testApp -> assertEquals(SyncServerConfig.url, testApp.baseUrl) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AsymmetricSyncTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AsymmetricSyncTests.kt index 1e2675c481..fbdddadf81 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AsymmetricSyncTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AsymmetricSyncTests.kt @@ -30,10 +30,10 @@ import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.mongodb.syncSession import io.realm.kotlin.schema.RealmClassKind import io.realm.kotlin.test.StandaloneDynamicMutableRealm -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.use import kotlinx.atomicfu.atomic @@ -56,7 +56,7 @@ class AsymmetricSyncTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX) + app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) val (email, password) = TestHelper.randomEmail() to "password1234" val user = runBlocking { app.createUserAndLogIn(email, password) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/CredentialsTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/CredentialsTests.kt index 53f3bf8fb8..75e1e3f828 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/CredentialsTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/CredentialsTests.kt @@ -35,6 +35,7 @@ import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.util.TestHelper import kotlinx.serialization.Serializable import kotlin.test.AfterTest @@ -60,7 +61,7 @@ class CredentialsTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) } @AfterTest diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/EmailPasswordAuthTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/EmailPasswordAuthTests.kt index 40ee4d0ac1..39f123121c 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/EmailPasswordAuthTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/EmailPasswordAuthTests.kt @@ -10,13 +10,13 @@ import io.realm.kotlin.mongodb.exceptions.ServiceException import io.realm.kotlin.mongodb.exceptions.UserAlreadyConfirmedException import io.realm.kotlin.mongodb.exceptions.UserAlreadyExistsException import io.realm.kotlin.mongodb.exceptions.UserNotFoundException -import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.syncServerAppName import io.realm.kotlin.test.mongodb.util.BaasApp -import io.realm.kotlin.test.mongodb.util.Service -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.addEmailProvider +import io.realm.kotlin.test.mongodb.util.BaseAppInitializer +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer +import io.realm.kotlin.test.mongodb.util.addEmailProvider import io.realm.kotlin.test.util.TestHelper import kotlin.test.AfterTest import kotlin.test.BeforeTest @@ -32,7 +32,7 @@ class EmailPasswordAuthWithAutoConfirmTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = TEST_APP_PARTITION) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) } @AfterTest @@ -243,9 +243,13 @@ class EmailPasswordAuthWithEmailConfirmTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = syncServerAppName("em-cnfrm"), initialSetup = { app: BaasApp, service: Service -> - addEmailProvider(app, autoConfirm = false) - }) + app = TestApp( + this::class.simpleName, + object : BaseAppInitializer( + syncServerAppName("em-cnfrm"), + { app: BaasApp -> addEmailProvider(app, autoConfirm = false) } + ) {} + ) } @AfterTest @@ -281,9 +285,15 @@ class EmailPasswordAuthWithCustomFunctionTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = syncServerAppName("em-cstm"), initialSetup = { app: BaasApp, service: Service -> - addEmailProvider(app, autoConfirm = false, runConfirmationFunction = true) - }) + app = TestApp( + this::class.simpleName, + object : BaseAppInitializer( + syncServerAppName("em-cstm"), + { app -> + addEmailProvider(app, autoConfirm = false, runConfirmationFunction = true) + } + ) {} + ) } @AfterTest diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FLXProgressListenerTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FLXProgressListenerTests.kt index 9fe4307aa6..9c67b39196 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FLXProgressListenerTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FLXProgressListenerTests.kt @@ -26,10 +26,10 @@ import io.realm.kotlin.mongodb.sync.Progress import io.realm.kotlin.mongodb.sync.ProgressMode import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.createUserAndLogIn import io.realm.kotlin.test.mongodb.use +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer import io.realm.kotlin.test.util.TestChannel import io.realm.kotlin.test.util.receiveOrFail import io.realm.kotlin.test.util.use @@ -66,7 +66,7 @@ class FLXProgressListenerTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX) + app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) partitionValue = org.mongodb.kbson.ObjectId().toString() } @@ -233,7 +233,7 @@ class FLXProgressListenerTests { @Test fun completesOnClose() = runBlocking { val channel = TestChannel(capacity = 5, onBufferOverflow = BufferOverflow.DROP_OLDEST, failIfBufferIsEmptyOnCancel = false) - TestApp("completesOnClose", TEST_APP_FLEX).use { app -> + TestApp("completesOnClose", DefaultFlexibleSyncAppInitializer).use { app -> val user = app.createUserAndLogIn() val realm = Realm.open(createSyncConfig(user)) try { diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncConfigurationTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncConfigurationTests.kt index be13d1f51f..337a520fae 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncConfigurationTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncConfigurationTests.kt @@ -24,10 +24,10 @@ import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.sync.InitialSubscriptionsCallback import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.mongodb.sync.SyncMode -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer import io.realm.kotlin.test.util.TestHelper import kotlinx.atomicfu.atomic import kotlin.test.AfterTest @@ -47,7 +47,7 @@ class FlexibleSyncConfigurationTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX) + app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) val (email, password) = TestHelper.randomEmail() to "password1234" val user = runBlocking { app.createUserAndLogIn(email, password) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncIntegrationTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncIntegrationTests.kt index 640c777eee..6951cab536 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncIntegrationTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncIntegrationTests.kt @@ -29,11 +29,11 @@ import io.realm.kotlin.mongodb.subscriptions import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.mongodb.sync.SyncSession import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail import io.realm.kotlin.test.mongodb.common.utils.waitForSynchronizationOrFail import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer import io.realm.kotlin.test.util.TestChannel import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.receiveOrFail @@ -63,7 +63,7 @@ class FlexibleSyncIntegrationTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX) + app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) val (email, password) = TestHelper.randomEmail() to "password1234" runBlocking { app.createUserAndLogIn(email, password) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt index 747b51c812..20646dd322 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt @@ -40,15 +40,14 @@ import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.test.mongodb.createUserAndLogIn import io.realm.kotlin.test.mongodb.syncServerAppName -import io.realm.kotlin.test.mongodb.util.BaasApp -import io.realm.kotlin.test.mongodb.util.Service -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.AUTHORIZED_ONLY_FUNCTION -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.ERROR_FUNCTION -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.FIRST_ARG_FUNCTION -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.NULL_FUNCTION -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.SUM_FUNCTION -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.VOID_FUNCTION -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.initializeDefault +import io.realm.kotlin.test.mongodb.util.AUTHORIZED_ONLY_FUNCTION +import io.realm.kotlin.test.mongodb.util.BaseAppInitializer +import io.realm.kotlin.test.mongodb.util.ERROR_FUNCTION +import io.realm.kotlin.test.mongodb.util.FIRST_ARG_FUNCTION +import io.realm.kotlin.test.mongodb.util.NULL_FUNCTION +import io.realm.kotlin.test.mongodb.util.SUM_FUNCTION +import io.realm.kotlin.test.mongodb.util.VOID_FUNCTION +import io.realm.kotlin.test.mongodb.util.addEmailProvider import io.realm.kotlin.test.util.TypeDescriptor import io.realm.kotlin.types.MutableRealmInt import io.realm.kotlin.types.RealmAny @@ -201,7 +200,18 @@ class FunctionsTests { fun setup() { app = TestApp( FunctionsTests::class.simpleName, - syncServerAppName("funcs"), + object : BaseAppInitializer( + syncServerAppName("funcs"), + { app -> + addEmailProvider(app) + app.addFunction(FIRST_ARG_FUNCTION) + app.addFunction(NULL_FUNCTION) + app.addFunction(SUM_FUNCTION) + app.addFunction(ERROR_FUNCTION) + app.addFunction(VOID_FUNCTION) + app.addFunction(AUTHORIZED_ONLY_FUNCTION) + } + ) {}, ejson = EJson( serializersModule = SerializersModule { polymorphic(RealmObject::class) { @@ -209,15 +219,7 @@ class FunctionsTests { } } ) - ) { app: BaasApp, service: Service -> - initializeDefault(app, service) - app.addFunction(FIRST_ARG_FUNCTION) - app.addFunction(NULL_FUNCTION) - app.addFunction(SUM_FUNCTION) - app.addFunction(ERROR_FUNCTION) - app.addFunction(VOID_FUNCTION) - app.addFunction(AUTHORIZED_ONLY_FUNCTION) - } + ) anonUser = runBlocking { app.login(Credentials.anonymous()) } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/GeoSpatialTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/GeoSpatialTests.kt index 1cdbe71ff3..58c2943aab 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/GeoSpatialTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/GeoSpatialTests.kt @@ -25,9 +25,9 @@ import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.mongodb.User import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.use import io.realm.kotlin.types.geo.Distance @@ -49,7 +49,7 @@ class GeoSpatialTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX) + app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) } @AfterTest diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt index f72eba9784..bb8cffc67d 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt @@ -29,8 +29,11 @@ import io.realm.kotlin.mongodb.ext.call import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.CustomLogCollector import io.realm.kotlin.test.mongodb.syncServerAppName -import io.realm.kotlin.test.mongodb.util.TestAppInitializer -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.initializeDefault +import io.realm.kotlin.test.mongodb.util.BaseAppInitializer +import io.realm.kotlin.test.mongodb.util.FIRST_ARG_FUNCTION +import io.realm.kotlin.test.mongodb.util.NULL_FUNCTION +import io.realm.kotlin.test.mongodb.util.SUM_FUNCTION +import io.realm.kotlin.test.mongodb.util.addEmailProvider import io.realm.kotlin.test.util.receiveOrFail import kotlinx.coroutines.CancellationException import kotlinx.coroutines.async @@ -122,13 +125,12 @@ class HttpLogObfuscatorTests { private fun initApp(): TestApp { return TestApp( this::class.simpleName, - appName = syncServerAppName("obfsctr"), - initialSetup = { app, service -> - initializeDefault(app, service) - app.addFunction(TestAppInitializer.FIRST_ARG_FUNCTION) - app.addFunction(TestAppInitializer.SUM_FUNCTION) - app.addFunction(TestAppInitializer.NULL_FUNCTION) - } + object : BaseAppInitializer(syncServerAppName("obfsctr"), { app -> + addEmailProvider(app) + app.addFunction(FIRST_ARG_FUNCTION) + app.addFunction(SUM_FUNCTION) + app.addFunction(NULL_FUNCTION) + }) {} ) } @@ -148,14 +150,16 @@ class HttpLogObfuscatorTests { RealmLog.add(logger) app = TestApp( "nullObfuscator", - appName = syncServerAppName("null-obf"), + object : BaseAppInitializer( + syncServerAppName("null-obf"), + { app -> + addEmailProvider(app) + app.addFunction(FIRST_ARG_FUNCTION) + app.addFunction(SUM_FUNCTION) + app.addFunction(NULL_FUNCTION) + } + ) {}, builder = { it.httpLogObfuscator(null) }, - initialSetup = { app, service -> - initializeDefault(app, service) - app.addFunction(TestAppInitializer.FIRST_ARG_FUNCTION) - app.addFunction(TestAppInitializer.SUM_FUNCTION) - app.addFunction(TestAppInitializer.NULL_FUNCTION) - } ) // Create user and log in @@ -190,9 +194,9 @@ class HttpLogObfuscatorTests { // Calling functions with arguments results in these not being obfuscated with(user.functions) { - call(TestAppInitializer.FIRST_ARG_FUNCTION.name, 42.0) - call(TestAppInitializer.SUM_FUNCTION.name, 42.0, 1.0) - call(TestAppInitializer.NULL_FUNCTION.name) + call(FIRST_ARG_FUNCTION.name, 42.0) + call(SUM_FUNCTION.name, 42.0, 1.0) + call(NULL_FUNCTION.name) } // Verify that none of the logs are obfuscated @@ -315,9 +319,9 @@ class HttpLogObfuscatorTests { async { with(user.functions) { - call(TestAppInitializer.FIRST_ARG_FUNCTION.name, 42.0) - call(TestAppInitializer.SUM_FUNCTION.name, 42.0, 1.0) - call(TestAppInitializer.NULL_FUNCTION.name) + call(FIRST_ARG_FUNCTION.name, 42.0) + call(SUM_FUNCTION.name, 42.0, 1.0) + call(NULL_FUNCTION.name) } } // 1st custom function call - request (obfuscate arguments) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/MutableSubscriptionSetTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/MutableSubscriptionSetTests.kt index 248aa3e7f0..9e5f9524a0 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/MutableSubscriptionSetTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/MutableSubscriptionSetTests.kt @@ -27,10 +27,10 @@ import io.realm.kotlin.mongodb.sync.SubscriptionSet import io.realm.kotlin.mongodb.sync.SubscriptionSetState import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.toRealmInstant import io.realm.kotlin.test.util.use @@ -57,7 +57,7 @@ class MutableSubscriptionSetTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX) + app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) val (email, password) = TestHelper.randomEmail() to "password1234" val user = runBlocking { app.createUserAndLogIn(email, password) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt index 0ab083e3ce..3b65a396e3 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt @@ -29,11 +29,11 @@ import io.realm.kotlin.mongodb.sync.ProgressMode import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.mongodb.sync.SyncSession import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail import io.realm.kotlin.test.mongodb.createUserAndLogIn import io.realm.kotlin.test.mongodb.use +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.util.TestChannel import io.realm.kotlin.test.util.receiveOrFail import io.realm.kotlin.test.util.use @@ -70,7 +70,7 @@ class PBSProgressListenerTests { @BeforeTest fun setup() { RealmLog.setLevel(LogLevel.INFO) - app = TestApp(this::class.simpleName, appName = TEST_APP_PARTITION) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) partitionValue = org.mongodb.kbson.ObjectId().toString() } @@ -265,7 +265,7 @@ class PBSProgressListenerTests { fun completesOnClose() = runBlocking { val channel = TestChannel(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - TestApp("completesOnClose", TEST_APP_PARTITION).use { app -> + TestApp("completesOnClose", DefaultPartitionBasedAppInitializer).use { app -> val user = app.createUserAndLogIn() val realm = Realm.open(createSyncConfig(user)) try { diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionExtensionsTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionExtensionsTests.kt index 45cf941a6f..3644f630b0 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionExtensionsTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionExtensionsTests.kt @@ -31,10 +31,10 @@ import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.mongodb.sync.WaitForSync import io.realm.kotlin.mongodb.syncSession import io.realm.kotlin.query.RealmResults -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.use import kotlinx.coroutines.TimeoutCancellationException @@ -61,7 +61,7 @@ class SubscriptionExtensionsTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX) + app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) val (email, password) = TestHelper.randomEmail() to "password1234" val user = runBlocking { app.createUserAndLogIn(email, password) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionSetTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionSetTests.kt index 993957978e..6c28da7467 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionSetTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionSetTests.kt @@ -24,12 +24,12 @@ import io.realm.kotlin.mongodb.subscriptions import io.realm.kotlin.mongodb.sync.Subscription import io.realm.kotlin.mongodb.sync.SubscriptionSetState import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX -import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.waitForSynchronizationOrFail import io.realm.kotlin.test.mongodb.createUserAndLogIn import io.realm.kotlin.test.mongodb.use +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.use import kotlin.test.AfterTest @@ -56,7 +56,7 @@ class SubscriptionSetTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX) + app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) val (email, password) = TestHelper.randomEmail() to "password1234" val user = runBlocking { app.createUserAndLogIn(email, password) @@ -89,7 +89,7 @@ class SubscriptionSetTests { @Test fun subscriptions_failOnNonFlexibleSyncRealms() { - TestApp(this::class.simpleName, appName = TEST_APP_PARTITION).use { testApp -> + TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer).use { testApp -> val (email, password) = TestHelper.randomEmail() to "password1234" val user = runBlocking { testApp.createUserAndLogIn(email, password) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionTests.kt index ffe94723a1..a6c8680285 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionTests.kt @@ -28,6 +28,7 @@ import io.realm.kotlin.mongodb.sync.asQuery import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.util.TestHelper.randomEmail import io.realm.kotlin.test.util.toRealmInstant import io.realm.kotlin.types.RealmInstant @@ -55,7 +56,7 @@ class SubscriptionTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer,) val (email, password) = randomEmail() to "password1234" val user = runBlocking { app.createUserAndLogIn(email, password) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt index 3a1011c711..66a26acc6b 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt @@ -47,9 +47,11 @@ import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.addEmailProvider -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.initializeFlexibleSync -import io.realm.kotlin.test.mongodb.util.TestAppInitializer.initializePartitionSync +import io.realm.kotlin.test.mongodb.util.BaasApp +import io.realm.kotlin.test.mongodb.util.BaseAppInitializer +import io.realm.kotlin.test.mongodb.util.addEmailProvider +import io.realm.kotlin.test.mongodb.util.initializeFlexibleSync +import io.realm.kotlin.test.mongodb.util.initializePartitionSync import io.realm.kotlin.test.util.TestChannel import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.receiveOrFail @@ -118,16 +120,15 @@ class SyncClientResetIntegrationTests { RealmLog.add(ClientResetLoggerInspector(logChannel)) val app = TestApp( this::class.simpleName, - appName = appName, - initialSetup = { app, service -> + object : BaseAppInitializer(appName, { app: BaasApp -> addEmailProvider(app) when (syncMode) { SyncMode.PARTITION_BASED -> - initializePartitionSync(app, service, recoveryDisabled) + initializePartitionSync(app, recoveryDisabled) SyncMode.FLEXIBLE -> - initializeFlexibleSync(app, service, recoveryDisabled) + initializeFlexibleSync(app, recoveryDisabled) } - } + }) {}, ) try { val (email, password) = TestHelper.randomEmail() to "password1234" diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientTests.kt index 52fe439bea..dd8d461b38 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientTests.kt @@ -8,6 +8,7 @@ import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.use import kotlin.test.AfterTest @@ -28,7 +29,7 @@ class SyncClientTests { @BeforeTest fun setup() { - app = TestApp(this::class.simpleName) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) val (email, password) = TestHelper.randomEmail() to "password1234" user = runBlocking { app.createUserAndLogIn(email, password) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncConfigTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncConfigTests.kt index 235dadff6b..5474ac22cb 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncConfigTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncConfigTests.kt @@ -48,6 +48,7 @@ import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.TestHelper.getRandomKey import io.realm.kotlin.test.util.TestHelper.randomEmail @@ -86,7 +87,7 @@ class SyncConfigTests { @BeforeTest fun setup() { partitionValue = TestHelper.randomPartitionValue() - app = TestApp(this::class.simpleName) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) } @AfterTest diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncSessionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncSessionTests.kt index 5832f3b170..ec372c0bb3 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncSessionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncSessionTests.kt @@ -36,6 +36,7 @@ import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.TestChannel import io.realm.kotlin.test.util.TestHelper @@ -78,7 +79,7 @@ class SyncSessionTests { @BeforeTest fun setup() { partitionValue = TestHelper.randomPartitionValue() - app = TestApp(this::class.simpleName) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) val (email, password) = TestHelper.randomEmail() to "password1234" user = runBlocking { app.createUserAndLogIn(email, password) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt index 46d54cb425..6755835db8 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt @@ -61,13 +61,14 @@ import io.realm.kotlin.query.RealmResults import io.realm.kotlin.schema.RealmClass import io.realm.kotlin.schema.RealmSchema import io.realm.kotlin.schema.ValuePropertyType -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail import io.realm.kotlin.test.mongodb.createUserAndLogIn import io.realm.kotlin.test.mongodb.use +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.TestChannel import io.realm.kotlin.test.util.TestHelper @@ -130,7 +131,7 @@ class SyncedRealmTests { @BeforeTest fun setup() { partitionValue = TestHelper.randomPartitionValue() - app = TestApp(this::class.simpleName) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) val (email, password) = randomEmail() to "password1234" val user = runBlocking { @@ -720,12 +721,14 @@ class SyncedRealmTests { fun roundtripCollectionsInMixed() = runBlocking { val (email1, password1) = randomEmail() to "password1234" val (email2, password2) = randomEmail() to "password1234" - val app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX) + val app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) val user1 = app.createUserAndLogIn(email1, password1) val user2 = app.createUserAndLogIn(email2, password2) // Create object with all types val selector = ObjectId().toString() + var parentId: ObjectId? = null + var childId: ObjectId? = null createFlexibleSyncConfig( user = user1, @@ -737,14 +740,13 @@ class SyncedRealmTests { realm.write { val child = ( JsonStyleRealmObject().apply { - this.id = "CHILD" this.selector = selector } ) + childId = child.id - copyToRealm( + parentId = copyToRealm( JsonStyleRealmObject().apply { - this.id = "PARENT" this.selector = selector value = realmAnyDictionaryOf( "primitive" to 1, @@ -753,7 +755,7 @@ class SyncedRealmTests { "dictionary" to realmAnyDictionaryOf("dictkey1" to 1, "dictkey2" to "Realm", "dictkey3" to child, "dictkey4" to realmAnyListOf(1, 2, 3)) ) } - ) + ).id } realm.syncSession.uploadAllLocalChangesOrFail() } @@ -766,7 +768,7 @@ class SyncedRealmTests { ).let { config -> Realm.open(config).use { realm -> realm.syncSession.downloadAllServerChanges(10.seconds) - val flow = realm.query("_id = $0", "PARENT").asFlow() + val flow = realm.query("_id = $0", parentId).asFlow() val parent = withTimeout(10.seconds) { flow.first { it.list.size >= 1 @@ -778,18 +780,18 @@ class SyncedRealmTests { value["list"]!!.asList().let { assertEquals(1, it[0]!!.asInt()) assertEquals("Realm", it[1]!!.asString()) - assertEquals("CHILD", it[2]!!.asRealmObject().id) + assertEquals(childId, it[2]!!.asRealmObject().id) it[3]!!.asDictionary().let { dict -> assertEquals(1, dict["listkey1"]!!.asInt()) assertEquals("Realm", dict["listkey2"]!!.asString()) - assertEquals("CHILD", dict["listkey3"]!!.asRealmObject().id) + assertEquals(childId, dict["listkey3"]!!.asRealmObject().id) } - assertEquals("CHILD", it[2]!!.asRealmObject().id) + assertEquals(childId, it[2]!!.asRealmObject().id) } value["dictionary"]!!.asDictionary().let { assertEquals(1, it["dictkey1"]!!.asInt()) assertEquals("Realm", it["dictkey2"]!!.asString()) - assertEquals("CHILD", it["dictkey3"]!!.asRealmObject().id) + assertEquals(childId, it["dictkey3"]!!.asRealmObject().id) it["dictkey4"]!!.asList().let { assertEquals(realmAnyListOf(1, 2, 3).asList(), it) } @@ -803,7 +805,7 @@ class SyncedRealmTests { fun collectionsInMixed_asFlow() = runBlocking { val (email1, password1) = randomEmail() to "password1234" val (email2, password2) = randomEmail() to "password1234" - val app = TestApp(this::class.simpleName, appName = TEST_APP_FLEX) + val app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) val user1 = app.createUserAndLogIn(email1, password1) val user2 = app.createUserAndLogIn(email2, password2) @@ -1138,7 +1140,7 @@ class SyncedRealmTests { fun writeCopyTo_localToFlexibleSync_throws() = runBlocking { TestApp( this::class.simpleName, - appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, builder = { it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) } @@ -1217,7 +1219,7 @@ class SyncedRealmTests { fun writeCopyTo_flexibleSyncToLocal() = runBlocking { TestApp( "writeCopyTo_flexibleSyncToLocal", - appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, builder = { it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) } @@ -1340,7 +1342,7 @@ class SyncedRealmTests { fun writeCopyTo_flexibleSyncToFlexibleSync() = runBlocking { TestApp( "writeCopyTo_flexibleSyncToFlexibleSync", - appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, builder = { it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) } @@ -1443,7 +1445,7 @@ class SyncedRealmTests { fun accessSessionAfterRemoteChange() = runBlocking { TestApp( "accessSessionAfterRemoteChange", - appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, builder = { it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) } @@ -1634,7 +1636,7 @@ class SyncedRealmTests { fun createInitialRealmFx() = runBlocking { TestApp( "createInitialRealmFx", - appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, builder = { it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) } @@ -1689,7 +1691,7 @@ class SyncedRealmTests { @OptIn(ExperimentalKBsonSerializerApi::class) TestApp( "initialRealm_flexibleSync", - appName = io.realm.kotlin.test.mongodb.TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, builder = { it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt index 87e056b344..5c5593f9c6 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt @@ -27,6 +27,7 @@ import io.realm.kotlin.mongodb.ext.profileAsBsonDocument import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException @@ -102,6 +103,7 @@ class UserProfileTests { fun setUp() { app = TestApp( this::class.simpleName, + DefaultPartitionBasedAppInitializer, networkTransport = object : NetworkTransport { override val authorizationHeaderName: String? get() = "" diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt index e51b042d48..6efbbd81e0 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt @@ -39,6 +39,7 @@ import io.realm.kotlin.test.mongodb.common.mongo.CustomDataType import io.realm.kotlin.test.mongodb.common.mongo.TEST_SERVICE_NAME import io.realm.kotlin.test.mongodb.common.mongo.customEjsonSerializer import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.TestHelper.randomEmail import kotlinx.serialization.SerialName @@ -71,7 +72,7 @@ class UserTests { @BeforeTest fun setUp() { - app = TestApp(this::class.simpleName) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) } @AfterTest diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/KtorNetworkTransportTest.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/KtorNetworkTransportTest.kt index fcef828d8f..2acb753971 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/KtorNetworkTransportTest.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/KtorNetworkTransportTest.kt @@ -26,11 +26,10 @@ import io.realm.kotlin.internal.util.CoroutineDispatcherFactory import io.realm.kotlin.internal.util.use import io.realm.kotlin.mongodb.internal.KtorNetworkTransport import io.realm.kotlin.test.mongodb.TEST_SERVER_BASE_URL -import io.realm.kotlin.test.mongodb.syncServerAppName +import io.realm.kotlin.test.mongodb.util.AppInitializer import io.realm.kotlin.test.mongodb.util.AppServicesClient import io.realm.kotlin.test.mongodb.util.BaasApp -import io.realm.kotlin.test.mongodb.util.KtorTestAppInitializer.initialize -import io.realm.kotlin.test.mongodb.util.Service +import io.realm.kotlin.test.mongodb.util.Function import io.realm.kotlin.test.util.TestChannel import io.realm.kotlin.test.util.receiveOrFail import io.realm.kotlin.test.util.trySendOrFail @@ -50,6 +49,64 @@ val TEST_METHODS = listOf( HttpMethod.Delete, ) +object KtorTestAppInitializer : AppInitializer { + override suspend fun initialize(client: AppServicesClient, app: BaasApp) { + client.initialize(app, TEST_METHODS) + } + + // Setups the app with the functions and https endpoints required to run the KtorNetworkTransportTests + suspend fun AppServicesClient.initialize(app: BaasApp, methods: List) = + with(app) { + // We have to create a function per method because the request parameter does not + // has what method triggered it. + methods.forEach { httpMethod: HttpMethod -> + val method = httpMethod.value + val function = addFunction( + Function( + name = "test_network_transport_$method", + runAsSystem = true, + source = + """ + exports = async function (request, response) { + response.setHeader('Content-Type', 'text/plain'); + let isSuccess = request.query["success"] == "true"; + + if (isSuccess) { + response.setStatusCode(200); + response.setBody("$method-success"); + } else { + response.setStatusCode(500); + response.setBody("$method-failure"); + } + } + """.trimIndent() + ) + ) + + addEndpoint( + """ + { + "route": "/test_network_transport", + "function_name": "${function.name}", + "function_id": "${function._id}", + "http_method": "$method", + "validation_method": "NO_VALIDATION", + "secret_id": "", + "secret_name": "", + "create_user_on_auth": false, + "fetch_custom_user_data": false, + "respond_result": false, + "disabled": false, + "return_type": "JSON" + } + """.trimIndent() + ) + } + } + + override val name: String = "ktor-test-app" +} + internal class KtorNetworkTransportTest { private lateinit var transport: KtorNetworkTransport private lateinit var endpoint: String @@ -76,9 +133,7 @@ internal class KtorNetworkTransportTest { dispatcher = dispatcher ) appClient.run { - getOrCreateApp(syncServerAppName("ktor")) { app: BaasApp, service: Service -> - initialize(app, TEST_METHODS) - } + getOrCreateApp(KtorTestAppInitializer) } } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt index d2399aaeb0..e27973998f 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt @@ -23,9 +23,9 @@ import io.realm.kotlin.mongodb.ext.collection import io.realm.kotlin.mongodb.ext.insertOne import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.realmSerializerModule -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer import kotlinx.serialization.SerializationException import org.mongodb.kbson.ExperimentalKBsonSerializerApi import org.mongodb.kbson.serialization.EJson @@ -47,7 +47,7 @@ class MongoClientTests { fun setUp() { app = TestApp( this::class.simpleName, - appName = TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, ) val user = app.createUserAndLogin() client = user.mongoClient(TEST_SERVICE_NAME, eJson = EJson(serializersModule = realmSerializerModule(setOf(CollectionDataType::class)))) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt index d4c38b4969..91ef0aea63 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt @@ -48,12 +48,12 @@ import io.realm.kotlin.mongodb.mongo.realmSerializerModule import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.mongodb.syncSession import io.realm.kotlin.notifications.ResultsChange -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage import io.realm.kotlin.test.mongodb.common.utils.retry +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer import io.realm.kotlin.test.util.receiveOrFail import io.realm.kotlin.test.util.use import io.realm.kotlin.types.BaseRealmObject @@ -158,7 +158,7 @@ sealed class MongoCollectionTests { open fun setUp() { app = TestApp( testId = this::class.simpleName, - appName = TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, builder = { it.httpLogObfuscator(null) } diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt index ed135800ed..fc99981af3 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt @@ -21,9 +21,9 @@ import io.realm.kotlin.internal.platform.runBlocking import io.realm.kotlin.mongodb.ext.insertOne import io.realm.kotlin.mongodb.mongo.MongoClient import io.realm.kotlin.mongodb.mongo.MongoDatabase -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage +import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer import kotlinx.serialization.SerializationException import org.mongodb.kbson.BsonDocument import org.mongodb.kbson.BsonInt32 @@ -48,7 +48,7 @@ class MongoDatabaseTests { fun setUp() { app = TestApp( this::class.simpleName, - appName = TEST_APP_FLEX, + DefaultFlexibleSyncAppInitializer, ) val user = app.createUserAndLogin() client = user.mongoClient(TEST_SERVICE_NAME) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/nonlatin/NonLatinTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/nonlatin/NonLatinTests.kt index fd880b43b9..93032998eb 100644 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/nonlatin/NonLatinTests.kt +++ b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/nonlatin/NonLatinTests.kt @@ -10,6 +10,7 @@ import io.realm.kotlin.test.mongodb.TestApp import io.realm.kotlin.test.mongodb.asTestApp import io.realm.kotlin.test.mongodb.common.PARTITION_BASED_SCHEMA import io.realm.kotlin.test.mongodb.createUserAndLogIn +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.util.TestChannel import io.realm.kotlin.test.util.TestHelper import io.realm.kotlin.test.util.receiveOrFail @@ -37,7 +38,7 @@ class NonLatinTests { fun setup() { partitionValue = TestHelper.randomPartitionValue() @OptIn(ExperimentalKBsonSerializerApi::class) - app = TestApp(this::class.simpleName) + app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) val (email, password) = TestHelper.randomEmail() to "password1234" user = runBlocking { app.createUserAndLogIn(email, password) diff --git a/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/RealmTests.kt b/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/RealmTests.kt index e63ed5fde9..3d0d9e5df5 100644 --- a/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/RealmTests.kt +++ b/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/RealmTests.kt @@ -22,6 +22,7 @@ import io.realm.kotlin.entities.sync.ParentPk import io.realm.kotlin.mongodb.Credentials import io.realm.kotlin.mongodb.sync.SyncConfiguration import io.realm.kotlin.test.mongodb.TestApp +import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer import io.realm.kotlin.test.platform.PlatformUtils import io.realm.kotlin.test.util.TestHelper import kotlinx.coroutines.runBlocking @@ -42,7 +43,7 @@ class RealmTests { @Ignore // See https://github.com/realm/realm-kotlin/issues/1627 fun cleanupAllRealmThreadsOnClose() = runBlocking { @OptIn(ExperimentalKBsonSerializerApi::class) - val app = TestApp("cleanupAllRealmThreadsOnClose") + val app = TestApp("cleanupAllRealmThreadsOnClose", DefaultPartitionBasedAppInitializer) val user = app.login(Credentials.anonymous()) val configuration = SyncConfiguration.create(user, TestHelper.randomPartitionValue(), setOf(ParentPk::class, ChildPk::class)) Realm.open(configuration).close() From ec6f2a0d57855c9970cec37610b0298441d06382 Mon Sep 17 00:00:00 2001 From: clementetb Date: Fri, 5 Jul 2024 20:54:11 +0200 Subject: [PATCH 09/10] Build linux libraries with glibc 2.17 (#1795) --- .github/workflows/pr.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index beeb9d53a1..ae8d285bbe 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -113,6 +113,12 @@ jobs: path: ./packages/cinterop/build/realmLinuxBuild key: jni-linux-lib-${{ needs.check-cache.outputs.packages-sha }} + - name: Setup Java 11 + uses: actions/setup-java@v3 + with: + distribution: ${{ vars.VERSION_JAVA_DISTRIBUTION }} + java-version: ${{ vars.VERSION_JAVA }} + - name: Setup cmake uses: jwlawson/actions-setup-cmake@v1.13 with: @@ -136,6 +142,8 @@ jobs: -DREALM_ENABLE_SYNC=1 \ -DREALM_NO_TESTS=1 \ -DREALM_BUILD_LIB_ONLY=true \ + -DCMAKE_TOOLCHAIN_FILE=../../../external/core/tools/cmake/x86_64-linux-gnu.toolchain.cmake \ + -DJAVA_INCLUDE_PATH=${{ env.JAVA_HOME }}/include/ \ ../../src/jvm make -j8 @@ -1091,7 +1099,9 @@ jobs: always() && !cancelled() && !contains(needs.*.result, 'failure') && - !contains(needs.*.result, 'cancelled') + !contains(needs.*.result, 'cancelled') && + endsWith(needs.check-cache.outputs.version-label, '-SNAPSHOT') && + (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/releases' || github.ref == 'refs/heads/release/k2') steps: - name: Checkout code uses: actions/checkout@v3 From fb796f09e7cfe508467ce3b1d617f7924467d3f3 Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Sat, 6 Jul 2024 17:28:47 +0100 Subject: [PATCH 10/10] [RKOTLIN-877] Avoid exporting Core's symbols to allow mixing multiple SDKs (#1780) --- .github/workflows/pr.yml | 2 ++ CHANGELOG.md | 1 + packages/cinterop/build.gradle.kts | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 40494fde38..45c5ecac9b 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -144,6 +144,7 @@ jobs: -DREALM_ENABLE_SYNC=1 \ -DREALM_NO_TESTS=1 \ -DREALM_BUILD_LIB_ONLY=true \ + -DCMAKE_CXX_VISIBILITY_PRESET=hidden \ ../../src/jvm make -j8 @@ -213,6 +214,7 @@ jobs: -DCMAKE_BUILD_TYPE=Release ` -DREALM_ENABLE_SYNC=ON ` -DREALM_NO_TESTS=1 ` + -DCMAKE_CXX_VISIBILITY_PRESET=hidden ` -DVCPKG_TARGET_TRIPLET=x64-windows-static cmake --build . --config Release diff --git a/CHANGELOG.md b/CHANGELOG.md index f6383607ac..05be5ed898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - None. ### Enhancements +- Avoid exporting Core's symbols so we can statically build the Kotlin SDK with other SDKs like Swift in the same project. (Issue [JIRA](https://jira.mongodb.org/browse/RKOTLIN-877)). - Improved mechanism for unpacking of JVM native libs suitable for local development. (Issue [#1715](https://github.com/realm/realm-kotlin/issues/1715) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1065)). ### Fixed diff --git a/packages/cinterop/build.gradle.kts b/packages/cinterop/build.gradle.kts index 6a0c7e1817..32a61dd7ca 100644 --- a/packages/cinterop/build.gradle.kts +++ b/packages/cinterop/build.gradle.kts @@ -494,6 +494,10 @@ fun getSharedCMakeFlags(buildType: BuildType, ccache: Boolean = true): Array