From 573519951de0c65c6281f31d469cdc195136cd0c Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 3 May 2024 11:38:53 -0700 Subject: [PATCH 01/10] Fix a large number of warnings when building the swiftui tests --- .../SwiftUITestHostUITests.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Realm/Tests/SwiftUITestHostUITests/SwiftUITestHostUITests.swift b/Realm/Tests/SwiftUITestHostUITests/SwiftUITestHostUITests.swift index 4760a0a02e..2db9d0ac1c 100644 --- a/Realm/Tests/SwiftUITestHostUITests/SwiftUITestHostUITests.swift +++ b/Realm/Tests/SwiftUITestHostUITests/SwiftUITestHostUITests.swift @@ -22,8 +22,10 @@ import RealmSwift @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) class SwiftUITests: XCTestCase { var realm: Realm! + @MainActor let app = XCUIApplication() + @MainActor override func setUpWithError() throws { continueAfterFailure = false @@ -39,6 +41,7 @@ class SwiftUITests: XCTestCase { ] } + @MainActor override func tearDownWithError() throws { app.terminate() self.realm.invalidate() @@ -51,6 +54,7 @@ class SwiftUITests: XCTestCase { String(repeating: XCUIKeyboardKey.delete.rawValue, count: string.count) } + @MainActor private var tables: XCUIElementQuery { if #available(iOS 16.0, *) { return app.collectionViews @@ -59,6 +63,7 @@ class SwiftUITests: XCTestCase { } } + @MainActor func testSampleApp() throws { app.launch() // assert realm is empty @@ -151,17 +156,20 @@ class SwiftUITests: XCTestCase { XCTAssertEqual(realm.objects(ReminderList.self).count, 0) } + @MainActor func testNSPredicateObservedResults() throws { app.launch() try observedResultsQueryTest() } + @MainActor func testSwiftQueryObservedResults() throws { app.launchEnvironment["query_type"] = "type_safe_query" app.launch() try observedResultsQueryTest() } + @MainActor private func observedResultsQueryTest() throws { let addButton = app.buttons["addList"] (1...5).forEach { _ in @@ -185,6 +193,7 @@ class SwiftUITests: XCTestCase { XCTAssertEqual(table.cells.count, 3) } + @MainActor func testMultipleEnvironmentRealms() { app.launchEnvironment["test_type"] = "multi_realm_test" app.launch() @@ -201,6 +210,7 @@ class SwiftUITests: XCTestCase { XCTAssertEqual(app.staticTexts["test_text_view"].label, "realm_c") } + @MainActor func testUnmanagedObjectState() { app.launchEnvironment["test_type"] = "unmanaged_object_test" app.launch() @@ -218,6 +228,7 @@ class SwiftUITests: XCTestCase { XCTAssertEqual(app.textFields["name"].value as? String, "test name") } + @MainActor func testKeyPathResults() { app.launchEnvironment["test_type"] = "observed_results_key_path" app.launch() @@ -266,6 +277,7 @@ class SwiftUITests: XCTestCase { XCTAssertEqual(tables.firstMatch.cells.count, 2) } + @MainActor func testUpdateResultsWithSearchable() { app.launchEnvironment["test_type"] = "observed_results_searchable" app.launch() @@ -323,6 +335,7 @@ class SwiftUITests: XCTestCase { XCTAssertEqual(table.cells.count, 0) } + @MainActor func testObservedResultsConfiguration() { app.launchEnvironment["test_type"] = "observed_results_configuration" app.launch() @@ -347,6 +360,7 @@ class SwiftUITests: XCTestCase { XCTAssertEqual(tableB.cells.count, 5) } + @MainActor func testKeyPathObservedSectionedResults() { app.launchEnvironment["test_type"] = "observed_sectioned_results_key_path" app.launch() @@ -428,6 +442,7 @@ class SwiftUITests: XCTestCase { XCTAssertEqual(realm.objects(ReminderList.self).count, 3) } + @MainActor func testKeyPathObservedSectionedResults2() { // Tests ObservedSectionedResults ctor that uses the `sectionBlock` param. app.launchEnvironment["test_type"] = "observed_sectioned_results_sort_descriptors" @@ -512,6 +527,7 @@ class SwiftUITests: XCTestCase { XCTAssertEqual(realm.objects(ReminderList.self).count, 3) } + @MainActor func testUpdateObservedSectionedResultsWithSearchable() { app.launchEnvironment["test_type"] = "observed_sectioned_results_searchable" app.launch() @@ -580,6 +596,7 @@ class SwiftUITests: XCTestCase { } } + @MainActor func testObservedSectionedResultsConfiguration() { app.launchEnvironment["test_type"] = "observed_sectioned_results_configuration" app.launch() From 2a351f30929b4c33a3c2b017f0fdd5f6c3f74957 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 3 May 2024 11:57:42 -0700 Subject: [PATCH 02/10] Extract duplicated code for setting client reset modes --- Realm/RLMUser.mm | 2 +- RealmSwift/Sync.swift | 116 +++++++++++++----------------------------- 2 files changed, 36 insertions(+), 82 deletions(-) diff --git a/Realm/RLMUser.mm b/Realm/RLMUser.mm index e68dbcd753..1e856e2bcf 100644 --- a/Realm/RLMUser.mm +++ b/Realm/RLMUser.mm @@ -180,7 +180,7 @@ - (RLMRealmConfiguration *)flexibleSyncConfigurationWithInitialSubscriptions:(RL RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init]; syncConfig.clientResetMode = clientResetMode; syncConfig.manualClientResetHandler = manualClientResetHandler; - syncConfig.initialSubscriptions = [[RLMInitialSubscriptionsConfiguration alloc] initWithCallback:initialSubscriptions rerunOnOpen:rerunOnOpen]; + syncConfig.initialSubscriptions = [[RLMInitialSubscriptionsConfiguration alloc] initWithCallback:initialSubscriptions rerunOnOpen:rerunOnOpen]; config.syncConfiguration = syncConfig; return config; } diff --git a/RealmSwift/Sync.swift b/RealmSwift/Sync.swift index 8e1b75a2b6..b09f2e5e65 100644 --- a/RealmSwift/Sync.swift +++ b/RealmSwift/Sync.swift @@ -691,6 +691,32 @@ public struct FunctionCallable: Sendable { } } +private func setSyncFields(_ config: RLMRealmConfiguration, clientResetMode: ClientResetMode, + cancelAsyncOpenOnNonFatalErrors: Bool) { + let syncConfig = config.syncConfiguration! + syncConfig.cancelAsyncOpenOnNonFatalErrors = cancelAsyncOpenOnNonFatalErrors + switch clientResetMode { + case .manual(let block): + syncConfig.clientResetMode = .manual + syncConfig.manualClientResetHandler = block + case .discardUnsyncedChanges(let beforeBlock, let afterBlock), + .discardLocal(let beforeBlock, let afterBlock): + syncConfig.clientResetMode = .discardUnsyncedChanges + syncConfig.beforeClientReset = ObjectiveCSupport.convert(object: beforeBlock) + syncConfig.afterClientReset = ObjectiveCSupport.convert(object: afterBlock) + case .recoverUnsyncedChanges(let beforeBlock, let afterBlock): + syncConfig.clientResetMode = .recoverUnsyncedChanges + syncConfig.beforeClientReset = ObjectiveCSupport.convert(object: beforeBlock) + syncConfig.afterClientReset = ObjectiveCSupport.convert(object: afterBlock) + case .recoverOrDiscardUnsyncedChanges(let beforeBlock, let afterBlock): + syncConfig.clientResetMode = .recoverOrDiscardUnsyncedChanges + syncConfig.beforeClientReset = ObjectiveCSupport.convert(object: beforeBlock) + syncConfig.afterClientReset = ObjectiveCSupport.convert(object: afterBlock) + } + + config.syncConfiguration = syncConfig +} + public extension User { /** Create a sync configuration instance. @@ -727,31 +753,8 @@ public extension User { func configuration(partitionValue: AnyBSON, clientResetMode: ClientResetMode = .recoverUnsyncedChanges(beforeReset: nil, afterReset: nil), cancelAsyncOpenOnNonFatalErrors: Bool = false) -> Realm.Configuration { - var config: RLMRealmConfiguration - switch clientResetMode { - case .manual(let manualClientReset): - config = self.__configuration(withPartitionValue: ObjectiveCSupport.convert(object: partitionValue), - clientResetMode: .manual, - manualClientResetHandler: manualClientReset) - case .discardUnsyncedChanges(let beforeClientReset, let afterClientReset), .discardLocal(let beforeClientReset, let afterClientReset): - config = self.__configuration(withPartitionValue: ObjectiveCSupport.convert(object: partitionValue), - clientResetMode: .discardUnsyncedChanges, - notifyBeforeReset: ObjectiveCSupport.convert(object: beforeClientReset), - notifyAfterReset: ObjectiveCSupport.convert(object: afterClientReset)) - case .recoverUnsyncedChanges(let beforeClientReset, let afterClientReset): - config = self.__configuration(withPartitionValue: ObjectiveCSupport.convert(object: partitionValue), - clientResetMode: .recoverUnsyncedChanges, - notifyBeforeReset: ObjectiveCSupport.convert(object: beforeClientReset), - notifyAfterReset: ObjectiveCSupport.convert(object: afterClientReset)) - case .recoverOrDiscardUnsyncedChanges(let beforeClientReset, let afterClientReset): - config = self.__configuration(withPartitionValue: ObjectiveCSupport.convert(object: partitionValue), - clientResetMode: .recoverOrDiscardUnsyncedChanges, - notifyBeforeReset: ObjectiveCSupport.convert(object: beforeClientReset), - notifyAfterReset: ObjectiveCSupport.convert(object: afterClientReset)) - } - let syncConfig = config.syncConfiguration! - syncConfig.cancelAsyncOpenOnNonFatalErrors = cancelAsyncOpenOnNonFatalErrors - config.syncConfiguration = syncConfig + let config = __configuration(withPartitionValue: ObjectiveCSupport.convert(object: partitionValue)) + setSyncFields(config, clientResetMode: clientResetMode, cancelAsyncOpenOnNonFatalErrors: cancelAsyncOpenOnNonFatalErrors) return ObjectiveCSupport.convert(object: config) } @@ -966,13 +969,9 @@ public extension SyncSession { block: @Sendable @escaping (Error?) -> Void) { switch direction { case .upload: - __waitForUploadCompletion(on: queue) { error in - block(error) - } + __waitForUploadCompletion(on: queue, callback: block) case .download: - __waitForDownloadCompletion(on: queue) { error in - block(error) - } + __waitForDownloadCompletion(on: queue, callback: block) } } @@ -1163,27 +1162,8 @@ extension User { */ public func flexibleSyncConfiguration(clientResetMode: ClientResetMode = .recoverUnsyncedChanges(), cancelAsyncOpenOnNonFatalErrors: Bool = false) -> Realm.Configuration { - var config: RLMRealmConfiguration - switch clientResetMode { - case .manual(let block): - config = __flexibleSyncConfiguration(with: .manual, manualClientResetHandler: block) - case .discardUnsyncedChanges(let beforeBlock, let afterBlock), - .discardLocal(let beforeBlock, let afterBlock): - config = __flexibleSyncConfiguration(with: .discardUnsyncedChanges, - notifyBeforeReset: ObjectiveCSupport.convert(object: beforeBlock), - notifyAfterReset: ObjectiveCSupport.convert(object: afterBlock)) - case .recoverUnsyncedChanges(let beforeBlock, let afterBlock): - config = __flexibleSyncConfiguration(with: .recoverUnsyncedChanges, - notifyBeforeReset: ObjectiveCSupport.convert(object: beforeBlock), - notifyAfterReset: ObjectiveCSupport.convert(object: afterBlock)) - case .recoverOrDiscardUnsyncedChanges(let beforeBlock, let afterBlock): - config = __flexibleSyncConfiguration(with: .recoverOrDiscardUnsyncedChanges, - notifyBeforeReset: ObjectiveCSupport.convert(object: beforeBlock), - notifyAfterReset: ObjectiveCSupport.convert(object: afterBlock)) - } - let syncConfig = config.syncConfiguration! - syncConfig.cancelAsyncOpenOnNonFatalErrors = cancelAsyncOpenOnNonFatalErrors - config.syncConfiguration = syncConfig + let config = __flexibleSyncConfiguration() + setSyncFields(config, clientResetMode: clientResetMode, cancelAsyncOpenOnNonFatalErrors: cancelAsyncOpenOnNonFatalErrors) return ObjectiveCSupport.convert(object: config) } @@ -1224,35 +1204,9 @@ extension User { cancelAsyncOpenOnNonFatalErrors: Bool = false, initialSubscriptions: @escaping @Sendable (SyncSubscriptionSet) -> Void, rerunOnOpen: Bool = false) -> Realm.Configuration { - var config: RLMRealmConfiguration - switch clientResetMode { - case .manual(let block): - config = self.__flexibleSyncConfiguration(initialSubscriptions: ObjectiveCSupport.convert(block: initialSubscriptions), - rerunOnOpen: rerunOnOpen, - clientResetMode: .manual, - manualClientResetHandler: block) - case .discardUnsyncedChanges(let beforeBlock, let afterBlock), .discardLocal(let beforeBlock, let afterBlock): - config = self.__flexibleSyncConfiguration(initialSubscriptions: ObjectiveCSupport.convert(block: initialSubscriptions), - rerunOnOpen: rerunOnOpen, - clientResetMode: .discardUnsyncedChanges, - notifyBeforeReset: ObjectiveCSupport.convert(object: beforeBlock), - notifyAfterReset: ObjectiveCSupport.convert(object: afterBlock)) - case .recoverUnsyncedChanges(let beforeBlock, let afterBlock): - config = self.__flexibleSyncConfiguration(initialSubscriptions: ObjectiveCSupport.convert(block: initialSubscriptions), - rerunOnOpen: rerunOnOpen, - clientResetMode: .recoverUnsyncedChanges, - notifyBeforeReset: ObjectiveCSupport.convert(object: beforeBlock), - notifyAfterReset: ObjectiveCSupport.convert(object: afterBlock)) - case .recoverOrDiscardUnsyncedChanges(let beforeBlock, let afterBlock): - config = self.__flexibleSyncConfiguration(initialSubscriptions: ObjectiveCSupport.convert(block: initialSubscriptions), - rerunOnOpen: rerunOnOpen, - clientResetMode: .recoverOrDiscardUnsyncedChanges, - notifyBeforeReset: ObjectiveCSupport.convert(object: beforeBlock), - notifyAfterReset: ObjectiveCSupport.convert(object: afterBlock)) - } - let syncConfig = config.syncConfiguration! - syncConfig.cancelAsyncOpenOnNonFatalErrors = cancelAsyncOpenOnNonFatalErrors - config.syncConfiguration = syncConfig + let config = __flexibleSyncConfiguration(initialSubscriptions: ObjectiveCSupport.convert(block: initialSubscriptions), + rerunOnOpen: rerunOnOpen) + setSyncFields(config, clientResetMode: clientResetMode, cancelAsyncOpenOnNonFatalErrors: cancelAsyncOpenOnNonFatalErrors) return ObjectiveCSupport.convert(object: config) } } From 2e95f9ff71bc17beab07f8ceb8a0588f1ffba93d Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 3 May 2024 12:15:29 -0700 Subject: [PATCH 03/10] Fix `-[RLMApp baseURL]` always being nil --- CHANGELOG.md | 1 + Realm/ObjectServerTests/AsyncSyncTests.swift | 17 ++++---- .../ObjectServerTests/RLMObjectServerTests.mm | 31 ++++++++++----- .../SwiftObjectServerTests.swift | 39 ++++--------------- Realm/RLMApp.mm | 2 +- RealmSwift/App.swift | 22 ++++++++--- 6 files changed, 56 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c09702e157..b2e88de67c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ x.y.z Release notes (yyyy-MM-dd) * ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?) * `@AutoOpen` and `@AsyncOpen` failed to use the `initialSubscriptions` set in the configuration passed to them ([PR #8572](https://github.com/realm/realm-swift/pull/8572), since v10.50.0). +* `App.baseURL` was always `nil` ([PR #8573](https://github.com/realm/realm-swift/pull/8573), since it was introduced in v10.50.0). diff --git a/Realm/ObjectServerTests/AsyncSyncTests.swift b/Realm/ObjectServerTests/AsyncSyncTests.swift index ad9e813fac..c9ce6cf552 100644 --- a/Realm/ObjectServerTests/AsyncSyncTests.swift +++ b/Realm/ObjectServerTests/AsyncSyncTests.swift @@ -80,17 +80,18 @@ class AsyncAwaitSyncTests: SwiftSyncTestCase { func testUpdateBaseUrl() async throws { let app = App(id: appId) - XCTAssertNotNil(app.baseURL) - XCTAssertEqual(app.baseURL, "http://localhost:9090") + XCTAssertEqual(app.baseURL, "https://services.cloud.mongodb.com") - try await app.updateBaseUrl(to: "http://localhost:8080") - XCTAssertEqual(app.baseURL, "http://localhost:8080") + try await app.updateBaseUrl(to: "http://localhost:9090") + XCTAssertEqual(app.baseURL, "http://localhost:9090") - try await app.updateBaseUrl(to: "http://localhost:7070/") - XCTAssertEqual(app.baseURL, "http://localhost:7070") + try await app.updateBaseUrl(to: "http://127.0.0.1:9090") + XCTAssertEqual(app.baseURL, "http://127.0.0.1:9090") - try await app.updateBaseUrl(to: nil) - XCTAssertEqual(app.baseURL, "https://services.cloud.mongodb.com") + // Fails as this appId doesn't exist in prod + await assertThrowsError(try await app.updateBaseUrl(to: nil)) { (error: AppError) in + XCTAssertEqual(error.code, .unknown) + } } @MainActor func testAsyncOpenStandalone() async throws { diff --git a/Realm/ObjectServerTests/RLMObjectServerTests.mm b/Realm/ObjectServerTests/RLMObjectServerTests.mm index 9eea48cb6f..ec5d69d80a 100644 --- a/Realm/ObjectServerTests/RLMObjectServerTests.mm +++ b/Realm/ObjectServerTests/RLMObjectServerTests.mm @@ -83,28 +83,39 @@ - (NSArray *)defaultObjectTypes { - (void)testUpdateBaseUrl { RLMApp *app = self.app; - XCTAssertEqual(app.baseURL, @"http://localhost:9090"); + XCTAssertEqualObjects(app.baseURL, @"http://localhost:9090"); XCTestExpectation *expectation = [self expectationWithDescription:@"should update base url"]; - [app updateBaseURL:@"http://localhost:8080" completion:^(NSError *error) { + [app updateBaseURL:@"http://127.0.0.1:9090" completion:^(NSError *error) { XCTAssertNil(error); [expectation fulfill]; }]; - XCTAssertEqual(app.baseURL, @"http://localhost:8080"); + [self waitForExpectations:@[expectation]]; + XCTAssertEqualObjects(app.baseURL, @"http://127.0.0.1:9090"); - XCTestExpectation *expectation1 = [self expectationWithDescription:@"should update base url"]; + TimeoutProxyServer *proxy = [[TimeoutProxyServer alloc] initWithPort:7070 targetPort:9090]; + proxy.delay = 0; + [proxy startAndReturnError:nil]; + + expectation = [self expectationWithDescription:@"should update base url"]; [app updateBaseURL:@"http://localhost:7070/" completion:^(NSError *error) { XCTAssertNil(error); - [expectation1 fulfill]; + [expectation fulfill]; }]; - XCTAssertEqual(app.baseURL, @"http://localhost:7070"); + [self waitForExpectations:@[expectation]]; + XCTAssertEqualObjects(app.baseURL, @"http://localhost:7070"); + [proxy stop]; - XCTestExpectation *expectation2 = [self expectationWithDescription:@"should update base url to default value"]; + expectation = [self expectationWithDescription:@"should fail to update base url to default value"]; [app updateBaseURL:nil completion:^(NSError *error) { - XCTAssertNil(error); - [expectation2 fulfill]; + // This fails because our local app doesn't exist in the prod env + XCTAssertNotNil(error); + XCTAssertEqual(error.code, RLMAppErrorUnknown); + [expectation fulfill]; }]; - XCTAssertEqual(app.baseURL, @"https://services.cloud.mongodb.com"); + [self waitForExpectations:@[expectation]]; + // baseURL update failed, so it's left unchanged + XCTAssertEqualObjects(app.baseURL, @"http://localhost:7070"); } - (void)testAnonymousAuthentication { diff --git a/Realm/ObjectServerTests/SwiftObjectServerTests.swift b/Realm/ObjectServerTests/SwiftObjectServerTests.swift index dd40675d51..f4c9302fe5 100644 --- a/Realm/ObjectServerTests/SwiftObjectServerTests.swift +++ b/Realm/ObjectServerTests/SwiftObjectServerTests.swift @@ -64,41 +64,16 @@ class SwiftObjectServerTests: SwiftSyncTestCase { func testUpdateBaseUrl() { let app = App(id: appId) - XCTAssertNotNil(app.baseURL) + XCTAssertEqual(app.baseURL, "https://services.cloud.mongodb.com") + + app.updateBaseUrl(to: "http://localhost:9090").await(self) XCTAssertEqual(app.baseURL, "http://localhost:9090") - let ex = expectation(description: "update base url") - app.updateBaseUrl(to: "http://localhost:8080", { result in - switch result { - case .success: - ex.fulfill() - case .failure: - XCTFail("Should not return an error") - } - }) - XCTAssertEqual(app.baseURL, "http://localhost:8080") + app.updateBaseUrl(to: "http://127.0.0.1:9090").await(self) + XCTAssertEqual(app.baseURL, "http://127.0.0.1:9090") - let ex1 = expectation(description: "update base url") - app.updateBaseUrl(to: "http://localhost:7070/", { result in - switch result { - case .success: - ex1.fulfill() - case .failure: - XCTFail("Should not return an error") - } - }) - XCTAssertEqual(app.baseURL, "http://localhost:7070") - - let ex2 = expectation(description: "update base url") - app.updateBaseUrl(to: nil, { result in - switch result { - case .success: - ex2.fulfill() - case .failure: - XCTFail("Should not return an error") - } - }) - XCTAssertEqual(app.baseURL, "https://services.cloud.mongodb.com") + app.updateBaseUrl(to: nil).awaitFailure(self) + XCTAssertEqual(app.baseURL, "http://127.0.0.1:9090") } func testBasicSwiftSync() throws { diff --git a/Realm/RLMApp.mm b/Realm/RLMApp.mm index 5778e3ced3..b0f9afd05e 100644 --- a/Realm/RLMApp.mm +++ b/Realm/RLMApp.mm @@ -414,7 +414,7 @@ - (RLMEmailPasswordAuth *)emailPasswordAuth { return [[RLMEmailPasswordAuth alloc] initWithApp:_app]; } -- (NSString *)baseUrl { +- (NSString *)baseURL { return getOptionalString(_app->get_base_url()) ?: RLMStringViewToNSString(_app->default_base_url()); } diff --git a/RealmSwift/App.swift b/RealmSwift/App.swift index c79afad2b3..8acdca293b 100644 --- a/RealmSwift/App.swift +++ b/RealmSwift/App.swift @@ -224,14 +224,27 @@ public extension App { Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). - parameter url: The new base url to connect to. Setting `nil` will reset the base url to the default url. - parameter completion: A callback invoked after completion. - - note: Updating the base URL would trigger a client reset. + - note: Updating the base URL will trigger a client reset. */ - @preconcurrency @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @_spi(RealmSwiftExperimental) func updateBaseUrl(to url: String?, _ completion: @Sendable @escaping (Error?) -> Void) { self.__updateBaseURL(url, completion: completion) } + /** + Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). + - parameter url: The new base url to connect to. Setting `nil` will reset the base url to the default url. + - parameter completion: A callback invoked after completion. + - note: Updating the base URL will trigger a client reset. + - returns A publisher that eventually return `Result.success` or `Error`. + */ + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + @_spi(RealmSwiftExperimental) func updateBaseUrl(to url: String?) -> Future { + promisify { + self.__updateBaseURL(url, completion: $0) + } + } + /** Login to a user for the Realm app. @@ -253,9 +266,8 @@ public extension App { Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). - parameter url: The new base url to connect to. Setting `nil` will reset the base url to the default url. - parameter completion: A callback invoked after completion. Will return `Result.success` or `Result.failure(Error)`. - - note: Updating the base URL would trigger a client reset. + - note: Updating the base URL will trigger a client reset. */ - @preconcurrency @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @_spi(RealmSwiftExperimental) func updateBaseUrl(to url: String?, _ completion: @Sendable @escaping (Result) -> Void) { self.__updateBaseURL(url, completion: { error in @@ -289,7 +301,7 @@ public extension App { /** Updates the base url used by Atlas device sync, in case the need to roam between servers (cloud and/or edge server). - parameter url: The new base url to connect to. Setting `nil` will reset the base url to the default url. - - note: Updating the base URL would trigger a client reset. + - note: Updating the base URL will trigger a client reset. */ @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @_spi(RealmSwiftExperimental) func updateBaseUrl(to url: String?) async throws { From 75bc1ff11da55fa7d40da87988581264f67cd896 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 3 May 2024 13:04:32 -0700 Subject: [PATCH 04/10] Check for errors when invoking subcommands in setup_baas.rb --- Realm/ObjectServerTests/setup_baas.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Realm/ObjectServerTests/setup_baas.rb b/Realm/ObjectServerTests/setup_baas.rb index 95fb2ad7cd..69865ab6f0 100644 --- a/Realm/ObjectServerTests/setup_baas.rb +++ b/Realm/ObjectServerTests/setup_baas.rb @@ -28,6 +28,15 @@ STITCH_SUPPORT_URL="https://static.realm.io/downloads/swift/stitch-support.tar.xz" MONGO_DIR="#{BUILD_DIR}/mongodb-macos-x86_64-#{MONGODB_VERSION}" +# exit immediately if any subcommand fails +class Object + def `(command) + ret = super + exit 1 unless $?.success? + ret + end +end + def setup_mongod if !File.exist?("#{BIN_DIR}/mongo") `cd '#{BUILD_DIR}' && curl --silent '#{MONGODB_URL}' | tar xz` @@ -38,7 +47,7 @@ def setup_mongod def run_mongod puts "starting mongod..." - puts `mkdir '#{BUILD_DIR}'/db_files` + puts `mkdir -p '#{BUILD_DIR}'/db_files` puts `#{MONGOD_EXE} --quiet \ --dbpath '#{BUILD_DIR}'/db_files \ --port 26000 \ @@ -132,13 +141,13 @@ def setup_stitch puts `chmod +x '#{assisted_agg_filepath}'` end - if `which node`.empty? && !Dir.exist?("#{BUILD_DIR}/node-v#{NODE_VERSION}-darwin-x64") + if `which node || true`.empty? && !Dir.exist?("#{BUILD_DIR}/node-v#{NODE_VERSION}-darwin-x64") puts "downloading node 🚀" puts `cd '#{BUILD_DIR}' && curl -O "https://nodejs.org/dist/v#{NODE_VERSION}/node-v#{NODE_VERSION}-darwin-x64.tar.gz" && tar xzf node-v#{NODE_VERSION}-darwin-x64.tar.gz` exports << "export PATH=\"#{BUILD_DIR}/node-v#{NODE_VERSION}-darwin-x64/bin/:$PATH\"" end - if `which yarn`.empty? + if `which yarn || true`.empty? `rm -rf "$HOME/.yarn"` `export PATH=\"#{BUILD_DIR}/node-v#{NODE_VERSION}-darwin-x64/bin/:$PATH\" && curl -o- -L https://yarnpkg.com/install.sh | bash` exports << "export PATH=\"$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH\"" From f9d479c015a664f2ad1e238ce48294d063414307 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 3 May 2024 14:33:53 -0700 Subject: [PATCH 05/10] Download BaaS in the post-clone script for sync tests --- ci_scripts/ci_post_clone.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci_scripts/ci_post_clone.sh b/ci_scripts/ci_post_clone.sh index 7fa373d0b1..4527250e7e 100755 --- a/ci_scripts/ci_post_clone.sh +++ b/ci_scripts/ci_post_clone.sh @@ -16,10 +16,10 @@ install_dependencies() { brew install swiftlint elif [[ "$CI_WORKFLOW" == "cocoapods"* ]]; then install_ruby - elif [[ "$CI_WORKFLOW" == "objectserver"* ]] || [[ "$target" == "swiftpm"* ]]; then + elif [[ "$CI_WORKFLOW" == "sync"* ]] || [[ "$CI_WORKFLOW" == "swiftpm"* ]]; then sh build.sh setup-baas sh build.sh download-core - elif [[ "$$CI_WORKFLOW" = *"spm"* ]] || [[ "$target" = "xcframework"* ]]; then + elif [[ "$CI_WORKFLOW" = *"spm"* ]] || [[ "$CI_WORKFLOW" = "xcframework"* ]]; then install_ruby elif [[ "$CI_WORKFLOW" == *"carthage"* ]]; then brew install carthage From daf82e6d91db688047b118128ea54252134d7955 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 3 May 2024 18:05:35 -0700 Subject: [PATCH 06/10] Don't run the SwiftUIServerTests base class tests --- Realm/ObjectServerTests/SwiftUIServerTests.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Realm/ObjectServerTests/SwiftUIServerTests.swift b/Realm/ObjectServerTests/SwiftUIServerTests.swift index c60b7eb74c..76e1fa1235 100644 --- a/Realm/ObjectServerTests/SwiftUIServerTests.swift +++ b/Realm/ObjectServerTests/SwiftUIServerTests.swift @@ -47,6 +47,14 @@ class SwiftUIServerTests: SwiftSyncTestCase { super.tearDown() } + override class var defaultTestSuite: XCTestSuite { + // Don't run tests for the base class + if self === SwiftUIServerTests.self { + return XCTestSuite(name: "SwiftUIServerTests") + } + return super.defaultTestSuite + } + var cancellables: Set = [] func awaitOpen(_ wrapper: some AsyncOpenStateWrapper, From ff3ff87c8e8521b6b59e42f1cf27fc44c01b9d51 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 3 May 2024 18:36:52 -0700 Subject: [PATCH 07/10] Fix the progress notifier tests --- .../SwiftFlexibleSyncServerTests.swift | 44 +++++++++---------- .../SwiftObjectServerTests.swift | 11 +++-- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift b/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift index 1ea0313f94..1b5d04e422 100644 --- a/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift +++ b/Realm/ObjectServerTests/SwiftFlexibleSyncServerTests.swift @@ -950,7 +950,9 @@ class SwiftFlexibleSyncTests: SwiftSyncTestCase { let user = createUser() var config = user.flexibleSyncConfiguration(initialSubscriptions: { subscriptions in - subscriptions.append(QuerySubscription()) + subscriptions.append(QuerySubscription { + $0.partition == self.name + }) }) config.objectTypes = objectTypes var downloadRealm: Realm? @@ -982,13 +984,12 @@ class SwiftFlexibleSyncTests: SwiftSyncTestCase { XCTAssertTrue(p1.isTransferComplete) } - func testNonStreamingDownloadNotifier() throws { + func testNonStreamingDownloadNotifier() async throws { try populateRealm() let realm = try openRealm(wait: false) let session = try XCTUnwrap(realm.syncSession) - let ex = expectation(description: "first download") let callCount = Locked(0) let progress = Locked(nil) @@ -1005,14 +1006,11 @@ class SwiftFlexibleSyncTests: SwiftSyncTestCase { XCTAssertNotNil(token) let subscriptions = realm.subscriptions - subscriptions.update({ - subscriptions.append(QuerySubscription(name: "huge_objects")) - }, onComplete: { err in - XCTAssertNil(err) - ex.fulfill() - }) - - waitForExpectations(timeout: 60.0) + try await subscriptions.update { + subscriptions.append(QuerySubscription { + $0.partition == self.name + }) + } XCTAssertEqual(realm.objects(SwiftHugeSyncObject.self).count, SwiftSyncTestCase.bigObjectCount) @@ -1062,12 +1060,12 @@ class SwiftFlexibleSyncTests: SwiftSyncTestCase { let subscriptions = realm.subscriptions subscriptions.update({ - subscriptions.append(QuerySubscription(name: "huge_objects")) + subscriptions.append(QuerySubscription { + $0.partition == self.name + }) }, onComplete: { err in - DispatchQueue.main.async { - XCTAssertNil(err) - ex.fulfill() - } + XCTAssertNil(err) + ex.fulfill() }) waitForExpectations(timeout: 60.0) @@ -1099,7 +1097,9 @@ class SwiftFlexibleSyncTests: SwiftSyncTestCase { let realm = try openRealm(wait: false) let subscriptions = realm.subscriptions subscriptions.update { - subscriptions.append(QuerySubscription(name: "huge_objects")) + subscriptions.append(QuerySubscription { + $0.partition == self.name + }) } let session = try XCTUnwrap(realm.syncSession) @@ -1107,13 +1107,11 @@ class SwiftFlexibleSyncTests: SwiftSyncTestCase { let callCount = Locked(0) let token = session.addProgressNotification(for: .upload, mode: .reportIndefinitely) { p in - if let progress = progress.value { - if progress.progressEstimate < 1 { - XCTAssertGreaterThanOrEqual(p.progressEstimate, progress.progressEstimate) - } + if let progress = progress.value, progress.progressEstimate < 1 { + XCTAssertGreaterThanOrEqual(p.progressEstimate, progress.progressEstimate) } progress.value = p - callCount.withLock({ $0 += 1 }) + callCount.withLock { $0 += 1 } } XCTAssertNotNil(token) waitForUploads(for: realm) @@ -1123,7 +1121,7 @@ class SwiftFlexibleSyncTests: SwiftSyncTestCase { let currentCount = callCount.value try realm.write { for _ in 0..(nil) let token = session.addProgressNotification(for: .upload, mode: .reportIndefinitely) { p in - DispatchQueue.main.async { @MainActor in - if let progress = progress { + progress.withLock { progress in + if let progress { XCTAssertGreaterThanOrEqual(p.progressEstimate, progress.progressEstimate) } progress = p } } XCTAssertNotNil(token) - waitForUploads(for: realm) for _ in 0..<5 { - progress = nil + progress.value = nil try realm.write { for _ in 0.. Date: Fri, 3 May 2024 12:06:09 -0700 Subject: [PATCH 08/10] Bump baas version --- dependencies.list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.list b/dependencies.list index be80716eea..c2703a4ffd 100755 --- a/dependencies.list +++ b/dependencies.list @@ -1,3 +1,3 @@ VERSION=10.50.0 REALM_CORE_VERSION=v14.6.2 -STITCH_VERSION=b43a1ddbb4729caa14a780f1e452d9ba6957b7f0 +STITCH_VERSION=b4f0184c963eed8f6cc5e857fac147bef10966d7 From e9d3c0bf20cb4a91feaddc85683ba337d71a3aa5 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 3 May 2024 21:19:25 -0700 Subject: [PATCH 09/10] Update the client reset tests to use the new endpoint --- Realm/ObjectServerTests/RealmServer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Realm/ObjectServerTests/RealmServer.swift b/Realm/ObjectServerTests/RealmServer.swift index 512f4e785d..31543f125a 100644 --- a/Realm/ObjectServerTests/RealmServer.swift +++ b/Realm/ObjectServerTests/RealmServer.swift @@ -1220,7 +1220,7 @@ public class RealmServer: NSObject { let appServerId = try retrieveAppServerId(appId) let ident = RLMGetClientFileIdent(ObjectiveCSupport.convert(object: realm)) XCTAssertNotEqual(ident, 0) - _ = try session.privateApps[appServerId].sync.forceReset.put(["file_ident": ident]).get() + _ = try session.apps[appServerId].sync.forceReset.put(["file_ident": ident]).get() } public func waitForSync(appId: String) throws { From 2ab87f475eeba10c722d99aa866979716eed5adf Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Sat, 4 May 2024 08:27:32 -0700 Subject: [PATCH 10/10] Temporarily disable installing BaaS for swiftpm tests --- ci_scripts/ci_post_clone.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci_scripts/ci_post_clone.sh b/ci_scripts/ci_post_clone.sh index 4527250e7e..8f1ce0618f 100755 --- a/ci_scripts/ci_post_clone.sh +++ b/ci_scripts/ci_post_clone.sh @@ -16,7 +16,8 @@ install_dependencies() { brew install swiftlint elif [[ "$CI_WORKFLOW" == "cocoapods"* ]]; then install_ruby - elif [[ "$CI_WORKFLOW" == "sync"* ]] || [[ "$CI_WORKFLOW" == "swiftpm"* ]]; then + elif [[ "$CI_WORKFLOW" == "sync"* ]]; then + # elif [[ "$CI_WORKFLOW" == "sync"* ]] || [[ "$CI_WORKFLOW" == "swiftpm"* ]]; then sh build.sh setup-baas sh build.sh download-core elif [[ "$CI_WORKFLOW" = *"spm"* ]] || [[ "$CI_WORKFLOW" = "xcframework"* ]]; then