From 87308e5a76e091d67fc2afd2be50a05076bb310b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 9 Jun 2020 11:18:53 +0200 Subject: [PATCH 01/15] Move SyncedRealmIntegrationTests to Kotlin source set --- .../{java => kotlin}/io/realm/SyncedRealmIntegrationTests.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename realm/realm-library/src/syncIntegrationTest/{java => kotlin}/io/realm/SyncedRealmIntegrationTests.java (100%) diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java similarity index 100% rename from realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java rename to realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java From 592ca9e3bf6c4592c3b1e43c4ed2630c0ebe1b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 9 Jun 2020 11:20:28 +0200 Subject: [PATCH 02/15] Automatic conversion of SyncedRealmIntegrationTests to Kotlin --- .../io/realm/SyncedRealmIntegrationTests.java | 521 ------------------ .../io/realm/SyncedRealmIntegrationTests.kt | 466 ++++++++++++++++ 2 files changed, 466 insertions(+), 521 deletions(-) delete mode 100644 realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java create mode 100644 realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java deleted file mode 100644 index a6f04205e5..0000000000 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.java +++ /dev/null @@ -1,521 +0,0 @@ -/* - * Copyright 2017 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.realm; - -import android.os.SystemClock; -import androidx.test.annotation.UiThreadTest; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.realm.entities.AllTypes; -import io.realm.entities.StringOnly; -import io.realm.exceptions.DownloadingRealmInterruptedException; -import io.realm.exceptions.RealmMigrationNeededException; -import io.realm.internal.OsRealmConfig; -import io.realm.log.LogLevel; -import io.realm.log.RealmLog; -import io.realm.log.RealmLogger; -import io.realm.objectserver.utils.Constants; -import io.realm.rule.RunTestInLooperThread; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - - -/** - * Catch all class for tests that not naturally fit anywhere else. - */ -@RunWith(AndroidJUnit4.class) -public class SyncedRealmIntegrationTests extends StandardIntegrationTest { - - @Test - @RunTestInLooperThread - public void loginLogoutResumeSyncing() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .schema(StringOnly.class) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - - Realm realm = Realm.getInstance(config); - realm.beginTransaction(); - realm.createObject(StringOnly.class).setChars("Foo"); - realm.commitTransaction(); - SyncManager.getSession(config).uploadAllLocalChanges(); - user.logOut(); - realm.close(); - try { - assertTrue(Realm.deleteRealm(config)); - } catch (IllegalStateException e) { - // FIXME: We don't have a way to ensure that the Realm instance on client thread has been - // closed for now https://github.com/realm/realm-java/issues/5416 - if (e.getMessage().contains("It's not allowed to delete the file")) { - // retry after 1 second - SystemClock.sleep(1000); - assertTrue(Realm.deleteRealm(config)); - } - } - - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); - SyncConfiguration config2 = user.createConfiguration(Constants.USER_REALM) - .schema(StringOnly.class) - .build(); - - Realm realm2 = Realm.getInstance(config2); - SyncManager.getSession(config2).downloadAllServerChanges(); - realm2.refresh(); - assertEquals(1, realm2.where(StringOnly.class).count()); - realm2.close(); - looperThread.testComplete(); - } - - @Test - @UiThreadTest - public void waitForInitialRemoteData_mainThreadThrows() { - final SyncUser user = SyncTestUtils.createTestUser(Constants.AUTH_URL); - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .waitForInitialRemoteData() - .build(); - - Realm realm = null; - try { - realm = Realm.getInstance(config); - fail(); - } catch (IllegalStateException ignore) { - } finally { - if (realm != null) { - realm.close(); - } - } - } - - @Test - public void waitForInitialRemoteData() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - Realm realm = Realm.getInstance(configOld); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo" + i); - } - } - }); - SyncManager.getSession(configOld).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with - // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .name("newRealm") - .schema(StringOnly.class) - .waitForInitialRemoteData() - .build(); - - realm = Realm.getInstance(config); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo 1" + i); - } - } - }); - try { - assertEquals(20, realm.where(StringOnly.class).count()); - } finally { - realm.close(); - } - } - - // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that - // We cannot do much better since we cannot control the order of events internally in Realm which would be - // needed to correctly test all error paths. - @Test - @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + - " https://github.com/realm/realm-java/issues/5416") - public void waitForInitialData_resilientInCaseOfRetries() throws InterruptedException { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .waitForInitialRemoteData() - .build(); - - for (int i = 0; i < 10; i++) { - Thread t = new Thread(new Runnable() { - @Override - public void run() { - Realm realm = null; - try { - // This will cause the download latch called later to immediately throw an InterruptedException. - Thread.currentThread().interrupt(); - realm = Realm.getInstance(config); - } catch (DownloadingRealmInterruptedException ignored) { - assertFalse(new File(config.getPath()).exists()); - } finally { - if (realm != null) { - realm.close(); - Realm.deleteRealm(config); - } - } - } - }); - t.start(); - t.join(); - } - } - - // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that - // We cannot do much better since we cannot control the order of events internally in Realm which would be - // needed to correctly test all error paths. - @Test - @RunTestInLooperThread - @Ignore("See https://github.com/realm/realm-java/issues/5373") - public void waitForInitialData_resilientInCaseOfRetriesAsync() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .directory(configurationFactory.getRoot()) - .waitForInitialRemoteData() - .build(); - Random randomizer = new Random(); - - for (int i = 0; i < 10; i++) { - RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { - @Override - public void onSuccess(Realm realm) { - fail(); - } - - @Override - public void onError(Throwable exception) { - fail(exception.toString()); - } - }); - SystemClock.sleep(randomizer.nextInt(5)); - task.cancel(); - } - looperThread.testComplete(); - } - - @Test - public void waitForInitialRemoteData_readOnlyTrue() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .build(); - Realm realm = Realm.getInstance(configOld); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo" + i); - } - } - }); - SyncManager.getSession(configOld).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - - // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should - // download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL); - final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .name("newRealm") - .waitForInitialRemoteData() - .readOnly() - .schema(StringOnly.class) - .build(); - assertFalse(configNew.realmExists()); - - realm = Realm.getInstance(configNew); - assertEquals(10, realm.where(StringOnly.class).count()); - realm.close(); - user.logOut(); - } - - @Test - public void waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration configNew = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .waitForInitialRemoteData() - .readOnly() - .schema(StringOnly.class) - .build(); - assertFalse(configNew.realmExists()); - - Realm realm = null; - try { - // This will fail, because the server Realm is completely empty and the Client is not allowed to write the - // schema. - realm = Realm.getInstance(configNew); - fail(); - } catch (RealmMigrationNeededException ignore) { - } finally { - if (realm != null) { - realm.close(); - } - user.logOut(); - } - } - - @Test - public void waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .waitForInitialRemoteData() // Not readonly so Client should be allowed to write schema - .schema(StringOnly.class) // This schema should be written when opening the empty Realm. - .schemaVersion(2) - .build(); - assertFalse(config.realmExists()); - - Realm realm = Realm.getInstance(config); - try { - assertEquals(0, realm.where(StringOnly.class).count()); - } finally { - realm.close(); - user.logOut(); - } - } - - @Ignore("FIXME: Re-enable this once we can test againt a proper Stitch server") - @Test - public void defaultRealm() throws InterruptedException { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncConfiguration config = user.getDefaultConfiguration(); - Realm realm = Realm.getInstance(config); - SyncManager.getSession(config).downloadAllServerChanges(); - realm.refresh(); - - try { - assertTrue(realm.isEmpty()); - } finally { - realm.close(); - user.logOut(); - } - } - - // Check that custom headers and auth header renames are correctly used for HTTP requests - // performed from Java. - @Test - @RunTestInLooperThread - public void javaRequestCustomHeaders() { - SyncManager.addCustomRequestHeader("Foo", "bar"); - SyncManager.setAuthorizationHeaderName("RealmAuth"); - runJavaRequestCustomHeadersTest(); - } - - // Check that custom headers and auth header renames are correctly used for HTTP requests - // performed from Java. - @Test - @RunTestInLooperThread - public void javaRequestCustomHeaders_specificHost() { - SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST); - SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST); - runJavaRequestCustomHeadersTest(); - } - - private void runJavaRequestCustomHeadersTest() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); - - AtomicBoolean headerSet = new AtomicBoolean(false); - RealmLog.setLevel(LogLevel.ALL); - RealmLogger logger = (level, tag, throwable, message) -> { - if (level == LogLevel.TRACE - && message.contains("Foo: bar") - && message.contains("RealmAuth: ")) { - headerSet.set(true); - } - }; - looperThread.runAfterTest(() -> { - RealmLog.remove(logger); - }); - RealmLog.add(logger); - - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - try { - user.changePassword("foo"); - } catch (ObjectServerError e) { - if (e.getErrorCode() != ErrorCode.INVALID_CREDENTIALS) { - throw e; - } - } - - assertTrue(headerSet.get()); - looperThread.testComplete(); - } - - // Test that auth header renaming, custom headers and url prefix are all propagated correctly - // to Sync. There really isn't a way to create a proper integration test since ROS used for testing - // isn't configured to accept such requests. Instead we inspect the log from Sync which will - // output the headers in TRACE mode. - @Test - @RunTestInLooperThread - public void syncAuthHeaderAndUrlPrefix() { - SyncManager.setAuthorizationHeaderName("TestAuth"); - SyncManager.addCustomRequestHeader("Test", "test"); - runSyncAuthHeadersAndUrlPrefixTest(); - } - - // Test that auth header renaming, custom headers and url prefix are all propagated correctly - // to Sync. There really isn't a way to create a proper integration test since ROS used for testing - // isn't configured to accept such requests. Instead we inspect the log from Sync which will - // output the headers in TRACE mode. - @Test - @RunTestInLooperThread - public void syncAuthHeaderAndUrlPrefix_specificHost() { - SyncManager.setAuthorizationHeaderName("TestAuth", Constants.HOST); - SyncManager.addCustomRequestHeader("Test", "test", Constants.HOST); - runSyncAuthHeadersAndUrlPrefixTest(); - } - - private void runSyncAuthHeadersAndUrlPrefixTest() { - SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true); - SyncUser user = SyncUser.logIn(credentials, Constants.AUTH_URL); - SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .urlPrefix("/foo") - .errorHandler(new SyncSession.ErrorHandler() { - @Override - public void onError(SyncSession session, ObjectServerError error) { - RealmLog.error(error.toString()); - } - }) - .build(); - - RealmLog.setLevel(LogLevel.ALL); - RealmLogger logger = (level, tag, throwable, message) -> { - if (tag.equals("REALM_SYNC") - && message.contains("GET /foo/") - && message.contains("TestAuth: Realm-Access-Token version=1") - && message.contains("Test: test")) { - looperThread.testComplete(); - } - }; - looperThread.runAfterTest(() -> { - RealmLog.remove(logger); - }); - RealmLog.add(logger); - Realm realm = Realm.getInstance(config); - looperThread.closeAfterTest(realm); - } - - @Test - @RunTestInLooperThread - public void progressListenersWorkWhenUsingWaitForInitialRemoteData() throws InterruptedException { - String username = UUID.randomUUID().toString(); - String password = "password"; - SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - final SyncConfiguration configOld = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly.class) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build(); - Realm realm = Realm.getInstance(configOld); - realm.executeTransaction(new Realm.Transaction() { - @Override - public void execute(Realm realm) { - for (int i = 0; i < 10; i++) { - realm.createObject(StringOnly.class).setChars("Foo" + i); - } - } - }); - SyncManager.getSession(configOld).uploadAllLocalChanges(); - realm.close(); - user.logOut(); - assertTrue(SyncManager.getAllSessions(user).isEmpty()); - - // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with - // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL); - SyncConfiguration config = user.createConfiguration(Constants.USER_REALM) - .name("newRealm") - .schema(StringOnly.class) - .waitForInitialRemoteData() - .build(); - assertFalse(config.realmExists()); - AtomicBoolean indefineteListenerComplete = new AtomicBoolean(false); - AtomicBoolean currentChangesListenerComplete = new AtomicBoolean(false); - RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() { - - @Override - public void onSuccess(Realm realm) { - realm.close(); - if (!indefineteListenerComplete.get()) { - fail("Indefinete progress listener did not report complete."); - } - if (!currentChangesListenerComplete.get()) { - fail("Current changes progress listener did not report complete."); - } - looperThread.testComplete(); - } - - @Override - public void onError(Throwable exception) { - fail(exception.toString()); - } - }); - looperThread.keepStrongReference(task); - SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.INDEFINITELY, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - indefineteListenerComplete.set(true); - } - } - }); - SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, new ProgressListener() { - @Override - public void onChange(Progress progress) { - if (progress.isTransferComplete()) { - currentChangesListenerComplete.set(true); - } - } - }); - } -} diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt new file mode 100644 index 0000000000..1f972ed043 --- /dev/null +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -0,0 +1,466 @@ +/* + * Copyright 2017 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm + +import android.os.SystemClock +import androidx.test.annotation.UiThreadTest +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.entities.StringOnly +import io.realm.exceptions.DownloadingRealmInterruptedException +import io.realm.exceptions.RealmMigrationNeededException +import io.realm.internal.OsRealmConfig +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.log.RealmLogger +import io.realm.objectserver.utils.Constants +import io.realm.rule.RunTestInLooperThread +import org.junit.Assert +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import java.io.File +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Catch all class for tests that not naturally fit anywhere else. + */ +@RunWith(AndroidJUnit4::class) +class SyncedRealmIntegrationTests : StandardIntegrationTest() { + @Test + @RunTestInLooperThread + @Throws(InterruptedException::class) + fun loginLogoutResumeSyncing() { + val username = UUID.randomUUID().toString() + val password = "password" + var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .schema(StringOnly::class.java) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build() + val realm = Realm.getInstance(config) + realm.beginTransaction() + realm.createObject(StringOnly::class.java).chars = "Foo" + realm.commitTransaction() + SyncManager.getSession(config).uploadAllLocalChanges() + user.logOut() + realm.close() + try { + Assert.assertTrue(Realm.deleteRealm(config)) + } catch (e: IllegalStateException) { + // FIXME: We don't have a way to ensure that the Realm instance on client thread has been + // closed for now https://github.com/realm/realm-java/issues/5416 + if (e.message!!.contains("It's not allowed to delete the file")) { + // retry after 1 second + SystemClock.sleep(1000) + Assert.assertTrue(Realm.deleteRealm(config)) + } + } + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL) + val config2: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .schema(StringOnly::class.java) + .build() + val realm2 = Realm.getInstance(config2) + SyncManager.getSession(config2).downloadAllServerChanges() + realm2.refresh() + Assert.assertEquals(1, realm2.where(StringOnly::class.java).count()) + realm2.close() + looperThread.testComplete() + } + + @Test + @UiThreadTest + fun waitForInitialRemoteData_mainThreadThrows() { + val user: SyncUser = SyncTestUtils.createTestUser(Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .waitForInitialRemoteData() + .build() + var realm: Realm? = null + try { + realm = Realm.getInstance(config) + Assert.fail() + } catch (ignore: IllegalStateException) { + } finally { + realm?.close() + } + } + + @Test + @Throws(InterruptedException::class) + fun waitForInitialRemoteData() { + val username = UUID.randomUUID().toString() + val password = "password" + var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly::class.java) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build() + var realm = Realm.getInstance(configOld) + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(StringOnly::class.java).chars = "Foo$i" + } + } + SyncManager.getSession(configOld).uploadAllLocalChanges() + realm.close() + user.logOut() + + // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with + // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .name("newRealm") + .schema(StringOnly::class.java) + .waitForInitialRemoteData() + .build() + realm = Realm.getInstance(config) + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(StringOnly::class.java).chars = "Foo 1$i" + } + } + try { + Assert.assertEquals(20, realm.where(StringOnly::class.java).count()) + } finally { + realm.close() + } + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + + " https://github.com/realm/realm-java/issues/5416") + @Throws(InterruptedException::class) + fun waitForInitialData_resilientInCaseOfRetries() { + val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .waitForInitialRemoteData() + .build() + for (i in 0..9) { + val t = Thread(Runnable { + var realm: Realm? = null + try { + // This will cause the download latch called later to immediately throw an InterruptedException. + Thread.currentThread().interrupt() + realm = Realm.getInstance(config) + } catch (ignored: DownloadingRealmInterruptedException) { + Assert.assertFalse(File(config.getPath()).exists()) + } finally { + if (realm != null) { + realm.close() + Realm.deleteRealm(config) + } + } + }) + t.start() + t.join() + } + } + + // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that + // We cannot do much better since we cannot control the order of events internally in Realm which would be + // needed to correctly test all error paths. + @Test + @RunTestInLooperThread + @Ignore("See https://github.com/realm/realm-java/issues/5373") + fun waitForInitialData_resilientInCaseOfRetriesAsync() { + val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .directory(configurationFactory.getRoot()) + .waitForInitialRemoteData() + .build() + val randomizer = Random() + for (i in 0..9) { + val task = Realm.getInstanceAsync(config, object : Realm.Callback() { + override fun onSuccess(realm: Realm) { + Assert.fail() + } + + override fun onError(exception: Throwable) { + Assert.fail(exception.toString()) + } + }) + SystemClock.sleep(randomizer.nextInt(5).toLong()) + task.cancel() + } + looperThread.testComplete() + } + + @Test + @Throws(InterruptedException::class) + fun waitForInitialRemoteData_readOnlyTrue() { + val username = UUID.randomUUID().toString() + val password = "password" + var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly::class.java) + .build() + var realm = Realm.getInstance(configOld) + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(StringOnly::class.java).chars = "Foo$i" + } + } + SyncManager.getSession(configOld).uploadAllLocalChanges() + realm.close() + user.logOut() + + // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should + // download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL) + val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .name("newRealm") + .waitForInitialRemoteData() + .readOnly() + .schema(StringOnly::class.java) + .build() + Assert.assertFalse(configNew.realmExists()) + realm = Realm.getInstance(configNew) + Assert.assertEquals(10, realm.where(StringOnly::class.java).count()) + realm.close() + user.logOut() + } + + @Test + fun waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { + val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .waitForInitialRemoteData() + .readOnly() + .schema(StringOnly::class.java) + .build() + Assert.assertFalse(configNew.realmExists()) + var realm: Realm? = null + try { + // This will fail, because the server Realm is completely empty and the Client is not allowed to write the + // schema. + realm = Realm.getInstance(configNew) + Assert.fail() + } catch (ignore: RealmMigrationNeededException) { + } finally { + realm?.close() + user.logOut() + } + } + + @Test + fun waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { + val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .waitForInitialRemoteData() // Not readonly so Client should be allowed to write schema + .schema(StringOnly::class.java) // This schema should be written when opening the empty Realm. + .schemaVersion(2) + .build() + Assert.assertFalse(config.realmExists()) + val realm = Realm.getInstance(config) + try { + Assert.assertEquals(0, realm.where(StringOnly::class.java).count()) + } finally { + realm.close() + user.logOut() + } + } + + @Ignore("FIXME: Re-enable this once we can test againt a proper Stitch server") + @Test + @Throws(InterruptedException::class) + fun defaultRealm() { + val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val config: SyncConfiguration = user.getDefaultConfiguration() + val realm = Realm.getInstance(config) + SyncManager.getSession(config).downloadAllServerChanges() + realm.refresh() + try { + Assert.assertTrue(realm.isEmpty) + } finally { + realm.close() + user.logOut() + } + } + + // Check that custom headers and auth header renames are correctly used for HTTP requests + // performed from Java. + @Test + @RunTestInLooperThread + fun javaRequestCustomHeaders() { + SyncManager.addCustomRequestHeader("Foo", "bar") + SyncManager.setAuthorizationHeaderName("RealmAuth") + runJavaRequestCustomHeadersTest() + } + + // Check that custom headers and auth header renames are correctly used for HTTP requests + // performed from Java. + @Test + @RunTestInLooperThread + fun javaRequestCustomHeaders_specificHost() { + SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST) + SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST) + runJavaRequestCustomHeadersTest() + } + + private fun runJavaRequestCustomHeadersTest() { + val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) + val headerSet = AtomicBoolean(false) + RealmLog.setLevel(LogLevel.ALL) + val logger = RealmLogger { level: Int, tag: String?, throwable: Throwable?, message: String? -> + if (level == LogLevel.TRACE && message!!.contains("Foo: bar") + && message.contains("RealmAuth: ")) { + headerSet.set(true) + } + } + looperThread.runAfterTest({ RealmLog.remove(logger) }) + RealmLog.add(logger) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + try { + user.changePassword("foo") + } catch (e: ObjectServerError) { + if (e.getErrorCode() !== ErrorCode.INVALID_CREDENTIALS) { + throw e + } + } + Assert.assertTrue(headerSet.get()) + looperThread.testComplete() + } + + // Test that auth header renaming, custom headers and url prefix are all propagated correctly + // to Sync. There really isn't a way to create a proper integration test since ROS used for testing + // isn't configured to accept such requests. Instead we inspect the log from Sync which will + // output the headers in TRACE mode. + @Test + @RunTestInLooperThread + fun syncAuthHeaderAndUrlPrefix() { + SyncManager.setAuthorizationHeaderName("TestAuth") + SyncManager.addCustomRequestHeader("Test", "test") + runSyncAuthHeadersAndUrlPrefixTest() + } + + // Test that auth header renaming, custom headers and url prefix are all propagated correctly + // to Sync. There really isn't a way to create a proper integration test since ROS used for testing + // isn't configured to accept such requests. Instead we inspect the log from Sync which will + // output the headers in TRACE mode. + @Test + @RunTestInLooperThread + fun syncAuthHeaderAndUrlPrefix_specificHost() { + SyncManager.setAuthorizationHeaderName("TestAuth", Constants.HOST) + SyncManager.addCustomRequestHeader("Test", "test", Constants.HOST) + runSyncAuthHeadersAndUrlPrefixTest() + } + + private fun runSyncAuthHeadersAndUrlPrefixTest() { + val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) + val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .urlPrefix("/foo") + .errorHandler(object : ErrorHandler() { + fun onError(session: SyncSession?, error: ObjectServerError) { + RealmLog.error(error.toString()) + } + }) + .build() + RealmLog.setLevel(LogLevel.ALL) + val logger = RealmLogger { level: Int, tag: String, throwable: Throwable?, message: String? -> + if (tag == "REALM_SYNC" && message!!.contains("GET /foo/") + && message.contains("TestAuth: Realm-Access-Token version=1") + && message.contains("Test: test")) { + looperThread.testComplete() + } + } + looperThread.runAfterTest({ RealmLog.remove(logger) }) + RealmLog.add(logger) + val realm = Realm.getInstance(config) + looperThread.closeAfterTest(realm) + } + + @Test + @RunTestInLooperThread + @Throws(InterruptedException::class) + fun progressListenersWorkWhenUsingWaitForInitialRemoteData() { + val username = UUID.randomUUID().toString() + val password = "password" + var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .schema(StringOnly::class.java) + .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build() + val realm = Realm.getInstance(configOld) + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(StringOnly::class.java).chars = "Foo$i" + } + } + SyncManager.getSession(configOld).uploadAllLocalChanges() + realm.close() + user.logOut() + Assert.assertTrue(SyncManager.getAllSessions(user).isEmpty()) + + // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with + // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). + user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL) + val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + .name("newRealm") + .schema(StringOnly::class.java) + .waitForInitialRemoteData() + .build() + Assert.assertFalse(config.realmExists()) + val indefineteListenerComplete = AtomicBoolean(false) + val currentChangesListenerComplete = AtomicBoolean(false) + val task = Realm.getInstanceAsync(config, object : Realm.Callback() { + override fun onSuccess(realm: Realm) { + realm.close() + if (!indefineteListenerComplete.get()) { + Assert.fail("Indefinete progress listener did not report complete.") + } + if (!currentChangesListenerComplete.get()) { + Assert.fail("Current changes progress listener did not report complete.") + } + looperThread.testComplete() + } + + override fun onError(exception: Throwable) { + Assert.fail(exception.toString()) + } + }) + looperThread.keepStrongReference(task) + SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.INDEFINITELY, object : ProgressListener() { + fun onChange(progress: Progress) { + if (progress.isTransferComplete()) { + indefineteListenerComplete.set(true) + } + } + }) + SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, object : ProgressListener() { + fun onChange(progress: Progress) { + if (progress.isTransferComplete()) { + currentChangesListenerComplete.set(true) + } + } + }) + } +} From 6c4cbebf004ba9f6d8fbd2f8ff409e434aa73871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Wed, 10 Jun 2020 16:30:11 +0200 Subject: [PATCH 03/15] Update to new Sync implementation and Kotlin --- .../java/io/realm/RealmConfiguration.java | 2 +- .../network/OkHttpNetworkTransport.java | 14 + .../realm/mongodb/sync/SyncConfiguration.java | 5 + .../kotlin/io/realm/SyncSessionTests.kt | 3 +- .../io/realm/SyncedRealmIntegrationTests.kt | 503 +++++++++--------- .../syncTestUtils/kotlin/io/realm/TestApp.kt | 5 +- .../io/realm/TestSyncConfigurationFactory.kt | 7 + .../mongodb/sync/SyncConfigurationExt.kt | 4 + 8 files changed, 273 insertions(+), 270 deletions(-) diff --git a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java index cd81d56373..b26ff44778 100644 --- a/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java +++ b/realm/realm-library/src/main/java/io/realm/RealmConfiguration.java @@ -245,7 +245,7 @@ public String getPath() { * * @return {@code true} if the Realm file exists, {@code false} otherwise. */ - boolean realmExists() { + protected boolean realmExists() { return new File(canonicalPath).exists(); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java index 896bbed2d2..658b73d4ce 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java @@ -9,6 +9,7 @@ import io.realm.internal.objectstore.OsJavaNetworkTransport; import io.realm.log.LogLevel; import io.realm.log.RealmLog; +import io.realm.mongodb.AppConfiguration; import okhttp3.Call; import okhttp3.ConnectionPool; import okhttp3.Headers; @@ -43,6 +44,19 @@ public Response sendRequest(String method, String url, long timeoutMs, Map entry : getCustomRequestHeaders().entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue()); + } + // and then replace default authorization header with custom one if present + String authorizationHeaderValue = headers.get(AppConfiguration.DEFAULT_AUTHORIZATION_HEADER_NAME); + String authorizationHeaderName = getAuthorizationHeaderName(); + if (authorizationHeaderValue != null && !AppConfiguration.DEFAULT_AUTHORIZATION_HEADER_NAME.equals(authorizationHeaderName)) { + headers.remove(AppConfiguration.DEFAULT_AUTHORIZATION_HEADER_NAME); + headers.put(authorizationHeaderName, authorizationHeaderValue); + } + for (Map.Entry entry : headers.entrySet()) { builder.addHeader(entry.getKey(), entry.getValue()); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java index df3ffde8c4..1d9fde4996 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncConfiguration.java @@ -441,6 +441,11 @@ public BsonValue getPartitionValue() { return partitionValue; } + @Override + protected boolean realmExists() { + return super.realmExists(); + } + /** * Builder used to construct instances of a SyncConfiguration in a fluent manner. */ diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index bcac480d64..c8e87af995 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -730,7 +730,8 @@ class SyncSessionTests { @Test // FIXME Investigate @Ignore("Asserts with no_session when tearing down, meaning that all session are not " + - "closed, but realm seems to be closed, so further investigation is needed") + "closed, but realm seems to be closed, so further investigation is needed " + + "seems to be caused by https://github.com/realm/realm-java/issues/5416") fun waitForInitialRemoteData_throwsOnTimeout() = looperThread.runBlocking { val syncConfiguration = configFactory .createSyncConfigurationBuilder(user) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index 1f972ed043..41c27d8cf2 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017 Realm Inc. + * Copyright 2020 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,126 +18,156 @@ package io.realm import android.os.SystemClock import androidx.test.annotation.UiThreadTest import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.realm.entities.StringOnly +import androidx.test.platform.app.InstrumentationRegistry +import io.realm.entities.DefaultSyncSchema +import io.realm.entities.SyncStringOnly import io.realm.exceptions.DownloadingRealmInterruptedException import io.realm.exceptions.RealmMigrationNeededException import io.realm.internal.OsRealmConfig +import io.realm.kotlin.syncSession import io.realm.log.LogLevel import io.realm.log.RealmLog import io.realm.log.RealmLogger -import io.realm.objectserver.utils.Constants -import io.realm.rule.RunTestInLooperThread -import org.junit.Assert -import org.junit.Ignore -import org.junit.Test +import io.realm.mongodb.* +import io.realm.mongodb.sync.* +import io.realm.rule.BlockingLooperThread +import io.realm.util.assertFailsWithErrorCode +import org.bson.BsonObjectId +import org.bson.types.ObjectId +import org.junit.* import org.junit.runner.RunWith import java.io.File import java.util.* import java.util.concurrent.atomic.AtomicBoolean +import kotlin.test.assertFailsWith + +private const val SECRET_PASSWORD = "123456" +private const val CUSTOM_HEADER_NAME = "Foo" +private const val CUSTOM_HEADER_VALUE = "bar" +private const val AUTH_HEADER_NAME = "RealmAuth" /** * Catch all class for tests that not naturally fit anywhere else. */ @RunWith(AndroidJUnit4::class) -class SyncedRealmIntegrationTests : StandardIntegrationTest() { +class SyncedRealmIntegrationTests { + + private val looperThread = BlockingLooperThread() + + private lateinit var app: App + private lateinit var user: User + private lateinit var syncConfiguration: SyncConfiguration + + private val configurationFactory: TestSyncConfigurationFactory = TestSyncConfigurationFactory() + + @Before + fun setup() { + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + RealmLog.setLevel(LogLevel.ALL) + app = TestApp() + user = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + syncConfiguration = configurationFactory + // TODO We generate new partition value for each test to avoid overlaps in data. We + // could make test booting with a cleaner state by somehow flushing data between + // tests. + .createSyncConfigurationBuilder(user, BsonObjectId(ObjectId())) + .modules(DefaultSyncSchema()) + .build() + } + + @After + fun teardown() { + if (this::app.isInitialized) { + app.close() + } + RealmLog.setLevel(LogLevel.WARN) + } + @Test - @RunTestInLooperThread - @Throws(InterruptedException::class) - fun loginLogoutResumeSyncing() { - val username = UUID.randomUUID().toString() - val password = "password" - var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .schema(StringOnly::class.java) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + fun loginLogoutResumeSyncing() = looperThread.runBlocking { + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSchema(SyncStringOnly::class.java) + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build() - val realm = Realm.getInstance(config) - realm.beginTransaction() - realm.createObject(StringOnly::class.java).chars = "Foo" - realm.commitTransaction() - SyncManager.getSession(config).uploadAllLocalChanges() - user.logOut() - realm.close() + Realm.getInstance(config).use { realm -> + realm.executeTransaction { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo" + } + realm.syncSession.uploadAllLocalChanges() + user.logOut() + } try { Assert.assertTrue(Realm.deleteRealm(config)) } catch (e: IllegalStateException) { // FIXME: We don't have a way to ensure that the Realm instance on client thread has been - // closed for now https://github.com/realm/realm-java/issues/5416 + // closed for now https://github.com/realm/realm-java/issues/5416 if (e.message!!.contains("It's not allowed to delete the file")) { // retry after 1 second SystemClock.sleep(1000) Assert.assertTrue(Realm.deleteRealm(config)) } } - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL) - val config2: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .schema(StringOnly::class.java) + + // FIXME Is this sufficient to test "loginLogoutResumeSynching"-case + user = app.login(Credentials.emailPassword(user.email, SECRET_PASSWORD)) + val config2: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSchema(SyncStringOnly::class.java) .build() - val realm2 = Realm.getInstance(config2) - SyncManager.getSession(config2).downloadAllServerChanges() - realm2.refresh() - Assert.assertEquals(1, realm2.where(StringOnly::class.java).count()) - realm2.close() + Realm.getInstance(config2).use { realm -> + realm.syncSession.downloadAllServerChanges() + realm.refresh() + Assert.assertEquals(1, realm.where(SyncStringOnly::class.java).count()) + } looperThread.testComplete() } @Test @UiThreadTest fun waitForInitialRemoteData_mainThreadThrows() { - val user: SyncUser = SyncTestUtils.createTestUser(Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + val user: User = SyncTestUtils.createTestUser(app) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() .build() - var realm: Realm? = null - try { - realm = Realm.getInstance(config) - Assert.fail() - } catch (ignore: IllegalStateException) { - } finally { - realm?.close() + assertFailsWith { + Realm.getInstance(config).close() } } @Test - @Throws(InterruptedException::class) fun waitForInitialRemoteData() { - val username = UUID.randomUUID().toString() - val password = "password" - var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly::class.java) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSchema(SyncStringOnly::class.java) + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build() - var realm = Realm.getInstance(configOld) - realm.executeTransaction { realm -> - for (i in 0..9) { - realm.createObject(StringOnly::class.java).chars = "Foo$i" + Realm.getInstance(configOld).use { realm -> + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" + } } + realm.syncSession.uploadAllLocalChanges() } - SyncManager.getSession(configOld).uploadAllLocalChanges() - realm.close() user.logOut() // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .name("newRealm") - .schema(StringOnly::class.java) + // FIXME Is this sufficient for testing. I guess we use the same local file when we cannot + // set the name + user = app.login(Credentials.emailPassword(user.email, SECRET_PASSWORD)) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + // FIXME How does this work for sync? + // .name("newRealm") + .testSchema(SyncStringOnly::class.java) .waitForInitialRemoteData() .build() - realm = Realm.getInstance(config) - realm.executeTransaction { realm -> - for (i in 0..9) { - realm.createObject(StringOnly::class.java).chars = "Foo 1$i" + Realm.getInstance(config).use { realm -> + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo 1$i" + } } - } - try { - Assert.assertEquals(20, realm.where(StringOnly::class.java).count()) - } finally { - realm.close() + Assert.assertEquals(20, realm.where(SyncStringOnly::class.java).count()) } } @@ -147,28 +177,19 @@ class SyncedRealmIntegrationTests : StandardIntegrationTest() { @Test @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + " https://github.com/realm/realm-java/issues/5416") - @Throws(InterruptedException::class) fun waitForInitialData_resilientInCaseOfRetries() { - val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() .build() for (i in 0..9) { val t = Thread(Runnable { var realm: Realm? = null - try { - // This will cause the download latch called later to immediately throw an InterruptedException. + assertFailsWith { Thread.currentThread().interrupt() - realm = Realm.getInstance(config) - } catch (ignored: DownloadingRealmInterruptedException) { - Assert.assertFalse(File(config.getPath()).exists()) - } finally { - if (realm != null) { - realm.close() - Realm.deleteRealm(config) - } + Realm.getInstance(config).close() } + Assert.assertFalse(File(config.getPath()).exists()) + Realm.deleteRealm(config) }) t.start() t.join() @@ -179,26 +200,21 @@ class SyncedRealmIntegrationTests : StandardIntegrationTest() { // We cannot do much better since we cannot control the order of events internally in Realm which would be // needed to correctly test all error paths. @Test - @RunTestInLooperThread - @Ignore("See https://github.com/realm/realm-java/issues/5373") - fun waitForInitialData_resilientInCaseOfRetriesAsync() { - val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .directory(configurationFactory.getRoot()) + // FIXME This does not throw anymore as described in issue. But do the test still make sense + // with new sync? + //@Ignore("See https://github.com/realm/realm-java/issues/5373") + fun waitForInitialData_resilientInCaseOfRetriesAsync() = looperThread.runBlocking { + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + // FIXME Is this important for the test + //.directory(configurationFactory.getRoot()) .waitForInitialRemoteData() .build() val randomizer = Random() for (i in 0..9) { val task = Realm.getInstanceAsync(config, object : Realm.Callback() { - override fun onSuccess(realm: Realm) { - Assert.fail() - } - - override fun onError(exception: Throwable) { - Assert.fail(exception.toString()) - } + override fun onSuccess(realm: Realm) { Assert.fail() } + override fun onError(exception: Throwable) { Assert.fail(exception.toString()) } }) SystemClock.sleep(randomizer.nextInt(5).toLong()) task.cancel() @@ -207,235 +223,205 @@ class SyncedRealmIntegrationTests : StandardIntegrationTest() { } @Test - @Throws(InterruptedException::class) + // FIXME Investigate fun waitForInitialRemoteData_readOnlyTrue() { - val username = UUID.randomUUID().toString() - val password = "password" - var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly::class.java) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSchema(SyncStringOnly::class.java) .build() - var realm = Realm.getInstance(configOld) - realm.executeTransaction { realm -> - for (i in 0..9) { - realm.createObject(StringOnly::class.java).chars = "Foo$i" + Realm.getInstance(configOld).use { realm -> + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" + } } + realm.syncSession.uploadAllLocalChanges() } - SyncManager.getSession(configOld).uploadAllLocalChanges() - realm.close() user.logOut() // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, false), Constants.AUTH_URL) - val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .name("newRealm") + // FIXME Is this sufficient for test. Guess we need a separate file to be able to test it!? + user = app.login(Credentials.emailPassword(user.email, SECRET_PASSWORD)) + val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + // FIXME IS this essential for tests + // .name("newRealm") .waitForInitialRemoteData() .readOnly() - .schema(StringOnly::class.java) + .testSchema(SyncStringOnly::class.java) .build() - Assert.assertFalse(configNew.realmExists()) - realm = Realm.getInstance(configNew) - Assert.assertEquals(10, realm.where(StringOnly::class.java).count()) - realm.close() + // FIXME This assert fails...due to commented .name above + Assert.assertFalse(configNew.testRealmExists()) + Realm.getInstance(configNew).use { realm -> + Assert.assertEquals(10, realm.where(SyncStringOnly::class.java).count()) + } user.logOut() } @Test + // FIXME Investigate fun waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { - val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() .readOnly() - .schema(StringOnly::class.java) + .testSchema(SyncStringOnly::class.java) .build() - Assert.assertFalse(configNew.realmExists()) - var realm: Realm? = null - try { + Assert.assertFalse(configNew.testRealmExists()) + assertFailsWith { // This will fail, because the server Realm is completely empty and the Client is not allowed to write the // schema. - realm = Realm.getInstance(configNew) - Assert.fail() - } catch (ignore: RealmMigrationNeededException) { - } finally { - realm?.close() - user.logOut() + // FIXME Does not throw. Has something changed around this + Realm.getInstance(configNew).close() } + user.logOut() } @Test fun waitForInitialRemoteData_readOnlyFalse_upgradeSchema() { - val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() // Not readonly so Client should be allowed to write schema - .schema(StringOnly::class.java) // This schema should be written when opening the empty Realm. + .testSchema(SyncStringOnly::class.java) // This schema should be written when opening the empty Realm. .schemaVersion(2) .build() - Assert.assertFalse(config.realmExists()) - val realm = Realm.getInstance(config) - try { - Assert.assertEquals(0, realm.where(StringOnly::class.java).count()) - } finally { - realm.close() - user.logOut() + Assert.assertFalse(config.testRealmExists()) + Realm.getInstance(config).use { realm -> + Assert.assertEquals(0, realm.where(SyncStringOnly::class.java).count()) } + user.logOut() } - @Ignore("FIXME: Re-enable this once we can test againt a proper Stitch server") @Test - @Throws(InterruptedException::class) fun defaultRealm() { - val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val config: SyncConfiguration = user.getDefaultConfiguration() - val realm = Realm.getInstance(config) - SyncManager.getSession(config).downloadAllServerChanges() - realm.refresh() - try { + val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, user.id) + Realm.getInstance(config).use { realm -> + realm.syncSession.downloadAllServerChanges() + realm.refresh() Assert.assertTrue(realm.isEmpty) - } finally { - realm.close() - user.logOut() } + user.logOut() } // Check that custom headers and auth header renames are correctly used for HTTP requests // performed from Java. + // FIXME Move to AppConfigurationTestt @Test - @RunTestInLooperThread fun javaRequestCustomHeaders() { - SyncManager.addCustomRequestHeader("Foo", "bar") - SyncManager.setAuthorizationHeaderName("RealmAuth") - runJavaRequestCustomHeadersTest() + looperThread.runBlocking { + // FIXME Hack to overcome that we cannot have multiple apps and needs to adjust + // configuration of TestApp for this test. Would not be required in AppConfigurationTest + app.close() + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + val app = TestApp(builder = { builder -> + builder.addCustomRequestHeader(CUSTOM_HEADER_NAME, CUSTOM_HEADER_VALUE) + builder.authorizationHeaderName(AUTH_HEADER_NAME) + }) + runJavaRequestCustomHeadersTest(app) + } } - // Check that custom headers and auth header renames are correctly used for HTTP requests - // performed from Java. - @Test - @RunTestInLooperThread - fun javaRequestCustomHeaders_specificHost() { - SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST) - SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST) - runJavaRequestCustomHeadersTest() - } + // FIXME Seems to be outdated...cannot find an option for setting headers for a specific host +// // Check that custom headers and auth header renames are correctly used for HTTP requests +// // performed from Java. +// @Test +// @RunTestInLooperThread +// fun javaRequestCustomHeaders_specificHost() { +// SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST) +// SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST) +// runJavaRequestCustomHeadersTest() +// } - private fun runJavaRequestCustomHeadersTest() { - val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) + private fun runJavaRequestCustomHeadersTest(app: App) { + val username = UUID.randomUUID().toString() + val password = "password" val headerSet = AtomicBoolean(false) + + // Setup logger to inspect that we get a log message with the custom headers RealmLog.setLevel(LogLevel.ALL) val logger = RealmLogger { level: Int, tag: String?, throwable: Throwable?, message: String? -> - if (level == LogLevel.TRACE && message!!.contains("Foo: bar") + if (level > LogLevel.TRACE && message!!.contains(CUSTOM_HEADER_NAME) && message.contains(CUSTOM_HEADER_VALUE) && message.contains("RealmAuth: ")) { headerSet.set(true) } } - looperThread.runAfterTest({ RealmLog.remove(logger) }) RealmLog.add(logger) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - try { - user.changePassword("foo") - } catch (e: ObjectServerError) { - if (e.getErrorCode() !== ErrorCode.INVALID_CREDENTIALS) { - throw e - } + assertFailsWithErrorCode(ErrorCode.SERVICE_UNKNOWN) { + app.registerUserAndLogin(username, password) } + // FIXME Guess it would be better to reset logger on Realm.init, but not sure of impact + // ...or is the logger intentionally shared to enable full trace of a full test run? + RealmLog.remove(logger) + Assert.assertTrue(headerSet.get()) looperThread.testComplete() } - // Test that auth header renaming, custom headers and url prefix are all propagated correctly - // to Sync. There really isn't a way to create a proper integration test since ROS used for testing - // isn't configured to accept such requests. Instead we inspect the log from Sync which will - // output the headers in TRACE mode. @Test - @RunTestInLooperThread - fun syncAuthHeaderAndUrlPrefix() { - SyncManager.setAuthorizationHeaderName("TestAuth") - SyncManager.addCustomRequestHeader("Test", "test") - runSyncAuthHeadersAndUrlPrefixTest() - } - - // Test that auth header renaming, custom headers and url prefix are all propagated correctly - // to Sync. There really isn't a way to create a proper integration test since ROS used for testing - // isn't configured to accept such requests. Instead we inspect the log from Sync which will - // output the headers in TRACE mode. - @Test - @RunTestInLooperThread - fun syncAuthHeaderAndUrlPrefix_specificHost() { - SyncManager.setAuthorizationHeaderName("TestAuth", Constants.HOST) - SyncManager.addCustomRequestHeader("Test", "test", Constants.HOST) - runSyncAuthHeadersAndUrlPrefixTest() - } - - private fun runSyncAuthHeadersAndUrlPrefixTest() { - val credentials: SyncCredentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "test", true) - val user: SyncUser = SyncUser.logIn(credentials, Constants.AUTH_URL) - val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .urlPrefix("/foo") - .errorHandler(object : ErrorHandler() { - fun onError(session: SyncSession?, error: ObjectServerError) { - RealmLog.error(error.toString()) - } - }) - .build() - RealmLog.setLevel(LogLevel.ALL) - val logger = RealmLogger { level: Int, tag: String, throwable: Throwable?, message: String? -> - if (tag == "REALM_SYNC" && message!!.contains("GET /foo/") - && message.contains("TestAuth: Realm-Access-Token version=1") - && message.contains("Test: test")) { - looperThread.testComplete() - } - } - looperThread.runAfterTest({ RealmLog.remove(logger) }) - RealmLog.add(logger) - val realm = Realm.getInstance(config) - looperThread.closeAfterTest(realm) - } - - @Test - @RunTestInLooperThread - @Throws(InterruptedException::class) - fun progressListenersWorkWhenUsingWaitForInitialRemoteData() { + // FIXME Investigate + fun progressListenersWorkWhenUsingWaitForInitialRemoteData() = looperThread.runBlocking { val username = UUID.randomUUID().toString() val password = "password" - var user: SyncUser = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL) + var user: User = app.registerUserAndLogin(username, password) // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) - .schema(StringOnly::class.java) - .sessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSchema(SyncStringOnly::class.java) + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) .build() - val realm = Realm.getInstance(configOld) - realm.executeTransaction { realm -> - for (i in 0..9) { - realm.createObject(StringOnly::class.java).chars = "Foo$i" + Realm.getInstance(configOld).use { realm -> + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" + } } + realm.syncSession.uploadAllLocalChanges() } - SyncManager.getSession(configOld).uploadAllLocalChanges() - realm.close() user.logOut() - Assert.assertTrue(SyncManager.getAllSessions(user).isEmpty()) + + // FIXME Is this equivalent to the old + // Assert.assertTrue(SyncManager.getAllSessions(user).isEmpty()) + assertFailsWith { + app.sync.getSession(configOld) + } // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password), Constants.AUTH_URL) - val config: SyncConfiguration = user.createConfiguration(Constants.USER_REALM) - .name("newRealm") - .schema(StringOnly::class.java) - .waitForInitialRemoteData() + // FIXME This whole part is maybe not applicable for new sync. Maybe use a new users to + // force using a new file?? + user = app.login(Credentials.emailPassword(username, password)) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + // FIXME Is this essential for tests + // .name("newRealm") + .testSchema(SyncStringOnly::class.java) + // FIXME I guess we should not wair for initial remote data, as we want to test progress listeners + // .waitForInitialRemoteData() .build() - Assert.assertFalse(config.realmExists()) - val indefineteListenerComplete = AtomicBoolean(false) + + // FIXME Reintroduce? + // Assert.assertFalse(config.testRealmExists()) + + val indefiniteListenerComplete = AtomicBoolean(false) val currentChangesListenerComplete = AtomicBoolean(false) val task = Realm.getInstanceAsync(config, object : Realm.Callback() { override fun onSuccess(realm: Realm) { + // FIXME Is it acceptable to register the listeners here as we cannot access the + // session before the Realm is opened? + realm.syncSession.addDownloadProgressListener(ProgressMode.INDEFINITELY, object : ProgressListener { + override fun onChange(progress: Progress) { + if (progress.isTransferComplete()) { + indefiniteListenerComplete.set(true) + } + } + }) + realm.syncSession.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, object : ProgressListener { + override fun onChange(progress: Progress) { + if (progress.isTransferComplete()) { + currentChangesListenerComplete.set(true) + } + } + }) realm.close() - if (!indefineteListenerComplete.get()) { - Assert.fail("Indefinete progress listener did not report complete.") + if (!indefiniteListenerComplete.get()) { + Assert.fail("Indefinite progress listener did not report complete.") } if (!currentChangesListenerComplete.get()) { Assert.fail("Current changes progress listener did not report complete.") @@ -448,19 +434,6 @@ class SyncedRealmIntegrationTests : StandardIntegrationTest() { } }) looperThread.keepStrongReference(task) - SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.INDEFINITELY, object : ProgressListener() { - fun onChange(progress: Progress) { - if (progress.isTransferComplete()) { - indefineteListenerComplete.set(true) - } - } - }) - SyncManager.getSession(config).addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, object : ProgressListener() { - fun onChange(progress: Progress) { - if (progress.isTransferComplete()) { - currentChangesListenerComplete.set(true) - } - } - }) } + } diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt index 30e686d0dc..74155d983f 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestApp.kt @@ -26,7 +26,7 @@ import io.realm.mongodb.AppConfiguration * * NOTE: This class must remain in the [io.realm] package in order to work. */ -class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: (AppConfiguration.Builder) -> Unit = {}) : App(createConfiguration()) { +class TestApp(networkTransport: OsJavaNetworkTransport? = null, builder: (AppConfiguration.Builder) -> AppConfiguration.Builder = { it }) : App(builder(configurationBuilder()).build()) { init { if (networkTransport != null) { @@ -35,12 +35,11 @@ class TestApp(networkTransport: OsJavaNetworkTransport? = null, customizeConfig: } companion object { - fun createConfiguration(): AppConfiguration { + fun configurationBuilder(): AppConfiguration.Builder { return AppConfiguration.Builder(initializeMongoDbRealm()) .baseUrl("http://127.0.0.1:9090") .appName("MongoDB Realm Integration Tests") .appVersion("1.0.") - .build() } // Initializes MongoDB Realm. Clears all local state and fetches the application ID. diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt index 1b03689832..ed9b6fa2ee 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt @@ -30,11 +30,18 @@ import io.realm.rule.TestRealmConfigurationFactory * test ends. */ class TestSyncConfigurationFactory : TestRealmConfigurationFactory() { + + // FIXME Wouldn't it make more sense to default to user.id or something like that fun createSyncConfigurationBuilder(user: User?): SyncConfiguration.Builder { return SyncConfiguration.Builder(user, "default") .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) } + fun createSyncConfigurationBuilder(user: User, partitionValue: String): SyncConfiguration.Builder { + return SyncConfiguration.Builder(user, partitionValue) + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + } + fun createSyncConfigurationBuilder(user: User, partitionValue: BsonValue): SyncConfiguration.Builder { return SyncConfigurationExt.Builder(user, partitionValue) .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY); diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt index ceeeb7b785..43b8dae7ce 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/mongodb/sync/SyncConfigurationExt.kt @@ -25,6 +25,10 @@ class SyncConfigurationExt { companion object } +fun SyncConfiguration.testRealmExists(): Boolean{ + return this.realmExists() +} + // Added to expose Builder(User, BsonValue) outside io.realm.mongodb.sync package for test fun SyncConfigurationExt.Companion.Builder(user: User, partitionValue: BsonValue): SyncConfiguration.Builder { return SyncConfiguration.Builder(user, partitionValue) From 0b7ca9a048ebbab34aca9cd14b265ba28b60c136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 30 Jun 2020 09:21:41 +0200 Subject: [PATCH 04/15] Clean up --- .../kotlin/io/realm/AppConfigurationTests.kt | 78 ++++++- .../kotlin/io/realm/ProgressListenerTests.kt | 88 ++++++++ .../io/realm/SyncedRealmIntegrationTests.kt | 207 ++++-------------- 3 files changed, 199 insertions(+), 174 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt index 265ed26321..e67ff50f57 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt @@ -17,24 +17,35 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import io.realm.mongodb.AppConfiguration +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.log.RealmLogger +import io.realm.mongodb.* +import io.realm.rule.BlockingLooperThread +import io.realm.util.assertFailsWithErrorCode import org.bson.codecs.StringCodec import org.bson.codecs.configuration.CodecRegistries +import org.junit.* import org.junit.Assert.* -import org.junit.Before -import org.junit.Ignore -import org.junit.Rule -import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import java.io.File import java.lang.IllegalArgumentException import java.net.URL +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.collections.LinkedHashMap import kotlin.test.assertFailsWith +private const val CUSTOM_HEADER_NAME = "Foo" +private const val CUSTOM_HEADER_VALUE = "bar" +private const val AUTH_HEADER_NAME = "RealmAuth" + @RunWith(AndroidJUnit4::class) class AppConfigurationTests { + val looperThread = BlockingLooperThread() + @get:Rule val tempFolder = TemporaryFolder() @@ -275,4 +286,61 @@ class AppConfigurationTests { assertEquals(configCodecRegistry, config.defaultCodecRegistry) } + // Check that custom headers and auth header renames are correctly used for HTTP requests + // performed from Java. + @Test + fun javaRequestCustomHeaders() { + var app: App? = null + try { + looperThread.runBlocking { + app = TestApp(builder = { builder -> + builder.addCustomRequestHeader(CUSTOM_HEADER_NAME, CUSTOM_HEADER_VALUE) + builder.authorizationHeaderName(AUTH_HEADER_NAME) + }) + runJavaRequestCustomHeadersTest(app!!) + } + } finally { + app?.close() + } + } + + // FIXME Seems to be outdated...cannot find an option for setting headers for a specific host +// // Check that custom headers and auth header renames are correctly used for HTTP requests +// // performed from Java. +// @Test +// @RunTestInLooperThread +// fun javaRequestCustomHeaders_specificHost() { +// SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST) +// SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST) +// runJavaRequestCustomHeadersTest() +// } + + private fun runJavaRequestCustomHeadersTest(app: App) { + val username = UUID.randomUUID().toString() + val password = "password" + val headerSet = AtomicBoolean(false) + + // Setup logger to inspect that we get a log message with the custom headers + val level = RealmLog.getLevel() + RealmLog.setLevel(LogLevel.ALL) + val logger = RealmLogger { level: Int, tag: String?, throwable: Throwable?, message: String? -> + if (level > LogLevel.TRACE && message!!.contains(CUSTOM_HEADER_NAME) && message.contains(CUSTOM_HEADER_VALUE) + && message.contains("RealmAuth: ")) { + headerSet.set(true) + } + } + RealmLog.add(logger) + assertFailsWithErrorCode(ErrorCode.SERVICE_UNKNOWN) { + app.registerUserAndLogin(username, password) + } + // FIXME Guess it would be better to reset logger on Realm.init, but not sure of impact + // ...or is the logger intentionally shared to enable full trace of a full test run? + RealmLog.remove(logger) + RealmLog.setLevel(level) + + assertTrue(headerSet.get()) + looperThread.testComplete() + } + + } diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt index 9dea7c506e..74dbac4633 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt @@ -19,6 +19,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.entities.DefaultSyncSchema import io.realm.entities.SyncDog +import io.realm.entities.SyncStringOnly +import io.realm.internal.OsRealmConfig import io.realm.kotlin.syncSession import io.realm.kotlin.where import io.realm.log.LogLevel @@ -27,16 +29,21 @@ import io.realm.mongodb.User import io.realm.mongodb.close import io.realm.mongodb.registerUserAndLogin import io.realm.mongodb.sync.* +import io.realm.rule.BlockingLooperThread +import org.bson.types.ObjectId import org.junit.After import org.junit.Assert.* import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import java.lang.IllegalStateException import java.util.* import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger +import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) class ProgressListenerTests { @@ -45,6 +52,9 @@ class ProgressListenerTests { private const val TEST_SIZE: Long = 10 } + private val looperThread = BlockingLooperThread() + private val configurationFactory = TestSyncConfigurationFactory() + private lateinit var app: TestApp private lateinit var partitionValue: String @@ -274,6 +284,8 @@ class ProgressListenerTests { } @Test + // For some reason this does not work, even though the following + // progressListenersWorkWhenUsingWaitForInitialRemoteData works @Ignore("FIXME: Tracked by https://github.com/realm/realm-java/issues/6976") fun addProgressListener_triggerImmediatelyWhenRegistered_waitForInitialRemoteData() { val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") @@ -290,6 +302,82 @@ class ProgressListenerTests { } } + @Test + fun progressListenersWorkWhenUsingWaitForInitialRemoteData() = looperThread.runBlocking { + val username = UUID.randomUUID().toString() + val password = "password" + var user: User = app.registerUserAndLogin(username, password) + + // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) + val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) + .testSchema(SyncStringOnly::class.java) + .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) + .build() + Realm.getInstance(configOld).use { realm -> + realm.executeTransaction { realm -> + for (i in 0..9) { + realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" + } + } + realm.syncSession.uploadAllLocalChanges() + } + user.logOut() + + assertFailsWith { + app.sync.getSession(configOld) + } + + // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with + // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). + // Use different user to trigger different path + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), password) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user2, user.id) + .testSchema(SyncStringOnly::class.java) + .waitForInitialRemoteData() + .build() + + assertFalse(config.testRealmExists()) + + val countDownLatch = CountDownLatch(2) + + val indefiniteListenerComplete = AtomicBoolean(false) + val currentChangesListenerComplete = AtomicBoolean(false) + val task = Realm.getInstanceAsync(config, object : Realm.Callback() { + override fun onSuccess(realm: Realm) { + realm.syncSession.addDownloadProgressListener(ProgressMode.INDEFINITELY, object : ProgressListener { + override fun onChange(progress: Progress) { + if (progress.isTransferComplete()) { + indefiniteListenerComplete.set(true) + countDownLatch.countDown() + } + } + }) + realm.syncSession.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, object : ProgressListener { + override fun onChange(progress: Progress) { + if (progress.isTransferComplete()) { + currentChangesListenerComplete.set(true) + countDownLatch.countDown() + } + } + }) + countDownLatch.await(100, TimeUnit.SECONDS) + realm.close() + if (!indefiniteListenerComplete.get()) { + fail("Indefinite progress listener did not report complete.") + } + if (!currentChangesListenerComplete.get()) { + fail("Current changes progress listener did not report complete.") + } + looperThread.testComplete() + } + + override fun onError(exception: Throwable) { + fail(exception.toString()) + } + }) + looperThread.keepStrongReference(task) + } + @Test fun uploadListener_keepIncreasingInSize() { val config = createSyncConfig() diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index 41c27d8cf2..b66cec36cc 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -16,10 +16,12 @@ package io.realm import android.os.SystemClock +import android.util.Log import androidx.test.annotation.UiThreadTest import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.entities.DefaultSyncSchema +import io.realm.entities.SyncDog import io.realm.entities.SyncStringOnly import io.realm.exceptions.DownloadingRealmInterruptedException import io.realm.exceptions.RealmMigrationNeededException @@ -35,16 +37,17 @@ import io.realm.util.assertFailsWithErrorCode import org.bson.BsonObjectId import org.bson.types.ObjectId import org.junit.* +import org.junit.Assert.* import org.junit.runner.RunWith import java.io.File import java.util.* +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean +import kotlin.test.assertEquals import kotlin.test.assertFailsWith private const val SECRET_PASSWORD = "123456" -private const val CUSTOM_HEADER_NAME = "Foo" -private const val CUSTOM_HEADER_VALUE = "bar" -private const val AUTH_HEADER_NAME = "RealmAuth" /** * Catch all class for tests that not naturally fit anywhere else. @@ -97,14 +100,14 @@ class SyncedRealmIntegrationTests { user.logOut() } try { - Assert.assertTrue(Realm.deleteRealm(config)) + assertTrue(Realm.deleteRealm(config)) } catch (e: IllegalStateException) { // FIXME: We don't have a way to ensure that the Realm instance on client thread has been // closed for now https://github.com/realm/realm-java/issues/5416 if (e.message!!.contains("It's not allowed to delete the file")) { // retry after 1 second SystemClock.sleep(1000) - Assert.assertTrue(Realm.deleteRealm(config)) + assertTrue(Realm.deleteRealm(config)) } } @@ -116,7 +119,7 @@ class SyncedRealmIntegrationTests { Realm.getInstance(config2).use { realm -> realm.syncSession.downloadAllServerChanges() realm.refresh() - Assert.assertEquals(1, realm.where(SyncStringOnly::class.java).count()) + assertEquals(1, realm.where(SyncStringOnly::class.java).count()) } looperThread.testComplete() } @@ -152,12 +155,9 @@ class SyncedRealmIntegrationTests { // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - // FIXME Is this sufficient for testing. I guess we use the same local file when we cannot - // set the name - user = app.login(Credentials.emailPassword(user.email, SECRET_PASSWORD)) - val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) - // FIXME How does this work for sync? - // .name("newRealm") + // Use different user to trigger different path + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user2, user.id) .testSchema(SyncStringOnly::class.java) .waitForInitialRemoteData() .build() @@ -167,7 +167,7 @@ class SyncedRealmIntegrationTests { realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo 1$i" } } - Assert.assertEquals(20, realm.where(SyncStringOnly::class.java).count()) + assertEquals(20, realm.where(SyncStringOnly::class.java).count()) } } @@ -188,7 +188,11 @@ class SyncedRealmIntegrationTests { Thread.currentThread().interrupt() Realm.getInstance(config).close() } - Assert.assertFalse(File(config.getPath()).exists()) + // FIXME Seems like the file is actually created before interrupted. Is this check + // correct? + assertFalse(File(config.getPath()).exists()) + // FIXME This can throw IllegalStateException as the realm is maybe not closed + // properly due to https://github.com/realm/realm-java/issues/5416 Realm.deleteRealm(config) }) t.start() @@ -206,24 +210,25 @@ class SyncedRealmIntegrationTests { fun waitForInitialData_resilientInCaseOfRetriesAsync() = looperThread.runBlocking { val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - // FIXME Is this important for the test - //.directory(configurationFactory.getRoot()) .waitForInitialRemoteData() .build() val randomizer = Random() for (i in 0..9) { val task = Realm.getInstanceAsync(config, object : Realm.Callback() { - override fun onSuccess(realm: Realm) { Assert.fail() } - override fun onError(exception: Throwable) { Assert.fail(exception.toString()) } + override fun onSuccess(realm: Realm) { fail() } + override fun onError(exception: Throwable) { fail(exception.toString()) } }) SystemClock.sleep(randomizer.nextInt(5).toLong()) task.cancel() } - looperThread.testComplete() + // Leave some time for the async callbacks to actually get through + looperThread.postRunnableDelayed( + Runnable { looperThread.testComplete() }, + 1000 + ) } @Test - // FIXME Investigate fun waitForInitialRemoteData_readOnlyTrue() { // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) @@ -241,36 +246,34 @@ class SyncedRealmIntegrationTests { // 2. Local state should now be completely reset. Open the Realm again with a new configuration which should // download the uploaded changes (pray it managed to do so within the time frame). - // FIXME Is this sufficient for test. Guess we need a separate file to be able to test it!? - user = app.login(Credentials.emailPassword(user.email, SECRET_PASSWORD)) - val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) - // FIXME IS this essential for tests - // .name("newRealm") + // Use different user to trigger different path + val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) + val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user2, user.id) .waitForInitialRemoteData() .readOnly() .testSchema(SyncStringOnly::class.java) .build() - // FIXME This assert fails...due to commented .name above - Assert.assertFalse(configNew.testRealmExists()) + assertFalse(configNew.testRealmExists()) Realm.getInstance(configNew).use { realm -> - Assert.assertEquals(10, realm.where(SyncStringOnly::class.java).count()) + assertEquals(10, realm.where(SyncStringOnly::class.java).count()) } user.logOut() } @Test - // FIXME Investigate + // FIXME + @Ignore("Not really sure how to do this test with new sync, but isn't it covered by the " + + "SyncedRealmMigrationTests?") fun waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() - .readOnly() .testSchema(SyncStringOnly::class.java) .build() - Assert.assertFalse(configNew.testRealmExists()) + assertFalse(configNew.testRealmExists()) assertFailsWith { // This will fail, because the server Realm is completely empty and the Client is not allowed to write the // schema. - // FIXME Does not throw. Has something changed around this + // FIXME Does not throw. How to test schema migration with new sync when server is in dev mode Realm.getInstance(configNew).close() } user.logOut() @@ -283,9 +286,9 @@ class SyncedRealmIntegrationTests { .testSchema(SyncStringOnly::class.java) // This schema should be written when opening the empty Realm. .schemaVersion(2) .build() - Assert.assertFalse(config.testRealmExists()) + assertFalse(config.testRealmExists()) Realm.getInstance(config).use { realm -> - Assert.assertEquals(0, realm.where(SyncStringOnly::class.java).count()) + assertEquals(0, realm.where(SyncStringOnly::class.java).count()) } user.logOut() } @@ -296,144 +299,10 @@ class SyncedRealmIntegrationTests { Realm.getInstance(config).use { realm -> realm.syncSession.downloadAllServerChanges() realm.refresh() - Assert.assertTrue(realm.isEmpty) + assertTrue(realm.isEmpty) } user.logOut() } - // Check that custom headers and auth header renames are correctly used for HTTP requests - // performed from Java. - // FIXME Move to AppConfigurationTestt - @Test - fun javaRequestCustomHeaders() { - looperThread.runBlocking { - // FIXME Hack to overcome that we cannot have multiple apps and needs to adjust - // configuration of TestApp for this test. Would not be required in AppConfigurationTest - app.close() - Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) - val app = TestApp(builder = { builder -> - builder.addCustomRequestHeader(CUSTOM_HEADER_NAME, CUSTOM_HEADER_VALUE) - builder.authorizationHeaderName(AUTH_HEADER_NAME) - }) - runJavaRequestCustomHeadersTest(app) - } - } - - // FIXME Seems to be outdated...cannot find an option for setting headers for a specific host -// // Check that custom headers and auth header renames are correctly used for HTTP requests -// // performed from Java. -// @Test -// @RunTestInLooperThread -// fun javaRequestCustomHeaders_specificHost() { -// SyncManager.addCustomRequestHeader("Foo", "bar", Constants.HOST) -// SyncManager.setAuthorizationHeaderName("RealmAuth", Constants.HOST) -// runJavaRequestCustomHeadersTest() -// } - - private fun runJavaRequestCustomHeadersTest(app: App) { - val username = UUID.randomUUID().toString() - val password = "password" - val headerSet = AtomicBoolean(false) - - // Setup logger to inspect that we get a log message with the custom headers - RealmLog.setLevel(LogLevel.ALL) - val logger = RealmLogger { level: Int, tag: String?, throwable: Throwable?, message: String? -> - if (level > LogLevel.TRACE && message!!.contains(CUSTOM_HEADER_NAME) && message.contains(CUSTOM_HEADER_VALUE) - && message.contains("RealmAuth: ")) { - headerSet.set(true) - } - } - RealmLog.add(logger) - assertFailsWithErrorCode(ErrorCode.SERVICE_UNKNOWN) { - app.registerUserAndLogin(username, password) - } - // FIXME Guess it would be better to reset logger on Realm.init, but not sure of impact - // ...or is the logger intentionally shared to enable full trace of a full test run? - RealmLog.remove(logger) - - Assert.assertTrue(headerSet.get()) - looperThread.testComplete() - } - - @Test - // FIXME Investigate - fun progressListenersWorkWhenUsingWaitForInitialRemoteData() = looperThread.runBlocking { - val username = UUID.randomUUID().toString() - val password = "password" - var user: User = app.registerUserAndLogin(username, password) - - // 1. Copy a valid Realm to the server (and pray it does it within 10 seconds) - val configOld: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) - .testSchema(SyncStringOnly::class.java) - .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) - .build() - Realm.getInstance(configOld).use { realm -> - realm.executeTransaction { realm -> - for (i in 0..9) { - realm.createObject(SyncStringOnly::class.java, ObjectId()).chars = "Foo$i" - } - } - realm.syncSession.uploadAllLocalChanges() - } - user.logOut() - - // FIXME Is this equivalent to the old - // Assert.assertTrue(SyncManager.getAllSessions(user).isEmpty()) - assertFailsWith { - app.sync.getSession(configOld) - } - - // 2. Local state should now be completely reset. Open the same sync Realm but different local name again with - // a new configuration which should download the uploaded changes (pray it managed to do so within the time frame). - // FIXME This whole part is maybe not applicable for new sync. Maybe use a new users to - // force using a new file?? - user = app.login(Credentials.emailPassword(username, password)) - val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) - // FIXME Is this essential for tests - // .name("newRealm") - .testSchema(SyncStringOnly::class.java) - // FIXME I guess we should not wair for initial remote data, as we want to test progress listeners - // .waitForInitialRemoteData() - .build() - - // FIXME Reintroduce? - // Assert.assertFalse(config.testRealmExists()) - - val indefiniteListenerComplete = AtomicBoolean(false) - val currentChangesListenerComplete = AtomicBoolean(false) - val task = Realm.getInstanceAsync(config, object : Realm.Callback() { - override fun onSuccess(realm: Realm) { - // FIXME Is it acceptable to register the listeners here as we cannot access the - // session before the Realm is opened? - realm.syncSession.addDownloadProgressListener(ProgressMode.INDEFINITELY, object : ProgressListener { - override fun onChange(progress: Progress) { - if (progress.isTransferComplete()) { - indefiniteListenerComplete.set(true) - } - } - }) - realm.syncSession.addDownloadProgressListener(ProgressMode.CURRENT_CHANGES, object : ProgressListener { - override fun onChange(progress: Progress) { - if (progress.isTransferComplete()) { - currentChangesListenerComplete.set(true) - } - } - }) - realm.close() - if (!indefiniteListenerComplete.get()) { - Assert.fail("Indefinite progress listener did not report complete.") - } - if (!currentChangesListenerComplete.get()) { - Assert.fail("Current changes progress listener did not report complete.") - } - looperThread.testComplete() - } - - override fun onError(exception: Throwable) { - Assert.fail(exception.toString()) - } - }) - looperThread.keepStrongReference(task) - } } From edba5df6d179e03470ae3561d695ba2771e93abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Fri, 7 Aug 2020 15:18:25 +0200 Subject: [PATCH 05/15] Enable readOnly in read only test --- .../kotlin/io/realm/SyncedRealmIntegrationTests.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index b66cec36cc..bc5ccb829c 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -262,11 +262,11 @@ class SyncedRealmIntegrationTests { @Test // FIXME - @Ignore("Not really sure how to do this test with new sync, but isn't it covered by the " + - "SyncedRealmMigrationTests?") + @Ignore("Not really sure how to do this test with new sync") fun waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() + .readOnly() .testSchema(SyncStringOnly::class.java) .build() assertFalse(configNew.testRealmExists()) From 88c0427df19e0686901a2f996530afc2f6ea820a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 31 Aug 2020 16:08:21 +0200 Subject: [PATCH 06/15] Add missing imports after merge --- .../kotlin/io/realm/AppConfigurationTests.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt index 5285f2458e..2681e4e282 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt @@ -18,9 +18,14 @@ package io.realm import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.internal.network.LoggingInterceptor.LOGIN_FEATURE -import io.realm.mongodb.AppConfiguration +import io.realm.log.LogLevel +import io.realm.log.RealmLog +import io.realm.log.RealmLogger +import io.realm.mongodb.* import io.realm.mongodb.log.obfuscator.HttpLogObfuscator import io.realm.mongodb.sync.SyncSession +import io.realm.rule.BlockingLooperThread +import io.realm.util.assertFailsWithErrorCode import org.bson.codecs.StringCodec import org.bson.codecs.configuration.CodecRegistries import org.junit.Assert.* @@ -31,7 +36,10 @@ import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import java.io.File import java.net.URL +import java.util.* import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.collections.LinkedHashMap import kotlin.test.assertFailsWith import kotlin.test.assertNull From a33af43300576882ed2345227b9eafa31eb665db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 31 Aug 2020 16:13:06 +0200 Subject: [PATCH 07/15] Fix ignored SyncedRealmIntegrationTests --- .../kotlin/io/realm/AppConfigurationTests.kt | 2 - .../kotlin/io/realm/ProgressListenerTests.kt | 3 +- .../io/realm/entities/SyncSchemeMigration.kt | 33 ++++++++++++++ .../kotlin/io/realm/SyncSessionTests.kt | 25 ----------- .../io/realm/SyncedRealmIntegrationTests.kt | 43 +++++++++---------- 5 files changed, 55 insertions(+), 51 deletions(-) create mode 100644 realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSchemeMigration.kt diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt index 2681e4e282..035076f33c 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppConfigurationTests.kt @@ -350,8 +350,6 @@ class AppConfigurationTests { assertFailsWithErrorCode(ErrorCode.SERVICE_UNKNOWN) { app.registerUserAndLogin(username, password) } - // FIXME Guess it would be better to reset logger on Realm.init, but not sure of impact - // ...or is the logger intentionally shared to enable full trace of a full test run? RealmLog.remove(logger) RealmLog.setLevel(level) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt index 74dbac4633..e12d281e43 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/ProgressListenerTests.kt @@ -284,8 +284,6 @@ class ProgressListenerTests { } @Test - // For some reason this does not work, even though the following - // progressListenersWorkWhenUsingWaitForInitialRemoteData works @Ignore("FIXME: Tracked by https://github.com/realm/realm-java/issues/6976") fun addProgressListener_triggerImmediatelyWhenRegistered_waitForInitialRemoteData() { val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") @@ -303,6 +301,7 @@ class ProgressListenerTests { } @Test + @Ignore("FIXME: Flacky: Tracked by https://github.com/realm/realm-java/issues/6976") fun progressListenersWorkWhenUsingWaitForInitialRemoteData() = looperThread.runBlocking { val username = UUID.randomUUID().toString() val password = "password" diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSchemeMigration.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSchemeMigration.kt new file mode 100644 index 0000000000..018288a99c --- /dev/null +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/entities/SyncSchemeMigration.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Realm Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.realm.entities + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import io.realm.annotations.RealmField +import org.bson.types.ObjectId + +open class SyncSchemeMigration : RealmObject() { + + companion object { + const val CLASS_NAME = "SyncSchemeMigration" + } + + @PrimaryKey + @RealmField(name = "_id") + var id = ObjectId() + +} diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index 4336a8df28..149c04c3b8 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -191,28 +191,6 @@ class SyncSessionTests { } } - @Test - // FIXME Differentiate path for Realms with different partition values - @Ignore("Partition value does not generate different paths") - fun differentPathsForDifferentPartitionValues() { - val syncConfiguration1 = configFactory - .createSyncConfigurationBuilder(user, BsonString("partitionvalue1")) - .modules(DefaultSyncSchema()) - - .build() - val syncConfiguration2 = configFactory - .createSyncConfigurationBuilder(user, BsonString("partitionvalue2")) - .modules(DefaultSyncSchema()) - - .build() - Realm.getInstance(syncConfiguration1).use { realm1 -> - Realm.getInstance(syncConfiguration2).use { realm2 -> - assertNotEquals(realm1, realm2) - assertNotEquals(realm1.path, realm2.path) - } - } - } - @Test(timeout = 3000) fun getState_active() { Realm.getInstance(syncConfiguration).use { realm -> @@ -401,8 +379,6 @@ class SyncSessionTests { // check that logging out a SyncUser used by different Realm will // affect all associated sessions. @Test(timeout = 5000) - // FIXME Differentiate path for Realms with different partition values, see differentPathsForDifferentPartitionValues - @Ignore("Partition value does not generate different paths") fun logout_sameSyncUserMultipleSessions() { Realm.getInstance(syncConfiguration).use { realm1 -> // New partitionValue to differentiate sync session @@ -435,7 +411,6 @@ class SyncSessionTests { app.login(Credentials.emailPassword(user.email!!, SECRET_PASSWORD)) // reviving the sessions. The state could be changed concurrently. - // FIXME Reavaluate with new sync states assertTrue( //session1.state == SyncSession.State.WAITING_FOR_ACCESS_TOKEN || session1.state == SyncSession.State.ACTIVE) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index f03f8bf7cf..1c26121507 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.realm.entities.DefaultSyncSchema import io.realm.entities.StringOnly +import io.realm.entities.SyncSchemeMigration import io.realm.entities.SyncStringOnly import io.realm.exceptions.DownloadingRealmInterruptedException import io.realm.exceptions.RealmMigrationNeededException @@ -100,7 +101,7 @@ class SyncedRealmIntegrationTests { try { assertTrue(Realm.deleteRealm(config)) } catch (e: IllegalStateException) { - // FIXME: We don't have a way to ensure that the Realm instance on client thread has been + // TODO: We don't have a way to ensure that the Realm instance on client thread has been // closed for now https://github.com/realm/realm-java/issues/5416 if (e.message!!.contains("It's not allowed to delete the file")) { // retry after 1 second @@ -109,7 +110,6 @@ class SyncedRealmIntegrationTests { } } - // FIXME Is this sufficient to test "loginLogoutResumeSynching"-case user = app.login(Credentials.emailPassword(user.email, SECRET_PASSWORD)) val config2: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .testSchema(SyncStringOnly::class.java) @@ -173,8 +173,6 @@ class SyncedRealmIntegrationTests { // We cannot do much better since we cannot control the order of events internally in Realm which would be // needed to correctly test all error paths. @Test - @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + - " https://github.com/realm/realm-java/issues/5416") fun waitForInitialData_resilientInCaseOfRetries() { val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() @@ -186,12 +184,17 @@ class SyncedRealmIntegrationTests { Thread.currentThread().interrupt() Realm.getInstance(config).close() } - // FIXME Seems like the file is actually created before interrupted. Is this check - // correct? - assertFalse(File(config.getPath()).exists()) - // FIXME This can throw IllegalStateException as the realm is maybe not closed - // properly due to https://github.com/realm/realm-java/issues/5416 - Realm.deleteRealm(config) + // TODO: We don't have a way to ensure that the Realm instance on client thread has been + // closed for now https://github.com/realm/realm-java/issues/5416 + try { + Realm.deleteRealm(config) + } catch (e: IllegalStateException) { + if (e.message!!.contains("It's not allowed to delete the file")) { + // retry after 1 second + SystemClock.sleep(1000) + assertTrue(Realm.deleteRealm(config)) + } + } }) t.start() t.join() @@ -202,9 +205,6 @@ class SyncedRealmIntegrationTests { // We cannot do much better since we cannot control the order of events internally in Realm which would be // needed to correctly test all error paths. @Test - // FIXME This does not throw anymore as described in issue. But do the test still make sense - // with new sync? - //@Ignore("See https://github.com/realm/realm-java/issues/5373") fun waitForInitialData_resilientInCaseOfRetriesAsync() = looperThread.runBlocking { val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) @@ -259,20 +259,19 @@ class SyncedRealmIntegrationTests { } @Test - // FIXME - @Ignore("Not really sure how to do this test with new sync") fun waitForInitialRemoteData_readOnlyTrue_throwsIfWrongServerSchema() { val configNew: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() .readOnly() - .testSchema(SyncStringOnly::class.java) + .testSchema(SyncSchemeMigration::class.java) .build() assertFalse(configNew.testRealmExists()) assertFailsWith { - // This will fail, because the server Realm is completely empty and the Client is not allowed to write the - // schema. - // FIXME Does not throw. How to test schema migration with new sync when server is in dev mode - Realm.getInstance(configNew).close() + Realm.getInstance(configNew).use { realm -> + realm.executeTransaction { + it.createObject(SyncSchemeMigration::class.java, ObjectId()) + } + } } user.logOut() } @@ -306,14 +305,14 @@ class SyncedRealmIntegrationTests { // Smoke test to check that `refreshConnections` doesn't crash. // Testing that it actually works is not feasible in a unit test. @Test - fun refreshConnections() { + fun refreshConnections() = looperThread.runBlocking { RealmLog.setLevel(LogLevel.DEBUG) Sync.refreshConnections() // No Realms // A single active Realm val username = UUID.randomUUID().toString() val password = "password" - val user: User = app.login(Credentials.emailPassword(username, password)) + val user: User = app.registerUserAndLogin(username, password) val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) .testSchema(StringOnly::class.java) .build() From 807b8fe084ae2715c471c20e27151a76285b5e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 31 Aug 2020 23:40:35 +0200 Subject: [PATCH 08/15] Fix ignored SyncSessionTests --- .../kotlin/io/realm/SyncSessionTests.kt | 52 ++++++++----------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt index 149c04c3b8..7efe966c3d 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncSessionTests.kt @@ -423,12 +423,9 @@ class SyncSessionTests { // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. @Test - // FIXME Investigate further - // FIXME Rewrite to use BlockingLooperThread - @Ignore("Re-logging in does not authorize") fun logBackResumeUpload() { val config1 = configFactory - .createSyncConfigurationBuilder(user) + .createSyncConfigurationBuilder(user, UUID.randomUUID().toString()) .modules(SyncStringOnlyModule()) .waitForInitialRemoteData() .build() @@ -448,11 +445,9 @@ class SyncSessionTests { val allResults = AtomicReference>() // notifier could be GC'ed before it get a chance to trigger the second commit, so declaring it outside the Runnable handler.post { // access the Realm from an different path on the device (using admin user), then monitor // when the offline commits get synchronized - // FIXME Do we somehow need to extract the refreshtoken...and could it be the reason for app.login not working later on val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) .modules(SyncStringOnlyModule()) - .waitForInitialRemoteData() .build() val realm2 = Realm.getInstance(config2) @@ -489,15 +484,13 @@ class SyncSessionTests { // A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in. // this test validate the behaviour of SyncSessionStopPolicy::AfterChangesUploaded @Test - // FIXME Investigate why it does not terminate...probably rewrite to BlockingLooperThread - @Ignore("Does not terminate") - fun uploadChangesWhenRealmOutOfScope() { + fun uploadChangesWhenRealmOutOfScope() = looperThread.runBlocking { val strongRefs: MutableList = ArrayList() val chars = CharArray(1000000) // 2MB Arrays.fill(chars, '.') val twoMBString = String(chars) val config1 = configFactory - .createSyncConfigurationBuilder(user) + .createSyncConfigurationBuilder(user, UUID.randomUUID().toString()) .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.AFTER_CHANGES_UPLOADED) .modules(SyncStringOnlyModule()) .build() @@ -520,40 +513,37 @@ class SyncSessionTests { val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) .modules(SyncStringOnlyModule()) .build() - Realm.getInstance(config2).use { realm2 -> - val all = realm2.where(SyncStringOnly::class.java).findAll() - if (all.size == 5) { - realm2.close() - testCompleted.countDown() - handlerThread.quit() - } else { - strongRefs.add(all) - val realmChangeListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet? -> - if (results.size == 5) { - realm2.close() - testCompleted.countDown() - handlerThread.quit() - } + val realm2 = Realm.getInstance(config2) + val all = realm2.where(SyncStringOnly::class.java).findAll() + if (all.size == 5) { + realm2.close() + testCompleted.countDown() + handlerThread.quit() + } else { + strongRefs.add(all) + val realmChangeListener = OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet? -> + if (results.size == 5) { + realm2.close() + testCompleted.countDown() + handlerThread.quit() } - all.addChangeListener(realmChangeListener) } + all.addChangeListener(realmChangeListener) } - handlerThread.quit() } TestHelper.awaitOrFail(testCompleted, TestHelper.STANDARD_WAIT_SECS) handlerThread.join() user.logOut() + looperThread.testComplete() } // A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in. @Test - // FIXME Investigate why it does not terminate...probably rewrite to BlockingLooperThread - @Ignore("Does not terminate") fun downloadChangesWhenRealmOutOfScope() { val uniqueName = UUID.randomUUID().toString() app.emailPasswordAuth.registerUser(uniqueName, "password") val config1 = configFactory - .createSyncConfigurationBuilder(user) + .createSyncConfigurationBuilder(user, UUID.randomUUID().toString()) .modules(SyncStringOnlyModule()) .build() Realm.getInstance(config1).use { realm -> @@ -570,13 +560,13 @@ class SyncSessionTests { val credentials = Credentials.emailPassword(user.email!!, SECRET_PASSWORD) app.login(credentials) - // now let the admin upload some commits + // Write updates from a different user val backgroundUpload = CountDownLatch(1) val handlerThread = HandlerThread("HandlerThread") handlerThread.start() val looper = handlerThread.looper val handler = Handler(looper) - handler.post { // using an admin user to open the Realm on different path on the device then some commits + handler.post { // Using a different user to open the Realm on different path on the device then some commits val user2 = app.registerUserAndLogin(TestHelper.getRandomEmail(), SECRET_PASSWORD) val config2: SyncConfiguration = configFactory.createSyncConfigurationBuilder(user2, config1.partitionValue) .modules(SyncStringOnlyModule()) From 68c99ba1e44b67136a2e0ddf80409054c79d2a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 1 Sep 2020 00:16:39 +0200 Subject: [PATCH 09/15] Expose OS sync session's shutdown_and_wait for test --- .../kotlin/io/realm/mongodb/sync/SyncSessionExt.kt | 4 ++++ .../main/cpp/io_realm_mongodb_sync_SyncSession.cpp | 11 +++++++++++ .../java/io/realm/mongodb/sync/SyncSession.java | 5 +++++ .../kotlin/io/realm/SyncedRealmIntegrationTests.kt | 11 ++++++----- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncSessionExt.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncSessionExt.kt index 872d230255..399a9f9a6a 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncSessionExt.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/mongodb/sync/SyncSessionExt.kt @@ -20,3 +20,7 @@ package io.realm.mongodb.sync fun SyncSession.testClose() { this.close() } + +fun SyncSession.testShutdownAndWait() { + this.shutdownAndWait() +} diff --git a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp index e814e8ebc3..71892e677e 100644 --- a/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp +++ b/realm/realm-library/src/main/cpp/io_realm_mongodb_sync_SyncSession.cpp @@ -323,3 +323,14 @@ JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeStop(JNIEnv* } CATCH_STD() } + +JNIEXPORT void JNICALL Java_io_realm_mongodb_sync_SyncSession_nativeShutdownAndWait (JNIEnv* env, jclass, jstring j_local_realm_path) { + try { + JStringAccessor local_realm_path(env, j_local_realm_path); + auto session = SyncManager::shared().get_existing_session(local_realm_path); + if (session) { + session->shutdown_and_wait(); + } + } + CATCH_STD() +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java index 4e1e66c3ad..695edeb8aa 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/mongodb/sync/SyncSession.java @@ -635,6 +635,10 @@ private void checkTimeout(long timeout, TimeUnit unit) { } } + void shutdownAndWait() { + nativeShutdownAndWait(configuration.getPath()); + } + /** * Interface used to report any session errors. * @@ -758,4 +762,5 @@ public void throwExceptionIfNeeded() { private static native byte nativeGetConnectionState(String localRealmPath); private static native void nativeStart(String localRealmPath); private static native void nativeStop(String localRealmPath); + private static native void nativeShutdownAndWait(String localRealmPath); } diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index 1c26121507..45d393b85a 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -186,15 +186,16 @@ class SyncedRealmIntegrationTests { } // TODO: We don't have a way to ensure that the Realm instance on client thread has been // closed for now https://github.com/realm/realm-java/issues/5416 + app.sync.getSession(config).testShutdownAndWait() try { Realm.deleteRealm(config) } catch (e: IllegalStateException) { - if (e.message!!.contains("It's not allowed to delete the file")) { - // retry after 1 second - SystemClock.sleep(1000) - assertTrue(Realm.deleteRealm(config)) + if (e.message!!.contains("It's not allowed to delete the file")) { + // retry after 1 second + SystemClock.sleep(1000) + assertTrue(Realm.deleteRealm(config)) + } } - } }) t.start() t.join() From d6a05d4557fd3ff71fea08e6acca384fbf9382e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 1 Sep 2020 08:55:55 +0200 Subject: [PATCH 10/15] Ignored flaky waitForInitialData_resilientInCaseOfRetries test --- .../kotlin/io/realm/SyncedRealmIntegrationTests.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index 45d393b85a..6692706494 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -173,6 +173,8 @@ class SyncedRealmIntegrationTests { // We cannot do much better since we cannot control the order of events internally in Realm which would be // needed to correctly test all error paths. @Test + @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + + " https://github.com/realm/realm-java/issues/5416") fun waitForInitialData_resilientInCaseOfRetries() { val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() From b677dccd86503c682f938f93178bbfc04526b033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 8 Sep 2020 08:50:08 +0200 Subject: [PATCH 11/15] Avoid native threads in SyncedRealmIntegrationTests --- .../kotlin/io/realm/SyncedRealmIntegrationTests.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index 6692706494..e4328a6ed3 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -175,12 +175,13 @@ class SyncedRealmIntegrationTests { @Test @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + " https://github.com/realm/realm-java/issues/5416") - fun waitForInitialData_resilientInCaseOfRetries() { + fun waitForInitialData_resilientInCaseOfRetries() = looperThread.runBlocking { val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() .build() for (i in 0..9) { - val t = Thread(Runnable { + val blockingLooperThread = BlockingLooperThread() + blockingLooperThread.runDetached { var realm: Realm? = null assertFailsWith { Thread.currentThread().interrupt() @@ -198,10 +199,10 @@ class SyncedRealmIntegrationTests { assertTrue(Realm.deleteRealm(config)) } } - }) - t.start() - t.join() + blockingLooperThread.testComplete() + }.await() } + looperThread.testComplete() } // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient towards that From 697482ad5ad7aaad6fbb0cf45a359fde05b8df41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 8 Sep 2020 08:53:28 +0200 Subject: [PATCH 12/15] Include previously ignored test --- .../kotlin/io/realm/SyncedRealmIntegrationTests.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index e4328a6ed3..9f482b047f 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -173,8 +173,6 @@ class SyncedRealmIntegrationTests { // We cannot do much better since we cannot control the order of events internally in Realm which would be // needed to correctly test all error paths. @Test - @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + - " https://github.com/realm/realm-java/issues/5416") fun waitForInitialData_resilientInCaseOfRetries() = looperThread.runBlocking { val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() From 898168f8bfcc8713cf1eb7d687b023ddebae110c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 8 Sep 2020 08:57:53 +0200 Subject: [PATCH 13/15] Remove irrelevant FIXME --- .../kotlin/io/realm/TestSyncConfigurationFactory.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt index ed9b6fa2ee..f098e1228f 100644 --- a/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt +++ b/realm/realm-library/src/syncTestUtils/kotlin/io/realm/TestSyncConfigurationFactory.kt @@ -30,8 +30,6 @@ import io.realm.rule.TestRealmConfigurationFactory * test ends. */ class TestSyncConfigurationFactory : TestRealmConfigurationFactory() { - - // FIXME Wouldn't it make more sense to default to user.id or something like that fun createSyncConfigurationBuilder(user: User?): SyncConfiguration.Builder { return SyncConfiguration.Builder(user, "default") .testSessionStopPolicy(OsRealmConfig.SyncSessionStopPolicy.IMMEDIATELY) From 2ea96d961fcc0a60b8a2290cce52d8cee20bd941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 8 Sep 2020 10:26:55 +0200 Subject: [PATCH 14/15] Reignoring flaky SyncedRealmIntegrationTest --- .../kotlin/io/realm/SyncedRealmIntegrationTests.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index 9f482b047f..6b529c7a1f 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -173,6 +173,8 @@ class SyncedRealmIntegrationTests { // We cannot do much better since we cannot control the order of events internally in Realm which would be // needed to correctly test all error paths. @Test + @Ignore("Sync somehow keeps a Realm alive, causing the Realm.deleteRealm to throw " + + " https://github.com/realm/realm-java/issues/5416") fun waitForInitialData_resilientInCaseOfRetries() = looperThread.runBlocking { val config: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .waitForInitialRemoteData() @@ -181,9 +183,10 @@ class SyncedRealmIntegrationTests { val blockingLooperThread = BlockingLooperThread() blockingLooperThread.runDetached { var realm: Realm? = null - assertFailsWith { + try { Thread.currentThread().interrupt() Realm.getInstance(config).close() + } catch (e: DownloadingRealmInterruptedException) { } // TODO: We don't have a way to ensure that the Realm instance on client thread has been // closed for now https://github.com/realm/realm-java/issues/5416 From dc0e72032739020a2adc6f1c1f21b4c9af2685a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Tue, 15 Sep 2020 12:33:42 +0200 Subject: [PATCH 15/15] Fix after merge --- .../kotlin/io/realm/SyncedRealmIntegrationTests.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt index 6b529c7a1f..b084b35b2e 100644 --- a/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt +++ b/realm/realm-library/src/syncIntegrationTest/kotlin/io/realm/SyncedRealmIntegrationTests.kt @@ -110,7 +110,7 @@ class SyncedRealmIntegrationTests { } } - user = app.login(Credentials.emailPassword(user.email, SECRET_PASSWORD)) + user = app.login(Credentials.emailPassword(user.profile.email, SECRET_PASSWORD)) val config2: SyncConfiguration = configurationFactory.createSyncConfigurationBuilder(user, user.id) .testSchema(SyncStringOnly::class.java) .build() @@ -312,7 +312,7 @@ class SyncedRealmIntegrationTests { @Test fun refreshConnections() = looperThread.runBlocking { RealmLog.setLevel(LogLevel.DEBUG) - Sync.refreshConnections() // No Realms + Sync.reconnect() // No Realms // A single active Realm val username = UUID.randomUUID().toString() @@ -322,11 +322,11 @@ class SyncedRealmIntegrationTests { .testSchema(StringOnly::class.java) .build() val realm = Realm.getInstance(config) - Sync.refreshConnections() + Sync.reconnect() // A single logged out Realm realm.close() - Sync.refreshConnections() + Sync.reconnect() looperThread.testComplete() }