diff --git a/.github/templates/build.lib.yml b/.github/templates/build.lib.yml index 5721c6ec87..c5a4b710a1 100644 --- a/.github/templates/build.lib.yml +++ b/.github/templates/build.lib.yml @@ -43,17 +43,25 @@ deploy-cluster: AtlasPrivateKey: #@ secret_AtlasPrivateKey #@ end -#@ def cleanupBaas(dependencies = []): -#@ needs = [] +#! We need to have two input arrays because there is not a 1-to-1 correspondence between dependencies +#! and targets, for example for macOS. +#@ def cleanupBaas(dependencies = [], targets = []): +#@ needs = ["deploy-cluster"] +#@ differentiators = [] #@ for dependency in dependencies: #@ needs.append(getJobName("test", dependency)) #@ end +#@ for target in targets: +#@ differentiators.append('"' + getJobName("", target) + '"') +#@ end cleanup-cluster: uses: ./.github/workflows/cleanup-baas.yml if: always() name: Cleanup needs: #@ needs with: + differentiators: #@ '[' + ", ".join(differentiators) + ']' + clusterName: ${{ needs.deploy-cluster.outputs.clusterName }} BaseUrl: #@ realm_BaseUrl AtlasBaseUrl: #@ atlas_BaseUrl secrets: diff --git a/.github/templates/main.yml b/.github/templates/main.yml index 485eb3bdae..71e090cc35 100644 --- a/.github/templates/main.yml +++ b/.github/templates/main.yml @@ -95,4 +95,4 @@ jobs: dashboard-path: dashboard.charts nuget-package: ${{ github.workspace }}/Realm/packages/Realm.${{ needs.build-packages.outputs.package_version }}.nupkg - #@ uploadArtifacts("dashboard.charts", "dashboard.charts", 30) - _: #@ template.replace(cleanupBaas([".NET Framework", "Code Coverage", "UWP Managed", "MacOS", "iOS", "Android"])) + _: #@ template.replace(cleanupBaas(dependencies = [".NET Framework", "Code Coverage", "UWP Managed", "MacOS", "iOS", "Android"], targets=[".NET Framework", "Code Coverage", "UWP Managed", "Xamarin.macOS", "iOS", "Android", "MacCatalyst"])) diff --git a/.github/templates/pr.yml b/.github/templates/pr.yml index a871f6b95f..1b0cde52e7 100644 --- a/.github/templates/pr.yml +++ b/.github/templates/pr.yml @@ -54,7 +54,7 @@ jobs: uses: ./.github/workflows/test-weaver.yml name: Test _: #@ template.replace(runTests("Code Coverage")) - _: #@ template.replace(cleanupBaas(["Code Coverage"])) + _: #@ template.replace(cleanupBaas(dependencies = ["Code Coverage"], targets = ["Code Coverage"])) verify-namespaces: runs-on: ubuntu-latest name: Verify Namespaces diff --git a/.github/templates/test-android.yml b/.github/templates/test-android.yml index 10d93348b5..4d6e619170 100644 --- a/.github/templates/test-android.yml +++ b/.github/templates/test-android.yml @@ -11,7 +11,7 @@ jobs: name: Xamarin.Android timeout-minutes: 60 steps: - - #@ template.replace(prepareTest()) + - #@ template.replace(prepareTest("android")) - #@ template.replace(buildTests("Tests/Tests.Android", target="SignAndroidPackage", AndroidUseSharedRuntime="False", EmbedAssembliesIntoApk="True")) - #@ template.replace(configureAWSCredentials("AWS_DEVICEFARM_ACCESS_KEY_ID", "AWS_DEVICEFARM_SECRET_ACCESS_KEY", "us-west-2")) - name: Run the tests diff --git a/.github/templates/test-code-coverage.yml b/.github/templates/test-code-coverage.yml index 12214b1aa8..8c934b378c 100644 --- a/.github/templates/test-code-coverage.yml +++ b/.github/templates/test-code-coverage.yml @@ -11,7 +11,7 @@ jobs: name: Code Coverage timeout-minutes: 90 steps: - - #@ template.replace(prepareTest(fetchWrappers=True)) + - #@ template.replace(prepareTest("code-coverage", fetchWrappers=True)) - name: Setup Coverlet & Report Generator run: | dotnet tool install coverlet.console --tool-path tools diff --git a/.github/templates/test-ios.yml b/.github/templates/test-ios.yml index b06fedfeb5..06893d0466 100644 --- a/.github/templates/test-ios.yml +++ b/.github/templates/test-ios.yml @@ -11,7 +11,7 @@ jobs: name: Xamarin.iOS timeout-minutes: 90 steps: - - #@ template.replace(prepareTest()) + - #@ template.replace(prepareTest("ios")) - #@ template.replace(buildTests("Tests/Tests.iOS", Platform="iPhoneSimulator")) - name: Run the tests uses: #@ actionRuniOSSimulator diff --git a/.github/templates/test-macos.yml b/.github/templates/test-macos.yml index 2b3cda6266..a1a654ef50 100644 --- a/.github/templates/test-macos.yml +++ b/.github/templates/test-macos.yml @@ -9,9 +9,9 @@ jobs: test-xamarin: runs-on: macos-latest name: Xamarin.macOS - timeout-minutes: 45 + timeout-minutes: 60 steps: - - #@ template.replace(prepareTest()) + - #@ template.replace(prepareTest("xamarinmacos")) - #@ template.replace(buildTests("Tests/Tests.XamarinMac")) - name: Run the tests run: #@ "Tests/Tests.XamarinMac/bin/" + configuration + "/Tests.XamarinMac.app/Contents/MacOS/Tests.XamarinMac --headless --result=${{ github.workspace }}/TestResults.XamarinMac.xml --labels=All" + baasTestArgs("xamarinmacos") @@ -19,7 +19,7 @@ jobs: test-maui: runs-on: macos-12 name: Maui.MacCatalyst - timeout-minutes: 45 + timeout-minutes: 60 steps: - #@ template.replace(checkoutCode()) - #@ template.replace(fetchPackageArtifacts()) diff --git a/.github/templates/test-net-core.yml b/.github/templates/test-net-core.yml index 71d00c207c..39302f9f23 100644 --- a/.github/templates/test-net-core.yml +++ b/.github/templates/test-net-core.yml @@ -38,7 +38,7 @@ jobs: env: DOTNET_DbgEnableMiniDump: 1 DOTNET_EnableCrashReport: 1 - run: #@ "${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Tests --result=TestResults.xml --labels=After" + baasTestArgs("net-core-${{ matrix.runner }}-${{ matrix.runtime }}") + run: #@ "${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Tests --result=TestResults.xml --labels=After" - name: Archive core dump uses: actions/upload-artifact@v3 if: failure() diff --git a/.github/templates/test-net-framework.yml b/.github/templates/test-net-framework.yml index 16ae0e3bc8..21e5d5946e 100644 --- a/.github/templates/test-net-framework.yml +++ b/.github/templates/test-net-framework.yml @@ -11,7 +11,7 @@ jobs: name: .NET Framework timeout-minutes: 45 steps: - - #@ template.replace(prepareTest()) + - #@ template.replace(prepareTest("net-framework")) - #@ template.replace(buildTests("Tests/Realm.Tests", TargetFramework="net461", RealmTestsStandaloneExe="true")) - name: Run the tests run: #@ "./Tests/Realm.Tests/bin/" + configuration + "/net461/Realm.Tests.exe --result=TestResults.Windows.xml --labels=After" + baasTestArgs("net-framework") diff --git a/.github/templates/test-uwp-managed.yml b/.github/templates/test-uwp-managed.yml index 10dbbc65e6..40887279aa 100644 --- a/.github/templates/test-uwp-managed.yml +++ b/.github/templates/test-uwp-managed.yml @@ -11,7 +11,7 @@ jobs: name: UWP timeout-minutes: 45 steps: - - #@ template.replace(prepareTest()) + - #@ template.replace(prepareTest("uwp-managed")) - name: Import test certificate run: | $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}") diff --git a/.github/templates/test-woven-classes.yml b/.github/templates/test-woven-classes.yml index ab33fb2a12..3343b9587d 100644 --- a/.github/templates/test-woven-classes.yml +++ b/.github/templates/test-woven-classes.yml @@ -15,5 +15,5 @@ jobs: - #@ template.replace(fetchPackageArtifacts()) - #@ template.replace(wovenClassesBuildTests("Tests/Realm.Tests", "net7.0", "win-x64")) - name: Run the tests - run: #@ "${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Tests --result=TestResults.WovenClasses.xml --labels=After" + baasTestArgs("weaved-classes") + run: #@ "${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Tests --result=TestResults.WovenClasses.xml --labels=After" - #@ publishTestsResults("TestResults.WovenClasses.xml", "Woven classes") diff --git a/.github/templates/test.lib.yml b/.github/templates/test.lib.yml index ae5fb3ce8c..549eba9f17 100644 --- a/.github/templates/test.lib.yml +++ b/.github/templates/test.lib.yml @@ -2,7 +2,7 @@ #@ load("common.lib.yml", "actionDownloadArtifact", "msbuild", "dotnetPublish", "fetchWrapperBinaries", "checkoutCode", "fetchPackageArtifacts") #@ def baasTestArgs(differentiator): -#@ return " --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=" + differentiator +#@ return " --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }}-"+ differentiator +" --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=" + differentiator #@ end --- #@ def publishTestsResults(files, test_title): @@ -81,7 +81,7 @@ env: #@ return dotnetPublish(projectPath, framework, runtime, properties) #@ end --- -#@ def prepareTest(fetchWrappers = False, cleanupWorkspace = False): +#@ def prepareTest(differentiator = "", fetchWrappers = False, cleanupWorkspace = False): - #@ template.replace(checkoutCode()) #@ if cleanupWorkspace: - name: Cleanup Workspace @@ -92,7 +92,10 @@ env: #@ else: - #@ template.replace(fetchPackageArtifacts()) #@ end +#@ if (differentiator != ""): +#! The following is used in case we need to re-run the action and the main deploy cluster job is not re-run too (because successful) - uses: realm/ci-actions/mdb-realm/deploy@fa20eb972b9f018654fdb4e2c7afb52b0532f907 + name: Deploy Cluster (alternative) if: ${{ inputs.realmUrl }} with: projectId: ${{ secrets.AtlasProjectId}} @@ -100,5 +103,7 @@ env: atlasUrl: ${{ inputs.atlasUrl}} apiKey: ${{ secrets.AtlasPublicKey}} privateApiKey: ${{ secrets.AtlasPrivateKey }} + clusterName: #@ "${{ inputs.clusterName }}-" + differentiator clusterSize: M10 +#@ end #@ end \ No newline at end of file diff --git a/.github/workflows/cleanup-baas.yml b/.github/workflows/cleanup-baas.yml index 1f3074653e..2a4fbd9113 100755 --- a/.github/workflows/cleanup-baas.yml +++ b/.github/workflows/cleanup-baas.yml @@ -2,6 +2,12 @@ name: cleanup-baas "on": workflow_call: inputs: + differentiators: + required: true + type: string + clusterName: + required: true + type: string BaseUrl: required: true type: string @@ -20,8 +26,11 @@ env: DOTNET_NOLOGO: true jobs: cleanup-baas: - runs-on: ubuntu-latest name: Cleanup Cluster + runs-on: ubuntu-latest + strategy: + matrix: + differentiator: ${{ fromJson(inputs.differentiators) }} timeout-minutes: 5 steps: - name: Checkout code @@ -36,3 +45,4 @@ jobs: atlasUrl: ${{ inputs.AtlasBaseUrl}} apiKey: ${{ secrets.AtlasPublicKey}} privateApiKey: ${{ secrets.AtlasPrivateKey }} + clusterName: ${{ inputs.clusterName }}-${{ matrix.differentiator }} diff --git a/.github/workflows/deploy-baas.yml b/.github/workflows/deploy-baas.yml index 442b07f2f1..0f772a9ba5 100755 --- a/.github/workflows/deploy-baas.yml +++ b/.github/workflows/deploy-baas.yml @@ -26,12 +26,20 @@ env: DOTNET_NOLOGO: true jobs: deploy-baas: - runs-on: ubuntu-latest name: Deploy Cluster + runs-on: ubuntu-latest + strategy: + matrix: + differentiator: ${{ fromJson(inputs.differentiators) }} outputs: - clusterName: ${{ steps.deploy-cluster.outputs.clusterName }} + clusterName: ${{ steps.generate-cluster-name.outputs.clusterName }} timeout-minutes: 20 steps: + - name: Generate cluster name + id: generate-cluster-name + run: | + name=$( echo ${{ github.run_id }}-${{ github.run_attempt }} | md5sum | cut -c1-7) + echo "clusterName=$name" >> "$GITHUB_OUTPUT" - name: Checkout code uses: actions/checkout@v3 with: @@ -45,7 +53,8 @@ jobs: atlasUrl: ${{ inputs.AtlasBaseUrl}} apiKey: ${{ secrets.AtlasPublicKey}} privateApiKey: ${{ secrets.AtlasPrivateKey }} - clusterSize: M20 + clusterName: ${{ steps.generate-cluster-name.outputs.clusterName }}-${{ matrix.differentiator }} + clusterSize: M5 deploy-apps: name: Deploy Apps needs: deploy-baas @@ -64,5 +73,5 @@ jobs: with: dotnet-version: 6.0.x - name: Deploy Apps - run: dotnet run . --baasurl=${{ inputs.BaseUrl }} --baascluster=${{ needs.deploy-baas.outputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey }} --baasprivateapikey=${{ secrets.AtlasPrivateKey }} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=${{ matrix.differentiator }} + run: dotnet run . --baasurl=${{ inputs.BaseUrl }} --baascluster=${{ needs.deploy-baas.outputs.clusterName }}-${{ matrix.differentiator }} --baasapikey=${{ secrets.AtlasPublicKey }} --baasprivateapikey=${{ secrets.AtlasPrivateKey }} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=${{ matrix.differentiator }} working-directory: Tools/DeployApps \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4f35d9d063..9b4d63b7b0 100755 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -301,6 +301,7 @@ jobs: if: always() name: Cleanup needs: + - deploy-cluster - test-net-framework - test-code-coverage - test-uwp-managed @@ -308,6 +309,8 @@ jobs: - test-ios - test-android with: + differentiators: '["net-framework", "code-coverage", "uwp-managed", "xamarinmacos", "ios", "android", "maccatalyst"]' + clusterName: ${{ needs.deploy-cluster.outputs.clusterName }} BaseUrl: https://realm-qa.mongodb.com AtlasBaseUrl: https://cloud-qa.mongodb.com secrets: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 8f19cac171..6a3ccb3bdc 100755 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -166,8 +166,11 @@ jobs: if: always() name: Cleanup needs: + - deploy-cluster - test-code-coverage with: + differentiators: '["code-coverage"]' + clusterName: ${{ needs.deploy-cluster.outputs.clusterName }} BaseUrl: https://realm-qa.mongodb.com AtlasBaseUrl: https://cloud-qa.mongodb.com secrets: diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index 0fbe11f500..7522fb8381 100755 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -58,6 +58,7 @@ jobs: name: Realm.${{ inputs.version }} path: ${{ github.workspace }}/Realm/packages/ - uses: realm/ci-actions/mdb-realm/deploy@fa20eb972b9f018654fdb4e2c7afb52b0532f907 + name: Deploy Cluster (alternative) if: ${{ inputs.realmUrl }} with: projectId: ${{ secrets.AtlasProjectId}} @@ -65,6 +66,7 @@ jobs: atlasUrl: ${{ inputs.atlasUrl}} apiKey: ${{ secrets.AtlasPublicKey}} privateApiKey: ${{ secrets.AtlasPrivateKey }} + clusterName: ${{ inputs.clusterName }}-android clusterSize: M10 - name: Add msbuild to PATH uses: microsoft/setup-msbuild@0b44c6745b7e81956596964100aadb92d667c497 @@ -85,7 +87,7 @@ jobs: app-id: io.realm.xamarintests project-arn: ${{ secrets.DEVICEFARM_PROJECT_ARN }} device-pool-arn: ${{ secrets.DEVICEFARM_ANDROID_POOL_ARN }} - arguments: ' --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=android' + arguments: ' --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }}-android --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=android' - name: Publish Unit Test Results uses: LaPeste/test-reporter@510caf50a955b1003bec48a6494be4d6537f3a0b if: always() diff --git a/.github/workflows/test-code-coverage.yml b/.github/workflows/test-code-coverage.yml index 3430828f20..66205ea979 100755 --- a/.github/workflows/test-code-coverage.yml +++ b/.github/workflows/test-code-coverage.yml @@ -140,6 +140,7 @@ jobs: name: wrappers-windows-uwp-ARM64 path: wrappers/build - uses: realm/ci-actions/mdb-realm/deploy@fa20eb972b9f018654fdb4e2c7afb52b0532f907 + name: Deploy Cluster (alternative) if: ${{ inputs.realmUrl }} with: projectId: ${{ secrets.AtlasProjectId}} @@ -147,6 +148,7 @@ jobs: atlasUrl: ${{ inputs.atlasUrl}} apiKey: ${{ secrets.AtlasPublicKey}} privateApiKey: ${{ secrets.AtlasPrivateKey }} + clusterName: ${{ inputs.clusterName }}-code-coverage clusterSize: M10 - name: Setup Coverlet & Report Generator run: | @@ -163,7 +165,7 @@ jobs: env: DOTNET_DbgEnableMiniDump: 1 DOTNET_EnableCrashReport: 1 - run: ./tools/coverlet ${{ steps.dotnet-publish.outputs.executable-path }} -t ${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Tests -a '--result=TestResults.Linux.xml --labels=After --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=code-coverage' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*' --exclude '[Realm.PlatformHelpers]*' + run: ./tools/coverlet ${{ steps.dotnet-publish.outputs.executable-path }} -t ${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Tests -a '--result=TestResults.Linux.xml --labels=After --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }}-code-coverage --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=code-coverage' -f lcov -o ./report.lcov --exclude '[Realm.Tests]*' --exclude '[Realm.Fody]*' --exclude '[Realm.PlatformHelpers]*' - name: Archive core dump uses: actions/upload-artifact@v3 if: failure() diff --git a/.github/workflows/test-ios.yml b/.github/workflows/test-ios.yml index a6e0998ecb..cd3b1f0804 100755 --- a/.github/workflows/test-ios.yml +++ b/.github/workflows/test-ios.yml @@ -50,6 +50,7 @@ jobs: name: Realm.${{ inputs.version }} path: ${{ github.workspace }}/Realm/packages/ - uses: realm/ci-actions/mdb-realm/deploy@fa20eb972b9f018654fdb4e2c7afb52b0532f907 + name: Deploy Cluster (alternative) if: ${{ inputs.realmUrl }} with: projectId: ${{ secrets.AtlasProjectId}} @@ -57,6 +58,7 @@ jobs: atlasUrl: ${{ inputs.atlasUrl}} apiKey: ${{ secrets.AtlasPublicKey}} privateApiKey: ${{ secrets.AtlasPrivateKey }} + clusterName: ${{ inputs.clusterName }}-ios clusterSize: M10 - name: Add msbuild to PATH uses: microsoft/setup-msbuild@0b44c6745b7e81956596964100aadb92d667c497 @@ -69,7 +71,7 @@ jobs: appPath: Tests/Tests.iOS/bin/iPhoneSimulator/Release/Tests.iOS.app bundleId: io.realm.dotnettests iphoneToSimulate: iPhone-8 - arguments: --headless --result=${{ github.workspace }}/TestResults.iOS.xml --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=ios + arguments: --headless --result=${{ github.workspace }}/TestResults.iOS.xml --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }}-ios --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=ios - name: Publish Unit Test Results uses: LaPeste/test-reporter@510caf50a955b1003bec48a6494be4d6537f3a0b if: always() diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml index 193a95e255..f01c7c06eb 100755 --- a/.github/workflows/test-macos.yml +++ b/.github/workflows/test-macos.yml @@ -28,7 +28,7 @@ jobs: test-xamarin: runs-on: macos-latest name: Xamarin.macOS - timeout-minutes: 45 + timeout-minutes: 60 steps: - name: Checkout code uses: actions/checkout@v3 @@ -50,6 +50,7 @@ jobs: name: Realm.${{ inputs.version }} path: ${{ github.workspace }}/Realm/packages/ - uses: realm/ci-actions/mdb-realm/deploy@fa20eb972b9f018654fdb4e2c7afb52b0532f907 + name: Deploy Cluster (alternative) if: ${{ inputs.realmUrl }} with: projectId: ${{ secrets.AtlasProjectId}} @@ -57,6 +58,7 @@ jobs: atlasUrl: ${{ inputs.atlasUrl}} apiKey: ${{ secrets.AtlasPublicKey}} privateApiKey: ${{ secrets.AtlasPrivateKey }} + clusterName: ${{ inputs.clusterName }}-xamarinmacos clusterSize: M10 - name: Add msbuild to PATH uses: microsoft/setup-msbuild@0b44c6745b7e81956596964100aadb92d667c497 @@ -64,7 +66,7 @@ jobs: - name: Build Tests/Tests.XamarinMac run: msbuild Tests/Tests.XamarinMac -p:Configuration=Release -restore -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ inputs.version }} - name: Run the tests - run: Tests/Tests.XamarinMac/bin/Release/Tests.XamarinMac.app/Contents/MacOS/Tests.XamarinMac --headless --result=${{ github.workspace }}/TestResults.XamarinMac.xml --labels=All --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=xamarinmacos + run: Tests/Tests.XamarinMac/bin/Release/Tests.XamarinMac.app/Contents/MacOS/Tests.XamarinMac --headless --result=${{ github.workspace }}/TestResults.XamarinMac.xml --labels=All --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }}-xamarinmacos --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=xamarinmacos - name: Publish Unit Test Results uses: LaPeste/test-reporter@510caf50a955b1003bec48a6494be4d6537f3a0b if: always() @@ -79,7 +81,7 @@ jobs: test-maui: runs-on: macos-12 name: Maui.MacCatalyst - timeout-minutes: 45 + timeout-minutes: 60 steps: - name: Checkout code uses: actions/checkout@v3 @@ -105,7 +107,7 @@ jobs: - name: Build the tests run: dotnet build Tests/Tests.Maui -c Release -f net6.0-maccatalyst -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ inputs.version }} - name: Run the tests - run: Tests/Tests.Maui/bin/Release/net6.0-maccatalyst/maccatalyst-x64/Tests.Maui.app/Contents/MacOS/Tests.Maui --headless --result=${{ github.workspace }}/TestResults.MacCatalyst.xml --labels=All --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=maccatalyst + run: Tests/Tests.Maui/bin/Release/net6.0-maccatalyst/maccatalyst-x64/Tests.Maui.app/Contents/MacOS/Tests.Maui --headless --result=${{ github.workspace }}/TestResults.MacCatalyst.xml --labels=All --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }}-maccatalyst --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=maccatalyst - name: Publish Unit Test Results uses: LaPeste/test-reporter@510caf50a955b1003bec48a6494be4d6537f3a0b if: always() diff --git a/.github/workflows/test-net-core.yml b/.github/workflows/test-net-core.yml index 48d79d8e4e..7af144a1b7 100755 --- a/.github/workflows/test-net-core.yml +++ b/.github/workflows/test-net-core.yml @@ -74,15 +74,6 @@ jobs: with: name: Realm.${{ inputs.version }} path: ${{ github.workspace }}/Realm/packages/ - - uses: realm/ci-actions/mdb-realm/deploy@fa20eb972b9f018654fdb4e2c7afb52b0532f907 - if: ${{ inputs.realmUrl }} - with: - projectId: ${{ secrets.AtlasProjectId}} - realmUrl: ${{ inputs.realmUrl }} - atlasUrl: ${{ inputs.atlasUrl}} - apiKey: ${{ secrets.AtlasPublicKey}} - privateApiKey: ${{ secrets.AtlasPrivateKey }} - clusterSize: M10 - name: Publish Tests/Realm.Tests run: dotnet publish Tests/Realm.Tests -c Release -f ${{ matrix.framework }} -r ${{ matrix.os.runtime }} -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ inputs.version }} -p:RealmTestsStandaloneExe=true --no-self-contained - name: Output executable path @@ -93,7 +84,7 @@ jobs: env: DOTNET_DbgEnableMiniDump: 1 DOTNET_EnableCrashReport: 1 - run: ${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Tests --result=TestResults.xml --labels=After --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=net-core-${{ matrix.runner }}-${{ matrix.runtime }} + run: ${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Tests --result=TestResults.xml --labels=After - name: Archive core dump uses: actions/upload-artifact@v3 if: failure() diff --git a/.github/workflows/test-net-framework.yml b/.github/workflows/test-net-framework.yml index 8ffc6d49de..aed0a68395 100755 --- a/.github/workflows/test-net-framework.yml +++ b/.github/workflows/test-net-framework.yml @@ -50,6 +50,7 @@ jobs: name: Realm.${{ inputs.version }} path: ${{ github.workspace }}/Realm/packages/ - uses: realm/ci-actions/mdb-realm/deploy@fa20eb972b9f018654fdb4e2c7afb52b0532f907 + name: Deploy Cluster (alternative) if: ${{ inputs.realmUrl }} with: projectId: ${{ secrets.AtlasProjectId}} @@ -57,6 +58,7 @@ jobs: atlasUrl: ${{ inputs.atlasUrl}} apiKey: ${{ secrets.AtlasPublicKey}} privateApiKey: ${{ secrets.AtlasPrivateKey }} + clusterName: ${{ inputs.clusterName }}-net-framework clusterSize: M10 - name: Add msbuild to PATH uses: microsoft/setup-msbuild@0b44c6745b7e81956596964100aadb92d667c497 @@ -64,7 +66,7 @@ jobs: - name: Build Tests/Realm.Tests run: msbuild Tests/Realm.Tests -p:Configuration=Release -restore -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ inputs.version }} -p:TargetFramework=net461 -p:RealmTestsStandaloneExe=true - name: Run the tests - run: ./Tests/Realm.Tests/bin/Release/net461/Realm.Tests.exe --result=TestResults.Windows.xml --labels=After --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=net-framework + run: ./Tests/Realm.Tests/bin/Release/net461/Realm.Tests.exe --result=TestResults.Windows.xml --labels=After --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }}-net-framework --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=net-framework - name: Publish Unit Test Results uses: LaPeste/test-reporter@510caf50a955b1003bec48a6494be4d6537f3a0b if: always() diff --git a/.github/workflows/test-tvos.yml b/.github/workflows/test-tvos.yml index d44a402e98..da2c3f0132 100755 --- a/.github/workflows/test-tvos.yml +++ b/.github/workflows/test-tvos.yml @@ -49,15 +49,6 @@ jobs: with: name: Realm.${{ inputs.version }} path: ${{ github.workspace }}/Realm/packages/ - - uses: realm/ci-actions/mdb-realm/deploy@fa20eb972b9f018654fdb4e2c7afb52b0532f907 - if: ${{ inputs.realmUrl }} - with: - projectId: ${{ secrets.AtlasProjectId}} - realmUrl: ${{ inputs.realmUrl }} - atlasUrl: ${{ inputs.atlasUrl}} - apiKey: ${{ secrets.AtlasPublicKey}} - privateApiKey: ${{ secrets.AtlasPrivateKey }} - clusterSize: M10 - name: Add msbuild to PATH uses: microsoft/setup-msbuild@0b44c6745b7e81956596964100aadb92d667c497 if: ${{ runner.os == 'Windows' }} @@ -69,7 +60,7 @@ jobs: appPath: Tests/Tests.XamarinTVOS/bin/iPhoneSimulator/Release/Tests.XamarinTVOS.app bundleId: io.realm.Tests-XamarinTVOS iphoneToSimulate: Apple-TV-1080p - arguments: --headless --result=${{ github.workspace }}/TestResults.tvOS.xml --labels=All --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=tvos + arguments: --headless --result=${{ github.workspace }}/TestResults.tvOS.xml --labels=All --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }}-tvos --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=tvos os: tvOS - name: Publish Unit Test Results uses: LaPeste/test-reporter@510caf50a955b1003bec48a6494be4d6537f3a0b diff --git a/.github/workflows/test-uwp-managed.yml b/.github/workflows/test-uwp-managed.yml index b9ffe0b6e0..88f515e4db 100755 --- a/.github/workflows/test-uwp-managed.yml +++ b/.github/workflows/test-uwp-managed.yml @@ -54,6 +54,7 @@ jobs: name: Realm.${{ inputs.version }} path: ${{ github.workspace }}/Realm/packages/ - uses: realm/ci-actions/mdb-realm/deploy@fa20eb972b9f018654fdb4e2c7afb52b0532f907 + name: Deploy Cluster (alternative) if: ${{ inputs.realmUrl }} with: projectId: ${{ secrets.AtlasProjectId}} @@ -61,6 +62,7 @@ jobs: atlasUrl: ${{ inputs.atlasUrl}} apiKey: ${{ secrets.AtlasPublicKey}} privateApiKey: ${{ secrets.AtlasPrivateKey }} + clusterName: ${{ inputs.clusterName }}-uwp-managed clusterSize: M10 - name: Import test certificate run: | @@ -75,7 +77,7 @@ jobs: - name: Build Tests/Tests.UWP run: msbuild Tests/Tests.UWP -p:Configuration=Release -restore -p:RestoreConfigFile=Tests/Test.NuGet.Config -p:UseRealmNupkgsWithVersion=${{ inputs.version }} -p:AppxBundle=Always -p:PackageCertificateKeyFile=${{ github.workspace }}\Tests\Tests.UWP\Tests.UWP_TemporaryKey.pfx -p:PackageCertificatePassword="${{ secrets.Pfx_Password }}" -p:UseDotNetNativeToolchain=false -p:AppxBundlePlatforms=x64 - name: Run the tests - run: ./Tests/Tests.UWP/RunTests.ps1 -ExtraAppArgs ' --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=uwp-managed' + run: ./Tests/Tests.UWP/RunTests.ps1 -ExtraAppArgs ' --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }}-uwp-managed --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=uwp-managed' shell: powershell - name: Publish Unit Test Results uses: LaPeste/test-reporter@510caf50a955b1003bec48a6494be4d6537f3a0b diff --git a/.github/workflows/test-woven-classes.yml b/.github/workflows/test-woven-classes.yml index b9670c41c9..3531864b5e 100644 --- a/.github/workflows/test-woven-classes.yml +++ b/.github/workflows/test-woven-classes.yml @@ -56,7 +56,7 @@ jobs: run: echo 'executable-path=./Tests/Realm.Tests/bin/Release/net7.0/win-x64' >> $GITHUB_OUTPUT shell: bash - name: Run the tests - run: ${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Tests --result=TestResults.WovenClasses.xml --labels=After --baasurl=${{ inputs.realmUrl }} --baascluster=${{ inputs.clusterName }} --baasapikey=${{ secrets.AtlasPublicKey}} --baasprivateapikey=${{ secrets.AtlasPrivateKey}} --baasprojectid=${{ secrets.AtlasProjectId }} --baasdifferentiator=weaved-classes + run: ${{ steps.dotnet-publish.outputs.executable-path }}/Realm.Tests --result=TestResults.WovenClasses.xml --labels=After - name: Publish Unit Test Results uses: LaPeste/test-reporter@510caf50a955b1003bec48a6494be4d6537f3a0b if: always() diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d262b9873..a4a21ffa01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,43 @@ ## vNext (TBD) ### Enhancements +* Added support for customizing the ignore attribute applied on certain generated properties of Realm models. The configuration option is called `realm.custom_ignore_attribute` and can be set in a global configuration file (more information about global configuration files can be found in the [.NET documentation](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-files)). The Realm generator will treat this as an opaque string, that will be appended to the `IgnoreDataMember` and `XmlIgnore` attributes already applied on these members. The attributes must be fully qualified unless the namespace they reside in is added to a global usings file. For example, this is how you would add `JsonIgnore` from `System.Text.Json`: + + ``` + realm.custom_ignore_attribute = [System.Text.Json.Serialization.JsonIgnore] + ``` + (Issue [#2579](https://github.com/realm/realm-dotnet/issues/2579)) + +### Fixed * None +### Compatibility +* Realm Studio: 13.0.0 or later. + +### Internal +* Using Core 13.20.1. + +## 11.5.0 (2023-09-15) + +### Enhancements +* Streamlined some of the error codes reported in `SessionException`. A few error codes have been combined and some have been deprecated since they are no longer reported by the server. (Issue [#3295](https://github.com/realm/realm-dotnet/issues/3295)) +* Full text search supports searching for prefix only. Eg. "description TEXT 'alex*'". (Core 13.18.0) +* Unknown protocol errors received from Atlas Device Sync will no longer cause the application to crash if a valid error action is also received. Unknown error actions will be treated as an ApplicationBug error action and will cause sync to fail with an error via the sync error handler. (Core 13.18.0) +* Added support for server log messages that are enabled by sync protocol version 10. Appservices request id will be provided in a server log message in a future server release. (Core 13.19.0) + ### Fixed * Fixed the message of the `MissingMemberException` being thrown when attempting to access a non-existent property with the dynamic API. (PR [#3432](https://github.com/realm/realm-dotnet/pull/3432)) * Fixed a `Cannot marshal generic Windows Runtime types with a non Windows Runtime type as a generic type argument` build error when using .NET Native. (Issue [#3434](https://github.com/realm/realm-dotnet/issues/3434), since 11.4.0) +* Fix failed assertion for unknown app server errors. (Core 13.17.2) +* Running a query on @keys in a Dictionary would throw an exception. (Core 13.17.2) +* Fixed crash in slab allocator (`Assertion failed: ref + size <= next->first`). (Core 13.20.1) +* Sending empty UPLOAD messages may lead to 'Bad server version' errors and client reset. (Core 13.20.1) ### Compatibility * Realm Studio: 13.0.0 or later. ### Internal -* Using Core x.y.z. +* Using Core 13.20.1. ## 11.4.0 (2023-08-16) diff --git a/Realm/AssemblyInfo.props b/Realm/AssemblyInfo.props index 18ea8c3f38..6329542431 100644 --- a/Realm/AssemblyInfo.props +++ b/Realm/AssemblyInfo.props @@ -1,7 +1,7 @@ Realm .NET - 11.4.0 + 11.5.0 Realm is a mobile database: a replacement for SQLite Realm Inc. Copyright © $([System.DateTime]::Now.ToString(yyyy)) Realm Inc. diff --git a/Realm/Realm.SourceGenerator/ClassCodeBuilder.cs b/Realm/Realm.SourceGenerator/ClassCodeBuilder.cs index 68d2f6c36f..51d4d84ffc 100644 --- a/Realm/Realm.SourceGenerator/ClassCodeBuilder.cs +++ b/Realm/Realm.SourceGenerator/ClassCodeBuilder.cs @@ -43,16 +43,29 @@ internal class ClassCodeBuilder }; private readonly ClassInfo _classInfo; + private readonly Lazy _ignoreFieldAttribute; private readonly string _helperClassName; private readonly string _accessorInterfaceName; private readonly string _managedAccessorClassName; private readonly string _unmanagedAccessorClassName; - public ClassCodeBuilder(ClassInfo classInfo) + public ClassCodeBuilder(ClassInfo classInfo, GeneratorConfig generatorConfig) { _classInfo = classInfo; + _ignoreFieldAttribute = new(() => + { + var result = "[IgnoreDataMember, XmlIgnore]"; + var customAttribute = generatorConfig.CustomIgnoreAttribute; + if (!string.IsNullOrEmpty(customAttribute)) + { + result += customAttribute; + } + + return result; + }); + var className = _classInfo.Name; _helperClassName = $"{className}ObjectHelper"; @@ -287,36 +300,36 @@ private string GeneratePartialClass(string interfaceString, string managedAccess private {_accessorInterfaceName} Accessor => _accessor ??= new {_unmanagedAccessorClassName}(typeof({_classInfo.Name})); /// -[IgnoreDataMember, XmlIgnore] +{_ignoreFieldAttribute.Value} public bool IsManaged => Accessor.IsManaged; /// -[IgnoreDataMember, XmlIgnore] +{_ignoreFieldAttribute.Value} public bool IsValid => Accessor.IsValid; /// -[IgnoreDataMember, XmlIgnore] +{_ignoreFieldAttribute.Value} public bool IsFrozen => Accessor.IsFrozen; /// -[IgnoreDataMember, XmlIgnore] +{_ignoreFieldAttribute.Value} public Realms.Realm? Realm => Accessor.Realm; /// -[IgnoreDataMember, XmlIgnore] +{_ignoreFieldAttribute.Value} public Realms.Schema.ObjectSchema ObjectSchema => Accessor.ObjectSchema!; /// -[IgnoreDataMember, XmlIgnore] +{_ignoreFieldAttribute.Value} public Realms.DynamicObjectApi DynamicApi => Accessor.DynamicApi; /// -[IgnoreDataMember, XmlIgnore] +{_ignoreFieldAttribute.Value} public int BacklinksCount => Accessor.BacklinksCount; {(_classInfo.ObjectType != ObjectType.EmbeddedObject ? string.Empty : -@"/// -[IgnoreDataMember, XmlIgnore] +$@"/// +{_ignoreFieldAttribute.Value} public Realms.IRealmObjectBase? Parent => Accessor.GetParent();")} void ISettableManagedAccessor.SetManagedAccessor(Realms.IRealmAccessor managedAccessor, Realms.Weaving.IRealmObjectHelper? helper, bool update, bool skipDefaults) diff --git a/Realm/Realm.SourceGenerator/CodeEmitter.cs b/Realm/Realm.SourceGenerator/CodeEmitter.cs index 92d26c39d4..218a15a9a4 100644 --- a/Realm/Realm.SourceGenerator/CodeEmitter.cs +++ b/Realm/Realm.SourceGenerator/CodeEmitter.cs @@ -28,10 +28,12 @@ namespace Realms.SourceGenerator internal class CodeEmitter { private readonly GeneratorExecutionContext _context; + private readonly GeneratorConfig _generatorConfig; - public CodeEmitter(GeneratorExecutionContext context) + public CodeEmitter(GeneratorExecutionContext context, GeneratorConfig generatorConfig) { _context = context; + _generatorConfig = generatorConfig; } public void Emit(ParsingResults parsingResults) @@ -45,7 +47,7 @@ public void Emit(ParsingResults parsingResults) try { - var generatedSource = new ClassCodeBuilder(classInfo).GenerateSource(); + var generatedSource = new ClassCodeBuilder(classInfo, _generatorConfig).GenerateSource(); // Replace all occurrences of at least 3 newlines with only 2 var formattedSource = Regex.Replace(generatedSource, @$"[{Environment.NewLine}]{{3,}}", $"{Environment.NewLine}{Environment.NewLine}"); @@ -65,9 +67,6 @@ public void Emit(ParsingResults parsingResults) } } - private static bool ShouldEmit(ClassInfo classInfo) - { - return !classInfo.Diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error); - } + private static bool ShouldEmit(ClassInfo classInfo) => classInfo.Diagnostics.All(d => d.Severity != DiagnosticSeverity.Error); } } diff --git a/Realm/Realm.SourceGenerator/Diagnostics.cs b/Realm/Realm.SourceGenerator/Diagnostics.cs index 986ced9c9e..3695895d49 100644 --- a/Realm/Realm.SourceGenerator/Diagnostics.cs +++ b/Realm/Realm.SourceGenerator/Diagnostics.cs @@ -51,10 +51,20 @@ private enum Id RealmObjectWithoutAutomaticProperty = 25, ParentOfNestedClassIsNotPartial = 27, IndexedPrimaryKey = 28, + InvalidGeneratorConfiguration = 1000, } #region Errors + public static Diagnostic InvalidConfiguration(string field, string description) + { + return CreateDiagnosticError( + Id.InvalidGeneratorConfiguration, + "Invalid source generator configuration", + $"The generator configuration for {field} is invalid: {description}", + Location.None); + } + public static Diagnostic UnexpectedError(string className, string message, string stackTrace) { return CreateDiagnosticError( diff --git a/Realm/Realm.SourceGenerator/DiagnosticsEmitter.cs b/Realm/Realm.SourceGenerator/DiagnosticsEmitter.cs index f993604e08..4b73f92e4a 100644 --- a/Realm/Realm.SourceGenerator/DiagnosticsEmitter.cs +++ b/Realm/Realm.SourceGenerator/DiagnosticsEmitter.cs @@ -24,22 +24,30 @@ namespace Realms.SourceGenerator { internal class DiagnosticsEmitter { - private GeneratorExecutionContext _context; + private readonly GeneratorExecutionContext _context; - public DiagnosticsEmitter(GeneratorExecutionContext context) + public DiagnosticsEmitter(GeneratorExecutionContext context, GeneratorConfig generatorConfig) { _context = context; - } - public void Emit(ParsingResults parsingResults) - { - foreach (var classInfo in parsingResults.ClassInfo) + var customIgnoreAttribute = generatorConfig.CustomIgnoreAttribute; + if (!string.IsNullOrEmpty(customIgnoreAttribute)) { - if (!classInfo.Diagnostics.Any()) + if (!customIgnoreAttribute!.StartsWith("[") || !customIgnoreAttribute.EndsWith("]")) { - continue; + _context.ReportDiagnostic(Diagnostics.InvalidConfiguration( + field: "realm.custom_ignore_attribute", + description: $"The attribute(s) string should start with '[' and end with ']'. Actual value: {customIgnoreAttribute}.")); + + generatorConfig.CustomIgnoreAttribute = null; } + } + } + public void Emit(ParsingResults parsingResults) + { + foreach (var classInfo in parsingResults.ClassInfo.Where(classInfo => classInfo.Diagnostics.Any())) + { try { SerializeDiagnostics(_context, classInfo); diff --git a/Realm/Realm.SourceGenerator/GeneratorConfig.cs b/Realm/Realm.SourceGenerator/GeneratorConfig.cs index 4aa6c4794d..b3da75e51c 100644 --- a/Realm/Realm.SourceGenerator/GeneratorConfig.cs +++ b/Realm/Realm.SourceGenerator/GeneratorConfig.cs @@ -20,18 +20,20 @@ namespace Realms.SourceGenerator { - internal class GeneratorConfig + internal record GeneratorConfig(bool IgnoreObjectsNullability) { - public bool IgnoreObjectsNullability { get; private set; } + public string? CustomIgnoreAttribute { get; set; } public static GeneratorConfig ParseConfig(AnalyzerConfigOptions analyzerConfigOptions) { analyzerConfigOptions.TryGetValue("realm.ignore_objects_nullability", out var ignoreObjectsNullabilityString); var ignoreObjectsNullability = ignoreObjectsNullabilityString == "true"; - return new GeneratorConfig + analyzerConfigOptions.TryGetValue("realm.custom_ignore_attribute", out var customIgnoreAttribute); + + return new GeneratorConfig(ignoreObjectsNullability) { - IgnoreObjectsNullability = ignoreObjectsNullability + CustomIgnoreAttribute = customIgnoreAttribute }; } } diff --git a/Realm/Realm.SourceGenerator/Parser.cs b/Realm/Realm.SourceGenerator/Parser.cs index 30777aff69..b419854a9f 100644 --- a/Realm/Realm.SourceGenerator/Parser.cs +++ b/Realm/Realm.SourceGenerator/Parser.cs @@ -288,11 +288,11 @@ private PropertyTypeInfo GetPropertyTypeInfo(ClassInfo classInfo, PropertyInfo p return propertyTypeInfo; } - if (propertySymbol is INamedTypeSymbol { SpecialType: SpecialType.System_DateTime }) + if (typeSymbol is INamedTypeSymbol { SpecialType: SpecialType.System_DateTime }) { classInfo.Diagnostics.Add(Diagnostics.DateTimeNotSupported(classInfo.Name, propertySymbol.Name, propertyLocation)); } - else if (propertySymbol.Type.Name == "List") + else if (typeSymbol.Name == "List") { classInfo.Diagnostics.Add(Diagnostics.ListWithoutInterface(classInfo.Name, propertySymbol.Name, propertyLocation)); } @@ -434,7 +434,7 @@ INamedTypeSymbol when typeSymbol.IsValidIntegerType() => PropertyTypeInfo.Int, INamedTypeSymbol when typeSymbol.SpecialType == SpecialType.System_Double => PropertyTypeInfo.Double, INamedTypeSymbol when typeSymbol.SpecialType == SpecialType.System_String => PropertyTypeInfo.String, INamedTypeSymbol when typeSymbol.SpecialType == SpecialType.System_Decimal || typeSymbol.Name == "Decimal128" => PropertyTypeInfo.Decimal, - ITypeSymbol when typeSymbol.ToDisplayString() == "byte[]" => PropertyTypeInfo.Data, + _ when typeSymbol.ToDisplayString() == "byte[]" => PropertyTypeInfo.Data, INamedTypeSymbol when typeSymbol.Name == "ObjectId" => PropertyTypeInfo.ObjectId, INamedTypeSymbol when typeSymbol.Name == "Guid" => PropertyTypeInfo.Guid, INamedTypeSymbol when typeSymbol.Name == "DateTimeOffset" => PropertyTypeInfo.Date, diff --git a/Realm/Realm.SourceGenerator/RealmGenerator.cs b/Realm/Realm.SourceGenerator/RealmGenerator.cs index 62796a39ec..9f4d026de9 100644 --- a/Realm/Realm.SourceGenerator/RealmGenerator.cs +++ b/Realm/Realm.SourceGenerator/RealmGenerator.cs @@ -39,10 +39,10 @@ public void Execute(GeneratorExecutionContext context) var parser = new Parser(context, generatorConfig); var parsingResults = parser.Parse(scr.RealmClasses); - var diagnosticsEmitter = new DiagnosticsEmitter(context); + var diagnosticsEmitter = new DiagnosticsEmitter(context, generatorConfig); diagnosticsEmitter.Emit(parsingResults); - var codeEmitter = new CodeEmitter(context); + var codeEmitter = new CodeEmitter(context, generatorConfig); codeEmitter.Emit(parsingResults); } } diff --git a/Realm/Realm.Unity/package.json b/Realm/Realm.Unity/package.json index 323f702842..9aec66953a 100644 --- a/Realm/Realm.Unity/package.json +++ b/Realm/Realm.Unity/package.json @@ -1,6 +1,6 @@ { "name": "io.realm.unity", - "version": "11.4.0", + "version": "11.5.0", "displayName": "Realm", "description": "Realm is an embedded, object-oriented database that lets you build real-time, always-on applications. With Realm, data is directly exposed as objects and queryable by code, removing the need for ORM's riddled with performance & maintenance issues. Additionally, objects and collections in Realm are always live, meaning that they always reflect the latest data stored in the database. You can subscribe to changes, letting you keep your UI consistently up to date.\nThe .NET Realm SDK also provide access to Atlas App Services, a secure backend that can sync data between devices, authenticate and manage users, and run serverless JavaScript functions.", "unity": "2021.1", diff --git a/Realm/Realm.Weaver/Analytics/Analytics.cs b/Realm/Realm.Weaver/Analytics/Analytics.cs index abb26eb1e7..ce0e16a954 100644 --- a/Realm/Realm.Weaver/Analytics/Analytics.cs +++ b/Realm/Realm.Weaver/Analytics/Analytics.cs @@ -470,14 +470,20 @@ private string GetJsonPayload() jsonPayload.Append('{'); - AppendKeyValues(_realmEnvMetrics); - jsonPayload.Append(','); - AppendKeyValues(_realmFeaturesToAnalyze, Metric.SdkFeatures); + jsonPayload.Append(GetJsonString(_realmEnvMetrics)); + + var featuresString = GetJsonString(_realmFeaturesToAnalyze, Metric.SdkFeatures); + if (!string.IsNullOrEmpty(featuresString)) + { + jsonPayload.Append(','); + jsonPayload.Append(featuresString); + } + jsonPayload.Append('}'); return jsonPayload.ToString(); - void AppendKeyValues(IDictionary dict, IDictionary? keyMapping = null) + string GetJsonString(IDictionary dict, IDictionary? keyMapping = null) { var mapping = dict .Select(kvp => @@ -495,7 +501,7 @@ void AppendKeyValues(IDictionary dict, IDictionary s != null); - jsonPayload.Append(string.Join(",", mapping)); + return string.Join(",", mapping); } } diff --git a/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs b/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs index 30755fb319..e8c1c09666 100644 --- a/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs +++ b/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs @@ -93,27 +93,45 @@ public event PropertyChangedEventHandler? PropertyChanged } } - [IgnoreDataMember] + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public int Count { get => IsValid ? Handle.Value.Count() : 0; } - [IgnoreDataMember, XmlIgnore] // XmlIgnore seems to be needed here as IgnoreDataMember is not sufficient for XmlSerializer. + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public ObjectSchema? ObjectSchema => Metadata?.Schema; Metadata? IMetadataObject.Metadata => Metadata; - [IgnoreDataMember] + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public bool IsManaged => Realm != null; - [IgnoreDataMember] + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public bool IsValid => Handle.Value.IsValid; - [IgnoreDataMember] + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public bool IsFrozen => Realm?.IsFrozen == true; - [IgnoreDataMember] + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public Realm Realm { get; } IThreadConfinedHandle IThreadConfined.Handle => Handle.Value; diff --git a/Realm/Realm/DatabaseTypes/RealmObjectBase.cs b/Realm/Realm/DatabaseTypes/RealmObjectBase.cs index ea2b1f9602..674b4bdb5f 100644 --- a/Realm/Realm/DatabaseTypes/RealmObjectBase.cs +++ b/Realm/Realm/DatabaseTypes/RealmObjectBase.cs @@ -78,6 +78,9 @@ public event PropertyChangedEventHandler? PropertyChanged /// Gets the accessor that encapsulates the methods and properties used by the object for its functioning. /// [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif IRealmAccessor IRealmObjectBase.Accessor => _accessor; /// @@ -85,14 +88,20 @@ public event PropertyChangedEventHandler? PropertyChanged /// . /// /// true if object belongs to a Realm; false if standalone. - [IgnoreDataMember] + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public bool IsManaged => _accessor.IsManaged; /// /// Gets an object encompassing the dynamic API for this RealmObjectBase instance. /// /// A instance that wraps this RealmObject. - [IgnoreDataMember] + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public DynamicObjectApi DynamicApi => _accessor.DynamicApi; /// @@ -102,7 +111,10 @@ public event PropertyChangedEventHandler? PropertyChanged /// Unmanaged objects are always considered valid. /// /// true if managed and part of the Realm or unmanaged; false if managed but deleted. - [IgnoreDataMember] + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public bool IsValid => _accessor.IsValid; /// @@ -112,21 +124,30 @@ public event PropertyChangedEventHandler? PropertyChanged /// /// true if the object is frozen and immutable; false otherwise. /// - [IgnoreDataMember] + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public bool IsFrozen => _accessor.IsFrozen; /// /// Gets the instance this object belongs to, or null if it is unmanaged. /// /// The instance this object belongs to. - [IgnoreDataMember] + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public Realm? Realm => _accessor.Realm; /// /// Gets the instance that describes how the this object belongs to sees it. /// /// A collection of properties describing the underlying schema of this object. - [IgnoreDataMember, XmlIgnore] // XmlIgnore seems to be needed here as IgnoreDataMember is not sufficient for XmlSerializer. + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public ObjectSchema? ObjectSchema => _accessor.ObjectSchema; /// @@ -136,7 +157,10 @@ public event PropertyChangedEventHandler? PropertyChanged /// This property is not observable so the event will not fire when its value changes. /// /// The number of objects referring to this one. - [IgnoreDataMember] + [IgnoreDataMember, XmlIgnore] +#if NET6_0_OR_GREATER + [System.Text.Json.Serialization.JsonIgnore] +#endif public int BacklinksCount => _accessor.BacklinksCount; internal RealmObjectBase() diff --git a/Realm/Realm/Exceptions/Sync/ClientError.cs b/Realm/Realm/Exceptions/Sync/ClientError.cs index 4697cc5710..6cf93d3133 100644 --- a/Realm/Realm/Exceptions/Sync/ClientError.cs +++ b/Realm/Realm/Exceptions/Sync/ClientError.cs @@ -26,12 +26,6 @@ internal enum ClientError AutoClientResetFailed = 132, } - internal enum SessionErrorCategory : byte - { - ClientError = 0, - SessionError = 1 - } - internal enum ServerRequestsAction { NoAction = 0, diff --git a/Realm/Realm/Exceptions/Sync/ErrorCode.cs b/Realm/Realm/Exceptions/Sync/ErrorCode.cs index fd1fc7cf0d..f78d79dfeb 100644 --- a/Realm/Realm/Exceptions/Sync/ErrorCode.cs +++ b/Realm/Realm/Exceptions/Sync/ErrorCode.cs @@ -16,125 +16,201 @@ // //////////////////////////////////////////////////////////////////////////// -namespace Realms.Sync.Exceptions +using System; +using Realms.Sync.ErrorHandling; +using static System.Net.WebRequestMethods; + +namespace Realms.Sync.Exceptions; + +/// +/// Error code enumeration, indicating the type of the session error. +/// +/// +public enum ErrorCode { /// - /// Error code enumeration, indicating the type of the session error. - /// - /// - public enum ErrorCode - { - /// - /// Unrecognized error code. It usually indicates incompatibility between the authentication server and client SDK versions. - /// - Unknown = -1, - - /// - /// Other session level error has occurred. - /// - OtherSessionError = 201, - - /// - /// Path to Realm is invalid. - /// - IllegalRealmPath = 204, - - /// - /// Permission to Realm has been denied. - /// - PermissionDenied = 206, - - /// - /// The client file identifier is invalid. - /// - BadClientFileIdentifier = 208, - - /// - /// The server version is invalid. - /// - BadServerVersion = 209, - - /// - /// The client version is invalid. - /// - BadClientVersion = 210, - - /// - /// Histories have diverged and cannot be merged. - /// - DivergingHistories = 211, - - /// - /// The changeset is invalid. - /// - BadChangeset = 212, - - /// - /// The client file is invalid. - /// - BadClientFile = 217, - - /// - /// Client file has expired likely due to history compaction on the server. - /// - ClientFileExpired = 222, - - /// - /// The user for this session doesn't match the user who originally created the file. This can happen - /// if you explicitly specify the Realm file path in the configuration and you open the Realm first with - /// user A, then with user B without changing the on-disk path. - /// - UserMismatch = 223, - - /// - /// The server has received too many sessions from this client. This is typically a transient error - /// but can also indicate that the client has too many Realms open at the same time. - /// - TooManySessions = 224, - - /// - /// The client attempted to upload an invalid schema change - either an additive schema change - /// when developer mode is off or a destructive schema change. - /// - InvalidSchemaChange = 225, - - /// - /// The client attempted to create a subscription for a query is invalid/malformed. - /// - BadQuery = 226, - - /// - /// The client attempted to create an object that already exists outside their view. - /// - ObjectAlreadyExists = 227, - - /// - /// The server permissions for this file have changed since the last time it was used. - /// - ServerPermissionsChanged = 228, - - /// - /// The client tried to synchronize before initial sync has completed. Please wait for - /// the server process to complete and try again. - /// - InitialSyncNotCompleted = 229, - - /// - /// Client attempted a write that is disallowed by permissions, or modifies an object - /// outside the current query - requires client reset. - /// - WriteNotAllowed = 230, - - /// - /// Client attempted a write that is disallowed by permissions, or modifies an - /// object outside the current query, and the server undid the modification. - /// - CompensatingWrite = 231, - - /// - /// An error sent by the server when its data structures used to track client progress - /// become corrupted. - /// - BadProgress = 233, - } + /// Unrecognized error code. It usually indicates incompatibility between the App Services server and client SDK versions. + /// + RuntimeError = 1000, + + /// + /// The partition value specified by the user is not valid - i.e. its the wrong type or is encoded incorrectly. + /// + BadPartitionValue = 1029, + + /// + /// A fundamental invariant in the communication between the client and the server was not upheld. This typically indicates + /// a bug in the synchronization layer and should be reported at https://github.com/realm/realm-core/issues. + /// + ProtocolInvariantFailed = 1038, + + /// + /// The changeset is invalid. + /// + BadChangeset = 1015, + + /// + /// The client attempted to create a subscription for a query is invalid/malformed. + /// + BadQuery = 1031, + + /// + /// A client reset has occurred. This error code will only be reported via a and only + /// in the case manual client reset handling is required - either via or when + /// ManualResetFallback is invoked on one of the automatic client reset handlers. + /// + /// + /// + ClientReset = 1032, + + /// + /// The client attempted to upload an invalid schema change - either an additive schema change + /// when developer mode is off or a destructive schema change. + /// + InvalidSchemaChange = 1036, + + /// + /// Permission to Realm has been denied. + /// + PermissionDenied = 1037, + + /// + /// The server permissions for this file have changed since the last time it was used. + /// + ServerPermissionsChanged = 1040, + + /// + /// The user for this session doesn't match the user who originally created the file. This can happen + /// if you explicitly specify the Realm file path in the configuration and you open the Realm first with + /// user A, then with user B without changing the on-disk path. + /// + UserMismatch = 1041, + + /// + /// Client attempted a write that is disallowed by permissions, or modifies an object + /// outside the current query - this will result in a . + /// + WriteNotAllowed = 1044, + + /// + /// Automatic client reset has failed. This will only be reported via + /// when an automatic client reset handler was used but it failed to perform the client reset operation - + /// typically due to a breaking schema change in the server schema or due to an exception occurring in the + /// before or after client reset callbacks. + /// + AutoClientResetFailed = 1028, + + /// + /// The wrong sync type was used to connect to the server. This means that you're using + /// to connect to an app configured for flexible sync or that you're using to connect + /// to an app configured to use partition sync. + /// + WrongSyncType = 1043, + + /// + /// Unrecognized error code. It usually indicates incompatibility between the App Services server and client SDK versions. + /// + [Obsolete("Use RuntimeError instead.")] + Unknown = RuntimeError, + + /// + /// Other session level error has occurred. + /// + /// + /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. + /// + [Obsolete("Use RuntimeError instead.")] + OtherSessionError = RuntimeError, + + /// + /// Path to Realm is invalid. + /// + /// + /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. + /// + [Obsolete("Use BadPartitionValue instead")] + IllegalRealmPath = BadPartitionValue, + + /// + /// The client file identifier is invalid. + /// + /// + [Obsolete("Use ClientReset instead")] + BadClientFileIdentifier = ClientReset, + + /// + /// The server version is invalid. + /// + /// + /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. + /// + [Obsolete("Use ProtocolInvariantFailed instead")] + BadServerVersion = ProtocolInvariantFailed, + + /// + /// The client version is invalid. + /// + /// + /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. + /// + [Obsolete("Use ProtocolInvariantFailed instead")] + BadClientVersion = ProtocolInvariantFailed, + + /// + /// Histories have diverged and cannot be merged. + /// + /// + [Obsolete("Use ClientReset instead")] + DivergingHistories = ClientReset, + + /// + /// The client file is invalid. + /// + /// + [Obsolete("Use ClientReset instead")] + BadClientFile = ClientReset, + + /// + /// Client file has expired likely due to history compaction on the server. + /// + /// + [Obsolete("Use ClientReset instead")] + ClientFileExpired = ClientReset, + + /// + /// The server has received too many sessions from this client. This is typically a transient error + /// but can also indicate that the client has too many Realms open at the same time. + /// + [Obsolete("This error code is no longer reported")] + TooManySessions = -2, + + /// + /// The client attempted to create an object that already exists outside their view. + /// + [Obsolete("This error code is no longer reported")] + ObjectAlreadyExists = -3, + + /// + /// The client tried to synchronize before initial sync has completed. Please wait for + /// the server process to complete and try again. + /// + [Obsolete("This error code is no longer reported")] + InitialSyncNotCompleted = -4, + + /// + /// Client attempted a write that is disallowed by permissions, or modifies an + /// object outside the current query, and the server undid the modification. + /// + /// + CompensatingWrite = 1033, + + /// + /// An error sent by the server when its data structures used to track client progress + /// become corrupted. + /// + /// + /// Sync error reporting has been simplified and some errors have been unified. See the obsoletion message for details on the new error code. + /// + [Obsolete("Use ProtocolInvariantFailed instead")] + BadProgress = ProtocolInvariantFailed, } diff --git a/Realm/Realm/Extensions/TestingExtensions.cs b/Realm/Realm/Extensions/TestingExtensions.cs index ab270a54ec..8e9917138a 100644 --- a/Realm/Realm/Extensions/TestingExtensions.cs +++ b/Realm/Realm/Extensions/TestingExtensions.cs @@ -44,7 +44,7 @@ public static void SimulateError(this Session session, ErrorCode errorCode, stri Argument.NotNull(session, nameof(session)); Argument.NotNull(message, nameof(message)); - session.ReportErrorForTesting((int)errorCode, SessionErrorCategory.SessionError, message, isFatal, ServerRequestsAction.ApplicationBug); + session.ReportErrorForTesting((int)errorCode, message, isFatal, ServerRequestsAction.ApplicationBug); } } } diff --git a/Realm/Realm/Handles/SessionHandle.cs b/Realm/Realm/Handles/SessionHandle.cs index 463a25fe64..e2c958603d 100644 --- a/Realm/Realm/Handles/SessionHandle.cs +++ b/Realm/Realm/Handles/SessionHandle.cs @@ -102,7 +102,7 @@ public static extern ulong register_progress_notifier(SessionHandle session, public static extern void wait(SessionHandle session, IntPtr task_completion_source, ProgressDirection direction, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_report_error_for_testing", CallingConvention = CallingConvention.Cdecl)] - public static extern void report_error_for_testing(SessionHandle session, int error_code, SessionErrorCategory error_category, [MarshalAs(UnmanagedType.LPWStr)] string message, IntPtr message_len, [MarshalAs(UnmanagedType.U1)] bool is_fatal, int action); + public static extern void report_error_for_testing(SessionHandle session, int error_code, [MarshalAs(UnmanagedType.LPWStr)] string message, IntPtr message_len, [MarshalAs(UnmanagedType.U1)] bool is_fatal, int action); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "realm_syncsession_stop", CallingConvention = CallingConvention.Cdecl)] public static extern void stop(SessionHandle session, out NativeException ex); @@ -246,9 +246,9 @@ public IntPtr GetRawPointer() return NativeMethods.get_raw_pointer(this); } - public void ReportErrorForTesting(int errorCode, SessionErrorCategory errorCategory, string errorMessage, bool isFatal, ServerRequestsAction action) + public void ReportErrorForTesting(int errorCode, string errorMessage, bool isFatal, ServerRequestsAction action) { - NativeMethods.report_error_for_testing(this, errorCode, errorCategory, errorMessage, (IntPtr)errorMessage.Length, isFatal, (int)action); + NativeMethods.report_error_for_testing(this, errorCode, errorMessage, (IntPtr)errorMessage.Length, isFatal, (int)action); } public void Stop() diff --git a/Realm/Realm/Sync/Session.cs b/Realm/Realm/Sync/Session.cs index ac20ce3437..a07eb8496d 100644 --- a/Realm/Realm/Sync/Session.cs +++ b/Realm/Realm/Sync/Session.cs @@ -222,7 +222,8 @@ internal void CloseHandle(bool waitForShutdown = false) } } - internal void ReportErrorForTesting(int errorCode, SessionErrorCategory sessionErrorCategory, string errorMessage, bool isFatal, ServerRequestsAction action) => Handle.ReportErrorForTesting(errorCode, sessionErrorCategory, errorMessage, isFatal, action); + internal void ReportErrorForTesting(int errorCode, string errorMessage, bool isFatal, ServerRequestsAction action) + => Handle.ReportErrorForTesting(errorCode, errorMessage, isFatal, action); internal void RaisePropertyChanged(string propertyName) { diff --git a/Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs b/Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs index 59c8c1cf89..a7fa3431d0 100644 --- a/Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs +++ b/Tests/Realm.Tests/Sync/AsymmetricObjectTests.cs @@ -28,8 +28,6 @@ using Realms.Sync; #if TEST_WEAVER using TestAsymmetricObject = Realms.AsymmetricObject; -using TestEmbeddedObject = Realms.EmbeddedObject; -using TestRealmObject = Realms.RealmObject; #else using TestAsymmetricObject = Realms.IAsymmetricObject; #endif @@ -90,31 +88,12 @@ public class AsymmetricObjectTests : SyncTestBase new object[] { "NullableGuidProperty", Guid.Parse("{C4EC8CEF-D62A-405E-83BB-B0A3D8DABB36}") } }; - [Test] - public void AddAsymmetricObjNotInSchema_Throws() - { - SyncTestHelpers.RunBaasTestAsync(async () => - { - var flxConfig = await GetFLXIntegrationConfigAsync(); - using var realm = await GetRealmAsync(flxConfig); - - Assert.Throws(() => - { - realm.Write(() => - { - realm.Add(new BasicAsymmetricObject()); - }); - }); - }); - } - [Test] public void AddCollectionOfAsymmetricObjs() { SyncTestHelpers.RunBaasTestAsync(async () => { var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject) }; using var realm = await GetRealmAsync(flxConfig); var partitionLike = Guid.NewGuid().ToString(); @@ -148,7 +127,6 @@ public void AddCollection_WithSomeObjectsAlreadyAdded_Throws() SyncTestHelpers.RunBaasTestAsync(async () => { var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject) }; using var realm = await GetRealmAsync(flxConfig); var partitionLike = Guid.NewGuid().ToString(); @@ -183,7 +161,6 @@ public void AddHugeAsymmetricObj() ObjectId id = default; var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(AsymmetricObjectWithAllTypes) }; using var realm = await GetRealmAsync(flxConfig); realm.Write(() => @@ -206,9 +183,7 @@ public void AccessAsymmetricObjAfterAddedToRealm_Throws() SyncTestHelpers.RunBaasTestAsync(async () => { var partitionLike = Guid.NewGuid().ToString(); - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject) }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var asymmetribObj = new BasicAsymmetricObject { @@ -233,9 +208,7 @@ public void AddSameAsymmetricObjTwice_Throws() { SyncTestHelpers.RunBaasTestAsync(async () => { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject) }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var partitionLike = Guid.NewGuid().ToString(); var asymmetricObj = new BasicAsymmetricObject { @@ -261,8 +234,6 @@ public void SetAndRemotelyReadValue(string propertyName, object propertyValue) { ObjectId id = default; var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(AsymmetricObjectWithAllTypes) }; - using var realm = await GetRealmAsync(flxConfig); realm.Write(() => @@ -290,7 +261,6 @@ public void MixAddingObjectAsymmetricAndNot() var partitionLike = Guid.NewGuid().ToString(); var id = new Random().Next(); var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject), typeof(PrimaryKeyInt32Object) }; flxConfig.PopulateInitialSubscriptions = (realm) => { @@ -346,15 +316,7 @@ public void EmbeddedObject_WhenParentAccessed_ReturnsParent() { SyncTestHelpers.RunBaasTestAsync(async () => { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] - { - typeof(AsymmetricObjectWithEmbeddedRecursiveObject), - typeof(EmbeddedLevel1), - typeof(EmbeddedLevel2), - typeof(EmbeddedLevel3) - }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var parent = new AsymmetricObjectWithEmbeddedRecursiveObject { @@ -387,9 +349,7 @@ public void EmbeddedObject_WhenParentAccessedInList_ReturnsParent() { SyncTestHelpers.RunBaasTestAsync(async () => { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(AsymmetricObjectWithEmbeddedListObject), typeof(EmbeddedIntPropertyObject) }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var parent = new AsymmetricObjectWithEmbeddedListObject(); parent.EmbeddedListObject.Add(new EmbeddedIntPropertyObject()); @@ -408,9 +368,7 @@ public void EmbeddedObject_WhenParentAccessedInDictionary_ReturnsParent() { SyncTestHelpers.RunBaasTestAsync(async () => { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(AsymmetricObjectWithEmbeddedDictionaryObject), typeof(EmbeddedIntPropertyObject) }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var parent = new AsymmetricObjectWithEmbeddedDictionaryObject(); parent.EmbeddedDictionaryObject.Add("child", new EmbeddedIntPropertyObject()); @@ -452,9 +410,7 @@ public void NonEmbeddedObject_WhenParentAccessed_Throws() { SyncTestHelpers.RunBaasTestAsync(async () => { - var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(BasicAsymmetricObject) }; - using var realm = await GetRealmAsync(flxConfig); + using var realm = await GetFLXIntegrationRealmAsync(); var topLevel = new BasicAsymmetricObject { @@ -627,7 +583,6 @@ public void DynamicAccess([Values(true, false)] bool isDynamic) { var flxConfig = await GetFLXIntegrationConfigAsync(); flxConfig.IsDynamic = isDynamic; - flxConfig.Schema = new[] { typeof(AsymmetricObjectWithAllTypes) }; using var realm = await GetRealmAsync(flxConfig); realm.Write(() => @@ -698,7 +653,6 @@ private static Task GetRemoteObjects(User user, string remoteFieldName, private async Task GetRealmWithRealmValueSchemaAsync() { var flxConfig = await GetFLXIntegrationConfigAsync(); - flxConfig.Schema = new[] { typeof(RealmValueObject), typeof(BasicAsymmetricObject) }; flxConfig.PopulateInitialSubscriptions = (realm) => { var query = realm.All(); diff --git a/Tests/Realm.Tests/Sync/FlexibleSyncTests.cs b/Tests/Realm.Tests/Sync/FlexibleSyncTests.cs index efa2fb8603..999927d4d2 100644 --- a/Tests/Realm.Tests/Sync/FlexibleSyncTests.cs +++ b/Tests/Realm.Tests/Sync/FlexibleSyncTests.cs @@ -2211,6 +2211,8 @@ public void Results_Subscribe_FirstTimeOnly_DoesntWaitForChanges([Values("abc", writerRealm.Add(new SyncAllTypesObject { DoubleProperty = 3.5, GuidProperty = testGuid, }); }); + await WaitForUploadAsync(writerRealm); + // Resubscribe to the same query with waitForSync.FirstTime. Since // we already have this subscription, SubscribeAsync should return // immediately without waiting for the download to happen. diff --git a/Tests/Realm.Tests/Sync/SessionTests.cs b/Tests/Realm.Tests/Sync/SessionTests.cs index 48fbeb0a77..a415b73427 100644 --- a/Tests/Realm.Tests/Sync/SessionTests.cs +++ b/Tests/Realm.Tests/Sync/SessionTests.cs @@ -136,7 +136,7 @@ public void Session_ClientReset_ManualRecovery_InitiateClientReset(string appTyp Assert.That(clientEx.Message, Does.Contain("Bad client file identifier")); Assert.That(clientEx.InnerException, Is.Null); - await TryInitiateClientReset(realm, clientEx, (int)ErrorCode.BadClientFileIdentifier); + await TryInitiateClientReset(realm, clientEx, ErrorCode.ClientReset); }); } @@ -169,7 +169,7 @@ void beforeCb(Realm _) var clientEx = await errorTcs.Task.Timeout(20_000, "Expected client reset"); - await TryInitiateClientReset(realm, clientEx, (int)ClientError.AutoClientResetFailed); + await TryInitiateClientReset(realm, clientEx, ErrorCode.AutoClientResetFailed); }); } @@ -216,6 +216,8 @@ public void Session_AutomaticRecoveryFallsbackToDiscardLocal(string appType) { SyncTestHelpers.RunBaasTestAsync(async () => { + await DisableClientResetRecoveryOnServer(appType); + var automaticResetCalled = false; var discardLocalResetCalled = false; @@ -254,7 +256,6 @@ public void Session_AutomaticRecoveryFallsbackToDiscardLocal(string appType) realm.Add(new ObjectWithPartitionValue(guid)); }); - await DisableClientResetRecoveryOnServer(appType); await TriggerClientReset(realm); await tcsAfterClientReset.Task.Timeout(20_000, detail: "Expected client reset"); @@ -410,8 +411,6 @@ public void SessionIntegrationTest_ClientResetHandlers_OutOfBoundArrayInsert_Add config = GetIntegrationConfig(user); } - config.Schema = new[] { typeof(ObjectWithPartitionValue) }; - return (config, guid); } @@ -731,7 +730,7 @@ public void Session_OnSessionError() { Assert.That(sender, Is.InstanceOf()); Assert.That(e, Is.InstanceOf()); - Assert.That(e.ErrorCode, Is.EqualTo(ErrorCode.TooManySessions)); + Assert.That(e.ErrorCode, Is.EqualTo(ErrorCode.WriteNotAllowed)); Assert.That(e.Message, Is.EqualTo(errorMsg)); Assert.That(e.InnerException, Is.Null); Assert.That(sessionErrorTriggered, Is.False); @@ -741,7 +740,9 @@ public void Session_OnSessionError() using var realm = await GetRealmAsync(config, waitForSync: true); var session = GetSession(realm); - session.SimulateError(ErrorCode.TooManySessions, errorMsg); + + var protocolError = 230; // ProtocolError::write_not_allowed + session.SimulateError((ErrorCode)protocolError, errorMsg); await tcs.Task; @@ -1189,7 +1190,7 @@ public void Session_WhenDisposed_MethodsThrow() Assert.Throws(() => _ = session.Equals(session)); Assert.Throws(() => _ = session.WaitForDownloadAsync()); Assert.Throws(() => _ = session.WaitForUploadAsync()); - Assert.Throws(() => session.ReportErrorForTesting(1, SessionErrorCategory.SessionError, "test", false, ServerRequestsAction.ApplicationBug)); + Assert.Throws(() => session.ReportErrorForTesting(1, "test", false, ServerRequestsAction.ApplicationBug)); // Calling CloseHandle multiple times should be fine session.CloseHandle(); @@ -1458,14 +1459,14 @@ private static ClientResetHandlerBase GetClientResetHandler( return handler; } - private static async Task TryInitiateClientReset(Realm realm, ClientResetException ex, int expectedError) + private static async Task TryInitiateClientReset(Realm realm, ClientResetException ex, ErrorCode expectedError) { if (!realm.IsClosed) { realm.Dispose(); } - Assert.That((int)ex.ErrorCode, Is.EqualTo(expectedError)); + Assert.That(ex.ErrorCode, Is.EqualTo(expectedError)); Assert.That(File.Exists(realm.Config.DatabasePath), Is.True); var didReset = false; diff --git a/Tests/Realm.Tests/Sync/SyncTestBase.cs b/Tests/Realm.Tests/Sync/SyncTestBase.cs index 39e5b94b2c..9af7590fbd 100644 --- a/Tests/Realm.Tests/Sync/SyncTestBase.cs +++ b/Tests/Realm.Tests/Sync/SyncTestBase.cs @@ -22,6 +22,7 @@ using System.Threading.Tasks; using Baas; using MongoDB.Bson; +using Realms.Schema; using Realms.Sync; using Realms.Sync.Exceptions; using static Realms.Tests.TestHelpers; @@ -220,7 +221,36 @@ protected async Task GetRealmAsync(SyncConfigurationBase config, bool wai private static T UpdateConfig(T config) where T : SyncConfigurationBase { - config.Schema = new[] { typeof(HugeSyncObject), typeof(PrimaryKeyStringObject), typeof(ObjectIdPrimaryKeyWithValueObject), typeof(SyncCollectionsObject), typeof(IntPropertyObject), typeof(EmbeddedIntPropertyObject), typeof(SyncAllTypesObject) }; + var schema = new RealmSchema.Builder() + { + typeof(HugeSyncObject), + typeof(PrimaryKeyStringObject), + typeof(ObjectIdPrimaryKeyWithValueObject), + typeof(SyncCollectionsObject), + typeof(IntPropertyObject), + typeof(EmbeddedIntPropertyObject), + typeof(SyncAllTypesObject), + typeof(ObjectWithPartitionValue), + }; + + if (config is FlexibleSyncConfiguration) + { + // We need to add all objects ever used by sync to the flx schema due to the way breaking schema changes work + // in dev mode. When a client connects with a subset of the server schema, they'll experience client reset as the + // server removes the missing tables and re-bootstraps. + schema.Add(typeof(BasicAsymmetricObject)); + schema.Add(typeof(AsymmetricObjectWithAllTypes)); + schema.Add(typeof(AsymmetricObjectWithEmbeddedRecursiveObject)); + schema.Add(typeof(EmbeddedLevel1)); + schema.Add(typeof(EmbeddedLevel2)); + schema.Add(typeof(EmbeddedLevel3)); + schema.Add(typeof(RealmValueObject)); + schema.Add(typeof(AsymmetricObjectWithEmbeddedDictionaryObject)); + schema.Add(typeof(AsymmetricObjectWithEmbeddedListObject)); + schema.Add(typeof(PrimaryKeyInt32Object)); + } + + config.Schema = schema; config.SessionStopPolicy = SessionStopPolicy.Immediately; return config; diff --git a/Tests/SourceGenerators/SourceGeneratorAssemblyToProcess/.globalconfig b/Tests/SourceGenerators/SourceGeneratorAssemblyToProcess/.globalconfig index 7177b99637..1193c5fe0c 100644 --- a/Tests/SourceGenerators/SourceGeneratorAssemblyToProcess/.globalconfig +++ b/Tests/SourceGenerators/SourceGeneratorAssemblyToProcess/.globalconfig @@ -2,4 +2,5 @@ is_global = true # This document can be used to experiment with the source generator configurations -# realm.ignore_objects_nullability = true \ No newline at end of file +# realm.ignore_objects_nullability = true +# realm.custom_ignore_attribute = [System.Text.Json.Serialization.JsonIgnore] \ No newline at end of file diff --git a/Tools/DeployApps/BaasClient.cs b/Tools/DeployApps/BaasClient.cs index 6729bad2ed..fa3ece4eee 100644 --- a/Tools/DeployApps/BaasClient.cs +++ b/Tools/DeployApps/BaasClient.cs @@ -135,17 +135,18 @@ public class FunctionReturn private string _groupId = null!; private string? _refreshToken; - private string _shortDifferentiator + private string _shortSuffix { get { - if (Differentiator.Length < 8) + var completeSuffix = $"{Differentiator}-{_clusterName}"; + if (completeSuffix.Length < 8) { - return Differentiator; + return completeSuffix; } using var sha = SHA256.Create(); - var inputBytes = Encoding.ASCII.GetBytes(Differentiator); + var inputBytes = Encoding.ASCII.GetBytes(completeSuffix); var hashBytes = sha.ComputeHash(inputBytes); var sb = new StringBuilder(); @@ -158,7 +159,7 @@ private string _shortDifferentiator } } - private string _appSuffix => $"-{_shortDifferentiator}-{_clusterName}"; + private string _appSuffix => $"-{_shortSuffix}"; public string Differentiator { get; } @@ -535,11 +536,6 @@ private async Task GetApps() { var name = doc["name"].AsString; - if (!name.EndsWith(_appSuffix)) - { - return null; - } - var appName = name[..^_appSuffix.Length]; return new BaasApp(doc["_id"].AsString, doc["client_app_id"].AsString, appName); }) diff --git a/wrappers/README.md b/wrappers/README.md index aa03370655..34a0bd0dab 100644 --- a/wrappers/README.md +++ b/wrappers/README.md @@ -51,7 +51,7 @@ docker run --rm -it -v ${pwd}:/source cimg/android:2023.05.1-ndk /bin/bash You need Visual Studio 2017 (or later) with the `C++ Universal Windows Platform tools` and `Visual C++ tools for CMake` components as well as a version of the Windows SDK installed. -Valid Windows platforms (architectures) are `Win32`, `x64`, and `ARM`. You can specify all or a subset to save time when building. +Valid Windows platforms (architectures) are `Win32`, `x64`, `ARM` and `ARM64`. You can specify all or a subset to save time when building. * To build for regular Windows run `.\build.ps1 Windows -Configuration Debug/Release -Platforms Win32, x64` diff --git a/wrappers/realm-core b/wrappers/realm-core index cc3c496740..7083d1018f 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit cc3c4967407afdce1681cdd02a76552938f4ccb7 +Subproject commit 7083d1018fda1d968b7dc9a3f8471e843f1c85e5 diff --git a/wrappers/src/error_handling.cpp b/wrappers/src/error_handling.cpp index 2438ecb37b..7ac267441d 100644 --- a/wrappers/src/error_handling.cpp +++ b/wrappers/src/error_handling.cpp @@ -62,7 +62,7 @@ namespace realm { catch (const SyncError& e) { REALM_ASSERT_DEBUG(false); - return NativeException(e); + return NativeException(e.status.code(), e.status.reason()); } catch (const Exception& e) { return NativeException(e); diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index ee687a90ed..53dc338c9e 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -178,7 +178,7 @@ Realm::Config get_shared_realm_config(Configuration configuration, std::optional config.schema_mode = sync_configuration->schema_mode; if (sync_configuration->is_flexible_sync) { - config.sync_config = std::make_shared(*sync_configuration->user, realm::SyncConfig::FLXSyncEnabled{}); + config.sync_config = std::make_shared(*sync_configuration->user, realm::SyncConfig::FLXSyncEnabled{}); } else { std::string partition(Utf16StringAccessor(sync_configuration->partition, sync_configuration->partition_len)); @@ -202,7 +202,7 @@ Realm::Config get_shared_realm_config(Configuration configuration, std::optional } realm_sync_error marshaled_error{ - error.get_system_error().value(), + error.status.code(), to_capi(error.simple_message), to_capi(error.logURL), error.is_client_reset_requested(), diff --git a/wrappers/src/sync_session_cs.cpp b/wrappers/src/sync_session_cs.cpp index 96ecd8339b..c4f915a0c5 100644 --- a/wrappers/src/sync_session_cs.cpp +++ b/wrappers/src/sync_session_cs.cpp @@ -175,24 +175,13 @@ enum class SessionErrorCategory : uint8_t { SessionError = 1 }; -REALM_EXPORT void realm_syncsession_report_error_for_testing(const SharedSyncSession& session, int err, SessionErrorCategory error_category, const uint16_t* message_buf, size_t message_len, bool is_fatal, int server_requests_action) +REALM_EXPORT void realm_syncsession_report_error_for_testing(const SharedSyncSession& session, int err, const uint16_t* message_buf, size_t message_len, bool is_fatal, int server_requests_action) { Utf16StringAccessor message(message_buf, message_len); std::error_code error_code; - switch (error_category) { - case SessionErrorCategory::ClientError: - error_code = std::error_code(err, realm::sync::client_error_category()); - break; - case SessionErrorCategory::SessionError: - error_code = std::error_code(err, realm::sync::protocol_error_category()); - break; - default: - // in case a new category isn't handle, just don't trigger any error - return; - } - - sync::SessionErrorInfo error{ error_code, std::move(message), is_fatal }; + sync::ProtocolErrorInfo protocol_error(err, message, is_fatal); + sync::SessionErrorInfo error(protocol_error); error.server_requests_action = static_cast(server_requests_action); SyncSession::OnlyForTesting::handle_error(*session, std::move(error)); diff --git a/wrappers/src/websocket_cs.cpp b/wrappers/src/websocket_cs.cpp index 419c6e34a4..635c8b399c 100644 --- a/wrappers/src/websocket_cs.cpp +++ b/wrappers/src/websocket_cs.cpp @@ -187,7 +187,7 @@ extern "C" { } REALM_EXPORT void realm_websocket_observer_closed_handler(WebSocketObserver* observer, bool was_clean, websocket::WebSocketError error_code, realm_string_t reason) { - observer->websocket_closed_handler(was_clean, Status(error_code, from_capi(reason))); + observer->websocket_closed_handler(was_clean, error_code, capi_to_std(reason)); } }