/*
* 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.objectserver;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.test.annotation.UiThreadTest;
import android.support.test.rule.UiThreadTestRule;
import org.junit.Rule;
import org.junit.Test;
import java.io.File;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import io.realm.Realm;
import io.realm.RealmAsyncTask;
import io.realm.SyncConfiguration;
import io.realm.SyncCredentials;
import io.realm.SyncUser;
import io.realm.TestHelper;
import io.realm.exceptions.DownloadingRealmInterruptedException;
import io.realm.objectserver.utils.Constants;
import io.realm.rule.RunInLooperThread;
import io.realm.rule.RunTestInLooperThread;
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.
*/
public class SyncedRealmTests extends BaseIntegrationTest {
@Rule
public RunInLooperThread looperThread = new RunInLooperThread();
@Rule
public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
@Test
@UiThreadTest
public void waitForInitialRemoteData_mainThreadThrows() {
final SyncUser user = loginUser();
SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM)
.waitForInitialRemoteData()
.build();
Realm realm = null;
try {
realm = Realm.getInstance(config);
fail();
} catch (IllegalStateException ignored) {
} finally {
if (realm != null) {
realm.close();
}
}
}
// Login user on a worker thread, so this method can be used from both UI and non-ui threads.
@NonNull
private SyncUser loginUser() {
final CountDownLatch userReady = new CountDownLatch(1);
final AtomicReference<SyncUser> user = new AtomicReference<>();
new Thread(new Runnable() {
@Override
public void run() {
SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true);
user.set(SyncUser.login(credentials, Constants.AUTH_URL));
userReady.countDown();
}
}).start();
TestHelper.awaitOrFail(userReady);
return user.get();
}
@Test
public void waitForInitialRemoteData() {
// TODO We can improve this test once we got Sync Progress Notifications. Right now we cannot detect
// when a Realm has been uploaded.
SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true);
SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL);
SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM)
.waitForInitialRemoteData()
.build();
Realm realm = null;
try {
realm = Realm.getInstance(config);
assertTrue(realm.isEmpty());
} finally {
if (realm != null) {
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
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 = new SyncConfiguration.Builder(user, 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
public void waitForInitialData_resilientInCaseOfRetriesAsync() {
SyncCredentials credentials = SyncCredentials.usernamePassword(UUID.randomUUID().toString(), "password", true);
SyncUser user = SyncUser.login(credentials, Constants.AUTH_URL);
final SyncConfiguration config = new SyncConfiguration.Builder(user, Constants.USER_REALM)
.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();
}
}