/* * Copyright 2014 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.content.Context; import android.os.Build; import android.os.Looper; import android.os.SystemClock; import android.support.test.InstrumentationRegistry; import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; import junit.framework.AssertionFailedError; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Random; import java.util.Scanner; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import io.realm.entities.AllJavaTypes; import io.realm.entities.AllTypes; import io.realm.entities.AllTypesPrimaryKey; import io.realm.entities.Cat; import io.realm.entities.CyclicType; import io.realm.entities.CyclicTypePrimaryKey; import io.realm.entities.DefaultValueConstructor; import io.realm.entities.DefaultValueFromOtherConstructor; import io.realm.entities.DefaultValueOfField; import io.realm.entities.DefaultValueOverwriteNullLink; import io.realm.entities.DefaultValueSetter; import io.realm.entities.Dog; import io.realm.entities.DogPrimaryKey; import io.realm.entities.NoPrimaryKeyNullTypes; import io.realm.entities.NonLatinFieldNames; import io.realm.entities.NullTypes; import io.realm.entities.Owner; import io.realm.entities.OwnerPrimaryKey; import io.realm.entities.PrimaryKeyAsBoxedByte; import io.realm.entities.PrimaryKeyAsBoxedInteger; import io.realm.entities.PrimaryKeyAsBoxedLong; import io.realm.entities.PrimaryKeyAsBoxedShort; import io.realm.entities.PrimaryKeyAsLong; import io.realm.entities.PrimaryKeyAsString; import io.realm.entities.PrimaryKeyMix; import io.realm.entities.PrimaryKeyRequiredAsBoxedByte; import io.realm.entities.PrimaryKeyRequiredAsBoxedInteger; import io.realm.entities.PrimaryKeyRequiredAsBoxedLong; import io.realm.entities.PrimaryKeyRequiredAsBoxedShort; import io.realm.entities.PrimaryKeyRequiredAsString; import io.realm.entities.RandomPrimaryKey; import io.realm.entities.StringOnly; import io.realm.entities.StringOnlyReadOnly; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.exceptions.RealmMigrationNeededException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; import io.realm.internal.SharedRealm; import io.realm.internal.Table; import io.realm.log.RealmLog; import io.realm.objectid.NullPrimaryKey; import io.realm.rule.RunInLooperThread; import io.realm.rule.RunTestInLooperThread; import io.realm.rule.TestRealmConfigurationFactory; import io.realm.util.ExceptionHolder; import io.realm.util.RealmThread; import static io.realm.TestHelper.testNoObjectFound; import static io.realm.TestHelper.testOneObjectFound; import static io.realm.internal.test.ExtraTests.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(AndroidJUnit4.class) public class RealmTests { private final static int TEST_DATA_SIZE = 10; @Rule public final UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); @Rule public final RunInLooperThread looperThread = new RunInLooperThread(); @Rule public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); @Rule public final TemporaryFolder tmpFolder = new TemporaryFolder(); @Rule public final ExpectedException thrown = ExpectedException.none(); private Context context; private Realm realm; private List<String> columnData = new ArrayList<String>(); private RealmConfiguration realmConfig; private void setColumnData() { columnData.add(0, AllTypes.FIELD_BOOLEAN); columnData.add(1, AllTypes.FIELD_DATE); columnData.add(2, AllTypes.FIELD_DOUBLE); columnData.add(3, AllTypes.FIELD_FLOAT); columnData.add(4, AllTypes.FIELD_STRING); columnData.add(5, AllTypes.FIELD_LONG); } @Before public void setUp() { // Injecting the Instrumentation instance is required // for your test to run with AndroidJUnitRunner. context = InstrumentationRegistry.getInstrumentation().getContext(); realmConfig = configFactory.createConfiguration(); realm = Realm.getInstance(realmConfig); } @After public void tearDown() { if (realm != null) { realm.close(); } } private void populateTestRealm(Realm realm, int objects) { realm.beginTransaction(); realm.deleteAll(); for (int i = 0; i < objects; ++i) { AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnBoolean((i % 3) == 0); allTypes.setColumnBinary(new byte[] {1, 2, 3}); allTypes.setColumnDate(new Date()); allTypes.setColumnDouble(Math.PI); allTypes.setColumnFloat(1.234567F + i); allTypes.setColumnString("test data " + i); allTypes.setColumnLong(i); NonLatinFieldNames nonLatinFieldNames = realm.createObject(NonLatinFieldNames.class); nonLatinFieldNames.set델타(i); nonLatinFieldNames.setΔέλτα(i); nonLatinFieldNames.set베타(1.234567F + i); nonLatinFieldNames.setΒήτα(1.234567F + i); } realm.commitTransaction(); } private void populateTestRealm() { populateTestRealm(realm, TEST_DATA_SIZE); } @Test public void getInstance_writeProtectedFile() throws IOException { String REALM_FILE = "readonly.realm"; File folder = configFactory.getRoot(); File realmFile = new File(folder, REALM_FILE); assertFalse(realmFile.exists()); assertTrue(realmFile.createNewFile()); assertTrue(realmFile.setWritable(false)); try { Realm.getInstance(new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) .directory(folder) .name(REALM_FILE) .build()); fail(); } catch (RealmFileException expected) { assertEquals(expected.getKind(), RealmFileException.Kind.PERMISSION_DENIED); } } @Test public void getInstance_writeProtectedFileWithContext() throws IOException { String REALM_FILE = "readonly.realm"; File folder = configFactory.getRoot(); File realmFile = new File(folder, REALM_FILE); assertFalse(realmFile.exists()); assertTrue(realmFile.createNewFile()); assertTrue(realmFile.setWritable(false)); try { Realm.getInstance(new RealmConfiguration.Builder(context).directory(folder).name(REALM_FILE).build()); fail(); } catch (RealmFileException expected) { assertEquals(expected.getKind(), RealmFileException.Kind.PERMISSION_DENIED); } } @Test public void getInstance_twiceWhenRxJavaUnavailable() { // Test for https://github.com/realm/realm-java/issues/2416 // Though it's not a recommended way to create multiple configuration instance with the same parameter, it's legal. final RealmConfiguration configuration1 = configFactory.createConfiguration("no_RxJava.realm"); TestHelper.emulateRxJavaUnavailable(configuration1); final RealmConfiguration configuration2 = configFactory.createConfiguration("no_RxJava.realm"); TestHelper.emulateRxJavaUnavailable(configuration2); final Realm realm1 = Realm.getInstance(configuration1); //noinspection TryFinallyCanBeTryWithResources try { final Realm realm2 = Realm.getInstance(configuration2); realm2.close(); } finally { realm1.close(); } } @Test public void checkIfValid() { // checkIfValid() must not throw any Exception against valid Realm instance. realm.checkIfValid(); realm.close(); try { realm.checkIfValid(); fail("closed Realm instance must throw IllegalStateException."); } catch (IllegalStateException ignored) { } realm = null; } @Test public void getInstance() { assertNotNull("Realm.getInstance unexpectedly returns null", realm); assertTrue("Realm.getInstance does not contain expected table", realm.getSchema().contains(AllTypes.CLASS_NAME)); } @Test public void where() { populateTestRealm(); RealmResults<AllTypes> resultList = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE, resultList.size()); } // Note that this test is relying on the values set while initializing the test dataset // TODO Move to RealmQueryTests? @Test public void where_queryResults() throws IOException { populateTestRealm(realm, 159); RealmResults<AllTypes> resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 33).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 3333).findAll(); assertEquals(0, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "test data 0").findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "test data 0", Case.INSENSITIVE).findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "Test data 0", Case.SENSITIVE).findAll(); assertEquals(0, resultList.size()); } // TODO Move to RealmQueryTests? @Test public void where_equalTo_wrongFieldTypeAsInput() throws IOException { populateTestRealm(); setColumnData(); for (int i = 0; i < columnData.size(); i++) { try { realm.where(AllTypes.class).equalTo(columnData.get(i), true).findAll(); if (i != 0) { fail("Realm.where should fail with illegal argument"); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), new Date()).findAll(); if (i != 1) { fail("Realm.where should fail with illegal argument"); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), 13.37D).findAll(); if (i != 2) { fail("Realm.where should fail with illegal argument"); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), 13.3711F).findAll(); if (i != 3) { fail("Realm.where should fail with illegal argument"); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), "test").findAll(); if (i != 4) { fail("Realm.where should fail with illegal argument"); } } catch (IllegalArgumentException ignored) { } try { realm.where(AllTypes.class).equalTo(columnData.get(i), 1337).findAll(); if (i != 5) { fail("Realm.where should fail with illegal argument"); } } catch (IllegalArgumentException ignored) { } } } // TODO Move to RealmQueryTests? @Test public void where_equalTo_invalidFieldName() throws IOException { try { realm.where(AllTypes.class).equalTo("invalidcolumnname", 33).findAll(); fail("Invalid field name"); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo("invalidcolumnname", "test").findAll(); fail("Invalid field name"); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo("invalidcolumnname", true).findAll(); fail("Invalid field name"); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo("invalidcolumnname", Math.PI).findAll(); fail("Invalid field name"); } catch (Exception ignored) { } try { realm.where(AllTypes.class).equalTo("invalidcolumnname", Math.PI).findAll(); fail("Invalid field name"); } catch (Exception ignored) { } } // TODO Move to RealmQueryTests? @Test public void where_equalTo_requiredFieldWithNullArgument() { // String try { realm.where(NullTypes.class).equalTo(NullTypes.FIELD_STRING_NOT_NULL, (String) null).findAll(); fail("Realm.where should fail with illegal argument"); } catch (IllegalArgumentException ignored) { } // Boolean try { realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BOOLEAN_NOT_NULL, (String) null).findAll(); fail("Realm.where should fail with illegal argument"); } catch (IllegalArgumentException ignored) { } // Byte try { realm.where(NullTypes.class).equalTo(NullTypes.FIELD_BYTE_NOT_NULL, (Byte) null).findAll(); fail("Realm.where should fail with illegal argument"); } catch (IllegalArgumentException ignored) { } // Short try { realm.where(NullTypes.class).equalTo(NullTypes.FIELD_SHORT_NOT_NULL, (Short) null).findAll(); fail("Realm.where should fail with illegal argument"); } catch (IllegalArgumentException ignored) { } // Integer try { realm.where(NullTypes.class).equalTo(NullTypes.FIELD_INTEGER_NOT_NULL, (Integer) null).findAll(); fail("Realm.where should fail with illegal argument"); } catch (IllegalArgumentException ignored) { } // Long try { realm.where(NullTypes.class).equalTo(NullTypes.FIELD_LONG_NOT_NULL, (Long) null).findAll(); fail("Realm.where should fail with illegal argument"); } catch (IllegalArgumentException ignored) { } // Float try { realm.where(NullTypes.class).equalTo(NullTypes.FIELD_FLOAT_NOT_NULL, (Float) null).findAll(); fail("Realm.where should fail with illegal argument"); } catch (IllegalArgumentException ignored) { } // Double try { realm.where(NullTypes.class).equalTo(NullTypes.FIELD_FLOAT_NOT_NULL, (Double) null).findAll(); fail("Realm.where should fail with illegal argument"); } catch (IllegalArgumentException ignored) { } // Date try { realm.where(NullTypes.class).equalTo(NullTypes.FIELD_DATE_NOT_NULL, (Date) null).findAll(); fail("Realm.where should fail with illegal argument"); } catch (IllegalArgumentException ignored) { } } @Test public void beginTransaction() throws IOException { populateTestRealm(); realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnFloat(3.14F); allTypes.setColumnString("a unique string"); realm.commitTransaction(); RealmResults<AllTypes> resultList = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE + 1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_STRING, "a unique string").findAll(); assertEquals(1, resultList.size()); resultList = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_FLOAT, 3.14F).findAll(); assertEquals(1, resultList.size()); } @Test public void nestedTransaction() { realm.beginTransaction(); try { realm.beginTransaction(); fail(); } catch (IllegalStateException e) { assertTrue(e.getMessage().startsWith("The Realm is already in a write transaction")); } realm.commitTransaction(); } private enum Method { METHOD_BEGIN, METHOD_COMMIT, METHOD_CANCEL, METHOD_DELETE_TYPE, METHOD_DELETE_ALL, METHOD_CREATE_OBJECT, METHOD_CREATE_OBJECT_WITH_PRIMARY_KEY, METHOD_COPY_TO_REALM, METHOD_COPY_TO_REALM_OR_UPDATE, METHOD_CREATE_ALL_FROM_JSON, METHOD_CREATE_OR_UPDATE_ALL_FROM_JSON, METHOD_CREATE_FROM_JSON, METHOD_CREATE_OR_UPDATE_FROM_JSON, METHOD_INSERT_COLLECTION, METHOD_INSERT_OBJECT, METHOD_INSERT_OR_UPDATE_COLLECTION, METHOD_INSERT_OR_UPDATE_OBJECT } // Calling methods on a wrong thread will fail. private boolean runMethodOnWrongThread(final Method method) throws InterruptedException, ExecutionException { if (method != Method.METHOD_BEGIN) { realm.beginTransaction(); realm.createObject(Dog.class); } ExecutorService executorService = Executors.newSingleThreadExecutor(); Future<Boolean> future = executorService.submit(new Callable<Boolean>() { @Override public Boolean call() throws Exception { try { switch (method) { case METHOD_BEGIN: realm.beginTransaction(); break; case METHOD_COMMIT: realm.commitTransaction(); break; case METHOD_CANCEL: realm.cancelTransaction(); break; case METHOD_DELETE_TYPE: realm.delete(AllTypes.class); break; case METHOD_DELETE_ALL: realm.deleteAll(); break; case METHOD_CREATE_OBJECT: realm.createObject(AllTypes.class); break; case METHOD_CREATE_OBJECT_WITH_PRIMARY_KEY: realm.createObject(AllJavaTypes.class, 1L); break; case METHOD_COPY_TO_REALM: realm.copyToRealm(new AllTypes()); break; case METHOD_COPY_TO_REALM_OR_UPDATE: realm.copyToRealm(new AllTypesPrimaryKey()); break; case METHOD_CREATE_ALL_FROM_JSON: realm.createAllFromJson(AllTypes.class, "[{}]"); break; case METHOD_CREATE_OR_UPDATE_ALL_FROM_JSON: realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, "[{\"columnLong\":1," + " \"columnBoolean\": true}]"); break; case METHOD_CREATE_FROM_JSON: realm.createObjectFromJson(AllTypes.class, "{}"); break; case METHOD_CREATE_OR_UPDATE_FROM_JSON: realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, "{\"columnLong\":1," + " \"columnBoolean\": true}"); break; case METHOD_INSERT_COLLECTION: realm.insert(Arrays.asList(new AllTypes(), new AllTypes())); break; case METHOD_INSERT_OBJECT: realm.insert(new AllTypes()); break; case METHOD_INSERT_OR_UPDATE_COLLECTION: realm.insert(Arrays.asList(new AllTypesPrimaryKey(), new AllTypesPrimaryKey())); break; case METHOD_INSERT_OR_UPDATE_OBJECT: realm.insertOrUpdate(new AllTypesPrimaryKey()); break; } return false; } catch (IllegalStateException ignored) { return true; } catch (RealmException jsonFailure) { // TODO: Eew. Reconsider how our JSON methods reports failure. See https://github.com/realm/realm-java/issues/1594 return (jsonFailure.getMessage().equals("Could not map Json")); } } }); boolean result = future.get(); if (method != Method.METHOD_BEGIN) { realm.cancelTransaction(); } return result; } @Test public void methodCalledOnWrongThread() throws ExecutionException, InterruptedException { for (Method method : Method.values()) { assertTrue(method.toString(), runMethodOnWrongThread(method)); } } @Test public void commitTransaction() { populateTestRealm(); realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnBoolean(true); realm.commitTransaction(); RealmResults<AllTypes> resultList = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE + 1, resultList.size()); } @Test(expected = IllegalStateException.class) public void commitTransaction_afterCancelTransaction() { realm.beginTransaction(); realm.cancelTransaction(); realm.commitTransaction(); } @Test(expected = IllegalStateException.class) public void commitTransaction_twice() { realm.beginTransaction(); realm.commitTransaction(); realm.commitTransaction(); } @Test public void cancelTransaction() { populateTestRealm(); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.cancelTransaction(); assertEquals(TEST_DATA_SIZE, realm.where(AllTypes.class).count()); try { realm.cancelTransaction(); fail(); } catch (IllegalStateException ignored) { } } @Test public void executeTransaction_null() { SharedRealm.VersionID oldVersion = realm.sharedRealm.getVersionID(); try { realm.executeTransaction(null); fail("null transaction should throw"); } catch (IllegalArgumentException ignored) { } SharedRealm.VersionID newVersion = realm.sharedRealm.getVersionID(); assertEquals(oldVersion, newVersion); } @Test public void executeTransaction_success() { assertEquals(0, realm.where(Owner.class).count()); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Owner owner = realm.createObject(Owner.class); owner.setName("Owner"); } }); assertEquals(1, realm.where(Owner.class).count()); } @Test public void executeTransaction_canceled() { final AtomicReference<RuntimeException> thrownException = new AtomicReference<RuntimeException>(null); assertEquals(0, realm.where(Owner.class).count()); try { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Owner owner = realm.createObject(Owner.class); owner.setName("Owner"); thrownException.set(new RuntimeException("Boom")); throw thrownException.get(); } }); } catch (RuntimeException e) { //noinspection ThrowableResultOfMethodCallIgnored assertTrue(e == thrownException.get()); } assertEquals(0, realm.where(Owner.class).count()); } @Test public void executeTransaction_cancelInsideClosureThrowsException() { assertEquals(0, realm.where(Owner.class).count()); TestHelper.TestLogger testLogger = new TestHelper.TestLogger(); try { RealmLog.add(testLogger); realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Owner owner = realm.createObject(Owner.class); owner.setName("Owner"); realm.cancelTransaction(); throw new RuntimeException("Boom"); } }); } catch (RuntimeException ignored) { // Ensures that we pass a valuable error message to the logger for developers. assertEquals(testLogger.message, "Could not cancel transaction, not currently in a transaction."); } finally { RealmLog.remove(testLogger); } assertEquals(0, realm.where(Owner.class).count()); } @Test public void delete_type() { // ** Deletes non existing table should succeed. realm.beginTransaction(); realm.delete(AllTypes.class); realm.commitTransaction(); // ** Deletes existing class, but leaves other classes classes. // Adds two classes. populateTestRealm(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setName("Castro"); realm.commitTransaction(); // Clears. realm.beginTransaction(); realm.delete(Dog.class); realm.commitTransaction(); // Checks one class is cleared but other class is still there. RealmResults<AllTypes> resultListTypes = realm.where(AllTypes.class).findAll(); assertEquals(TEST_DATA_SIZE, resultListTypes.size()); RealmResults<Dog> resultListDogs = realm.where(Dog.class).findAll(); assertEquals(0, resultListDogs.size()); // ** delete() must throw outside a transaction. try { realm.delete(AllTypes.class); fail("Expected exception"); } catch (IllegalStateException ignored) { } } private void createAndTestFilename(String language, String fileName) { RealmConfiguration realmConfig = configFactory.createConfiguration(fileName); Realm realm1 = Realm.getInstance(realmConfig); realm1.beginTransaction(); Dog dog1 = realm1.createObject(Dog.class); dog1.setName("Rex"); realm1.commitTransaction(); realm1.close(); File file = new File(realmConfig.getPath()); assertTrue(language, file.exists()); Realm realm2 = Realm.getInstance(realmConfig); Dog dog2 = realm2.where(Dog.class).findFirst(); assertEquals(language, "Rex", dog2.getName()); realm2.close(); } // TODO Move to RealmConfigurationTests? @Test public void realmConfiguration_fileName() { createAndTestFilename("American", "Washington"); createAndTestFilename("Danish", "København"); createAndTestFilename("Russian", "Москва"); createAndTestFilename("Greek", "Αθήνα"); createAndTestFilename("Chinese", "北京市"); createAndTestFilename("Korean", "서울시"); createAndTestFilename("Arabic", "الرياض"); createAndTestFilename("India", "नई दिल्ली"); createAndTestFilename("Japanese", "東京都"); } @Test public void utf8Tests() { realm.beginTransaction(); realm.delete(AllTypes.class); realm.commitTransaction(); String file = "assets/unicode_codepoints.csv"; Scanner scanner = new Scanner(getClass().getClassLoader().getResourceAsStream(file), "UTF-8"); int i = 0; String currentUnicode = null; try { realm.beginTransaction(); while (scanner.hasNextLine()) { currentUnicode = scanner.nextLine(); char[] chars = Character.toChars(Integer.parseInt(currentUnicode, 16)); String codePoint = new String(chars); AllTypes o = realm.createObject(AllTypes.class); o.setColumnLong(i); o.setColumnString(codePoint); AllTypes realmType = realm.where(AllTypes.class).equalTo("columnLong", i).findFirst(); if (i > 1) { assertEquals("Codepoint: " + i + " / " + currentUnicode, codePoint, realmType.getColumnString()); // codepoint 0 is NULL, ignore for now. } i++; } realm.commitTransaction(); } catch (Exception e) { fail("Failure, Codepoint: " + i + " / " + currentUnicode + " " + e.getMessage()); } } private List<String> getCharacterArray() { List<String> chars_array = new ArrayList<String>(); String file = "assets/unicode_codepoints.csv"; Scanner scanner = new Scanner(getClass().getClassLoader().getResourceAsStream(file), "UTF-8"); int i = 0; String currentUnicode = null; try { while (scanner.hasNextLine()) { currentUnicode = scanner.nextLine(); char[] chars = Character.toChars(Integer.parseInt(currentUnicode, 16)); String codePoint = new String(chars); chars_array.add(codePoint); i++; } } catch (Exception e) { fail("Failure, Codepoint: " + i + " / " + currentUnicode + " " + e.getMessage()); } return chars_array; } // The test writes and reads random Strings. @Test @Ignore("This test is slow. Move it to another testsuite that runs once a day on Jenkins") public void unicodeStrings() { List<String> chars_array = getCharacterArray(); // Change seed value for new random values. long seed = 20; Random random = new Random(seed); String test_char = ""; String test_char_old = ""; int random_value; for (int i = 0; i < 1000; i++) { random_value = random.nextInt(25); for (int j = 0; j < random_value; j++) { test_char = test_char_old + chars_array.get(random.nextInt(27261)); test_char_old = test_char; } realm.beginTransaction(); StringOnly stringOnly = realm.createObject(StringOnly.class); stringOnly.setChars(test_char); realm.commitTransaction(); realm.where(StringOnly.class).findFirst().getChars(); realm.beginTransaction(); realm.delete(StringOnly.class); realm.commitTransaction(); } } @Test public void getInstance_referenceCounting() { // At this point reference count should be one because of the setUp method. try { realm.where(AllTypes.class).count(); } catch (IllegalStateException e) { fail(); } // Makes sure the reference counter is per realm file. RealmConfiguration anotherConfig = configFactory.createConfiguration("anotherRealm.realm"); Realm.deleteRealm(anotherConfig); Realm otherRealm = Realm.getInstance(anotherConfig); // Raises the reference. Realm realm = null; try { realm = Realm.getInstance(configFactory.createConfiguration()); } finally { if (realm != null) { realm.close(); } } try { // This should not fail because the reference is now 1. if (realm != null) { realm.where(AllTypes.class).count(); } } catch (IllegalStateException e) { fail(); } this.realm.close(); try { this.realm.where(AllTypes.class).count(); fail(); } catch (IllegalStateException ignored) { } try { otherRealm.where(AllTypes.class).count(); } catch (IllegalStateException e) { fail(); } finally { otherRealm.close(); } try { otherRealm.where(AllTypes.class).count(); fail(); } catch (IllegalStateException ignored) { } } @Test public void getInstance_referenceCounting_doubleClose() { realm.close(); realm.close(); // Counts down once too many. Counter is now potentially negative. realm = Realm.getInstance(configFactory.createConfiguration()); realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); RealmResults<AllTypes> queryResult = realm.where(AllTypes.class).findAll(); assertEquals(allTypes, queryResult.get(0)); realm.commitTransaction(); realm.close(); // This might not close the Realm if the reference count is wrong. // This should now fail due to the Realm being fully closed. thrown.expect(IllegalStateException.class); allTypes.getColumnString(); } @Test public void writeCopyTo() throws IOException { RealmConfiguration configA = configFactory.createConfiguration("file1.realm"); RealmConfiguration configB = configFactory.createConfiguration("file2.realm"); Realm.deleteRealm(configA); Realm.deleteRealm(configB); Realm realm1 = null; try { realm1 = Realm.getInstance(configA); realm1.beginTransaction(); AllTypes allTypes = realm1.createObject(AllTypes.class); allTypes.setColumnString("Hello World"); realm1.commitTransaction(); realm1.writeCopyTo(new File(configB.getPath())); } finally { if (realm1 != null) { realm1.close(); } } // Copy is compacted i.e. smaller than original. File file1 = new File(configA.getPath()); File file2 = new File(configB.getPath()); assertTrue(file1.length() >= file2.length()); Realm realm2 = null; try { // Contents is copied too. realm2 = Realm.getInstance(configB); RealmResults<AllTypes> results = realm2.where(AllTypes.class).findAll(); assertEquals(1, results.size()); assertEquals("Hello World", results.first().getColumnString()); } finally { if (realm2 != null) { realm2.close(); } } } @Test public void compactRealm() { final RealmConfiguration configuration = realm.getConfiguration(); realm.close(); realm = null; assertTrue(Realm.compactRealm(configuration)); realm = Realm.getInstance(configuration); } @Test public void compactRealm_failsIfOpen() { assertFalse(Realm.compactRealm(realm.getConfiguration())); } @Test public void compactRealm_encryptedEmptyRealm() { RealmConfiguration realmConfig = configFactory.createConfiguration("enc.realm", TestHelper.getRandomKey()); Realm realm = Realm.getInstance(realmConfig); realm.close(); assertTrue(Realm.compactRealm(realmConfig)); realm = Realm.getInstance(realmConfig); assertFalse(realm.isClosed()); assertTrue(realm.isEmpty()); realm.close(); } @Test public void compactRealm_encryptedPopulatedRealm() { final int DATA_SIZE = 100; RealmConfiguration realmConfig = configFactory.createConfiguration("enc.realm", TestHelper.getRandomKey()); Realm realm = Realm.getInstance(realmConfig); populateTestRealm(realm, DATA_SIZE); realm.close(); assertTrue(Realm.compactRealm(realmConfig)); realm = Realm.getInstance(realmConfig); assertFalse(realm.isClosed()); assertEquals(DATA_SIZE, realm.where(AllTypes.class).count()); realm.close(); } @Test public void compactRealm_emptyRealm() throws IOException { final String REALM_NAME = "test.realm"; RealmConfiguration realmConfig = configFactory.createConfiguration(REALM_NAME); Realm realm = Realm.getInstance(realmConfig); realm.close(); long before = new File(realmConfig.getPath()).length(); assertTrue(Realm.compactRealm(realmConfig)); long after = new File(realmConfig.getPath()).length(); assertTrue(before >= after); } @Test public void compactRealm_populatedRealm() throws IOException { final String REALM_NAME = "test.realm"; RealmConfiguration realmConfig = configFactory.createConfiguration(REALM_NAME); Realm realm = Realm.getInstance(realmConfig); populateTestRealm(realm, 100); realm.close(); long before = new File(realmConfig.getPath()).length(); assertTrue(Realm.compactRealm(realmConfig)); long after = new File(realmConfig.getPath()).length(); assertTrue(before >= after); } @Test public void compactRealm_onExternalStorage() { final File externalFilesDir = context.getExternalFilesDir(null); final RealmConfiguration config = new RealmConfiguration.Builder() .directory(externalFilesDir) .name("external.realm") .build(); Realm.deleteRealm(config); Realm realm = Realm.getInstance(config); realm.close(); assertTrue(Realm.compactRealm(config)); realm = Realm.getInstance(config); realm.close(); Realm.deleteRealm(config); } @Test public void copyToRealm_null() { realm.beginTransaction(); try { realm.copyToRealm((AllTypes) null); fail("Copying null objects into Realm should not be allowed"); } catch (IllegalArgumentException ignored) { } finally { realm.cancelTransaction(); } } @Test public void copyToRealm_managedObject() { realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnString("Test"); realm.commitTransaction(); realm.beginTransaction(); AllTypes copiedAllTypes = realm.copyToRealm(allTypes); realm.commitTransaction(); assertTrue(allTypes == copiedAllTypes); } @Test public void copyToRealm_fromOtherRealm() { realm.beginTransaction(); AllTypes allTypes = realm.createObject(AllTypes.class); allTypes.setColumnString("Test"); realm.commitTransaction(); RealmConfiguration realmConfig = configFactory.createConfiguration("other-realm"); Realm otherRealm = Realm.getInstance(realmConfig); otherRealm.beginTransaction(); AllTypes copiedAllTypes = otherRealm.copyToRealm(allTypes); otherRealm.commitTransaction(); assertNotSame(allTypes, copiedAllTypes); // Same object in different Realms is not the same. assertEquals(allTypes.getColumnString(), copiedAllTypes.getColumnString()); // But data is still the same. otherRealm.close(); } @Test public void copyToRealm() { Date date = new Date(); Dog dog = new Dog(); dog.setName("Fido"); RealmList<Dog> list = new RealmList<Dog>(); list.add(dog); AllTypes allTypes = new AllTypes(); allTypes.setColumnString("String"); allTypes.setColumnLong(1L); allTypes.setColumnFloat(1F); allTypes.setColumnDouble(1D); allTypes.setColumnBoolean(true); allTypes.setColumnDate(date); allTypes.setColumnBinary(new byte[] {1, 2, 3}); allTypes.setColumnRealmObject(dog); allTypes.setColumnRealmList(list); realm.beginTransaction(); AllTypes realmTypes = realm.copyToRealm(allTypes); realm.commitTransaction(); assertNotSame(allTypes, realmTypes); // Objects should not be considered equal. assertEquals(allTypes.getColumnString(), realmTypes.getColumnString()); // But they contain the same data. assertEquals(allTypes.getColumnLong(), realmTypes.getColumnLong()); assertEquals(allTypes.getColumnFloat(), realmTypes.getColumnFloat(), 0); assertEquals(allTypes.getColumnDouble(), realmTypes.getColumnDouble(), 0); assertEquals(allTypes.isColumnBoolean(), realmTypes.isColumnBoolean()); assertEquals(allTypes.getColumnDate(), realmTypes.getColumnDate()); assertArrayEquals(allTypes.getColumnBinary(), realmTypes.getColumnBinary()); assertEquals(allTypes.getColumnRealmObject().getName(), dog.getName()); assertEquals(list.size(), realmTypes.getColumnRealmList().size()); assertEquals(list.get(0).getName(), realmTypes.getColumnRealmList().get(0).getName()); } @Test public void copyToRealm_cyclicObjectReferences() { CyclicType oneCyclicType = new CyclicType(); oneCyclicType.setName("One"); CyclicType anotherCyclicType = new CyclicType(); anotherCyclicType.setName("Two"); oneCyclicType.setObject(anotherCyclicType); anotherCyclicType.setObject(oneCyclicType); realm.beginTransaction(); CyclicType realmObject = realm.copyToRealm(oneCyclicType); realm.commitTransaction(); assertEquals("One", realmObject.getName()); assertEquals("Two", realmObject.getObject().getName()); assertEquals(2, realm.where(CyclicType.class).count()); // Tests copyToRealm overload that uses the Iterator. // Makes sure we reuse the same graph cache Map to avoid duplicates. realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); assertEquals(0, realm.where(CyclicType.class).count()); realm.beginTransaction(); List<CyclicType> cyclicTypes = realm.copyToRealm(Arrays.asList(oneCyclicType, anotherCyclicType)); realm.commitTransaction(); assertEquals(2, cyclicTypes.size()); assertEquals("One", cyclicTypes.get(0).getName()); assertEquals("Two", cyclicTypes.get(1).getName()); assertEquals(2, realm.where(CyclicType.class).count()); } @Test public void copyToRealm_cyclicObjectReferencesWithPK() { CyclicTypePrimaryKey oneCyclicType = new CyclicTypePrimaryKey(1, "One"); CyclicTypePrimaryKey anotherCyclicType = new CyclicTypePrimaryKey(2, "Two"); oneCyclicType.setObject(anotherCyclicType); anotherCyclicType.setObject(oneCyclicType); realm.beginTransaction(); CyclicTypePrimaryKey realmObject = realm.copyToRealm(oneCyclicType); realm.commitTransaction(); assertEquals("One", realmObject.getName()); assertEquals("Two", realmObject.getObject().getName()); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); // Tests copyToRealm overload that uses the Iterator. // Makes sure we reuse the same graph cache Map to avoid duplicates. realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); assertEquals(0, realm.where(CyclicTypePrimaryKey.class).count()); realm.beginTransaction(); List<CyclicTypePrimaryKey> cyclicTypes = realm.copyToRealm(Arrays.asList(oneCyclicType, anotherCyclicType)); realm.commitTransaction(); assertEquals(2, cyclicTypes.size()); assertEquals("One", cyclicTypes.get(0).getName()); assertEquals("Two", cyclicTypes.get(1).getName()); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); } @Test public void copyToRealm_cyclicListReferences() { CyclicType oneCyclicType = new CyclicType(); oneCyclicType.setName("One"); CyclicType anotherCyclicType = new CyclicType(); anotherCyclicType.setName("Two"); oneCyclicType.setObjects(new RealmList(anotherCyclicType)); anotherCyclicType.setObjects(new RealmList(oneCyclicType)); realm.beginTransaction(); CyclicType realmObject = realm.copyToRealm(oneCyclicType); realm.commitTransaction(); assertEquals("One", realmObject.getName()); assertEquals(2, realm.where(CyclicType.class).count()); } // Checks that if a field has a null value, it gets converted to the default value for that type. @Test public void copyToRealm_convertsNullToDefaultValue() { realm.beginTransaction(); AllTypes realmTypes = realm.copyToRealm(new AllTypes()); realm.commitTransaction(); assertEquals("", realmTypes.getColumnString()); assertEquals(new Date(0), realmTypes.getColumnDate()); assertArrayEquals(new byte[0], realmTypes.getColumnBinary()); } // Check that using copyToRealm will set the primary key directly instead of first setting // it to the default value (which can fail). @Test public void copyToRealm_primaryKeyIsSetDirectly() { realm.beginTransaction(); realm.createObject(OwnerPrimaryKey.class, 0); realm.copyToRealm(new OwnerPrimaryKey(1, "Foo")); realm.commitTransaction(); assertEquals(2, realm.where(OwnerPrimaryKey.class).count()); } @Test public void copyToRealm_stringPrimaryKeyIsNull() { final long SECONDARY_FIELD_VALUE = 34992142L; TestHelper.addStringPrimaryKeyObjectToTestRealm(realm, (String) null, SECONDARY_FIELD_VALUE); RealmResults<PrimaryKeyAsString> results = realm.where(PrimaryKeyAsString.class).findAll(); assertEquals(1, results.size()); assertEquals(null, results.first().getName()); assertEquals(SECONDARY_FIELD_VALUE, results.first().getId()); } @Test public void copyToRealm_boxedNumberPrimaryKeyIsNull() { final String SECONDARY_FIELD_VALUE = "nullNumberPrimaryKeyObj"; final Class[] CLASSES = {PrimaryKeyAsBoxedByte.class, PrimaryKeyAsBoxedShort.class, PrimaryKeyAsBoxedInteger.class, PrimaryKeyAsBoxedLong.class}; TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, SECONDARY_FIELD_VALUE); TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, SECONDARY_FIELD_VALUE); TestHelper.addIntegerPrimaryKeyObjectToTestRealm(realm, (Integer) null, SECONDARY_FIELD_VALUE); TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, SECONDARY_FIELD_VALUE); for (Class clazz : CLASSES) { RealmResults results = realm.where(clazz).findAll(); assertEquals(1, results.size()); assertEquals(null, ((NullPrimaryKey) results.first()).getId()); assertEquals(SECONDARY_FIELD_VALUE, ((NullPrimaryKey) results.first()).getName()); } } @Test public void copyToRealm_duplicatedNullPrimaryKeyThrows() { final String[] PRIMARY_KEY_TYPES = {"String", "BoxedByte", "BoxedShort", "BoxedInteger", "BoxedLong"}; TestHelper.addStringPrimaryKeyObjectToTestRealm(realm, (String) null, 0); TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, (String) null); TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, (String) null); TestHelper.addIntegerPrimaryKeyObjectToTestRealm(realm, (Integer) null, (String) null); TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, (String) null); for (String className : PRIMARY_KEY_TYPES) { try { realm.beginTransaction(); switch (className) { case "String": realm.copyToRealm(new PrimaryKeyAsString()); break; case "BoxedByte": realm.copyToRealm(new PrimaryKeyAsBoxedByte()); break; case "BoxedShort": realm.copyToRealm(new PrimaryKeyAsBoxedShort()); break; case "BoxedInteger": realm.copyToRealm(new PrimaryKeyAsBoxedInteger()); break; case "BoxedLong": realm.copyToRealm(new PrimaryKeyAsBoxedLong()); break; default: } fail("Null value as primary key already exists."); } catch (RealmPrimaryKeyConstraintException expected) { assertTrue("Exception message is: " + expected.getMessage(), expected.getMessage().contains("Primary key value already exists: 'null' .")); } finally { realm.cancelTransaction(); } } } @Test public void copyToRealm_doNotCopyReferencedObjectIfManaged() { realm.beginTransaction(); // Child object is managed by Realm. CyclicTypePrimaryKey childObj = realm.createObject(CyclicTypePrimaryKey.class, 1); childObj.setName("Child"); // Parent object is an unmanaged object. CyclicTypePrimaryKey parentObj = new CyclicTypePrimaryKey(2); parentObj.setObject(childObj); realm.copyToRealm(parentObj); realm.commitTransaction(); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); } @Test public void copyToRealm_list() { Dog dog1 = new Dog(); dog1.setName("Dog 1"); Dog dog2 = new Dog(); dog2.setName("Dog 2"); RealmList<Dog> list = new RealmList<Dog>(); list.addAll(Arrays.asList(dog1, dog2)); realm.beginTransaction(); List<Dog> copiedList = new ArrayList<Dog>(realm.copyToRealm(list)); realm.commitTransaction(); assertEquals(2, copiedList.size()); assertEquals(dog1.getName(), copiedList.get(0).getName()); assertEquals(dog2.getName(), copiedList.get(1).getName()); } @Test public void copyToRealm_objectInOtherThreadThrows() { final CountDownLatch bgThreadDoneLatch = new CountDownLatch(1); realm.beginTransaction(); final Dog dog = realm.createObject(Dog.class); realm.commitTransaction(); new Thread(new Runnable() { @Override public void run() { final Realm bgRealm = Realm.getInstance(realm.getConfiguration()); bgRealm.beginTransaction(); try { bgRealm.copyToRealm(dog); fail(); } catch (IllegalArgumentException expected) { assertEquals("Objects which belong to Realm instances in other threads cannot be copied into this" + " Realm instance.", expected.getMessage()); } bgRealm.cancelTransaction(); bgRealm.close(); bgThreadDoneLatch.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDoneLatch); } @Test public void copyToRealmOrUpdate_null() { realm.beginTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyToRealmOrUpdate((AllTypes) null); } @Test public void copyToRealmOrUpdate_stringPrimaryKeyFieldIsNull() { final long SECONDARY_FIELD_VALUE = 2192841L; final long SECONDARY_FIELD_UPDATED = 44887612L; PrimaryKeyAsString nullPrimaryKeyObj = TestHelper.addStringPrimaryKeyObjectToTestRealm(realm, (String) null, SECONDARY_FIELD_VALUE); RealmResults<PrimaryKeyAsString> result = realm.where(PrimaryKeyAsString.class).findAll(); assertEquals(1, result.size()); assertEquals(null, result.first().getName()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setId(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsString.class).findFirst().getId()); } @Test public void copyToRealmOrUpdate_boxedBytePrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = "nullBytePrimaryKeyObj"; final String SECONDARY_FIELD_UPDATED = "nullBytePrimaryKeyObjUpdated"; PrimaryKeyAsBoxedByte nullPrimaryKeyObj = TestHelper.addBytePrimaryKeyObjectToTestRealm(realm, (Byte) null, SECONDARY_FIELD_VALUE); RealmResults<PrimaryKeyAsBoxedByte> result = realm.where(PrimaryKeyAsBoxedByte.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedByte.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_boxedShortPrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = "nullShortPrimaryKeyObj"; final String SECONDARY_FIELD_UPDATED = "nullShortPrimaryKeyObjUpdated"; PrimaryKeyAsBoxedShort nullPrimaryKeyObj = TestHelper.addShortPrimaryKeyObjectToTestRealm(realm, (Short) null, SECONDARY_FIELD_VALUE); RealmResults<PrimaryKeyAsBoxedShort> result = realm.where(PrimaryKeyAsBoxedShort.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedShort.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_boxedIntegerPrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = "nullIntegerPrimaryKeyObj"; final String SECONDARY_FIELD_UPDATED = "nullIntegerPrimaryKeyObjUpdated"; PrimaryKeyAsBoxedInteger nullPrimaryKeyObj = TestHelper.addIntegerPrimaryKeyObjectToTestRealm(realm, (Integer) null, SECONDARY_FIELD_VALUE); RealmResults<PrimaryKeyAsBoxedInteger> result = realm.where(PrimaryKeyAsBoxedInteger.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedInteger.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_boxedLongPrimaryKeyFieldIsNull() { final String SECONDARY_FIELD_VALUE = "nullLongPrimaryKeyObj"; final String SECONDARY_FIELD_UPDATED = "nullLongPrimaryKeyObjUpdated"; PrimaryKeyAsBoxedLong nullPrimaryKeyObj = TestHelper.addLongPrimaryKeyObjectToTestRealm(realm, (Long) null, SECONDARY_FIELD_VALUE); RealmResults<PrimaryKeyAsBoxedLong> result = realm.where(PrimaryKeyAsBoxedLong.class).findAll(); assertEquals(1, result.size()); assertEquals(SECONDARY_FIELD_VALUE, result.first().getName()); assertEquals(null, result.first().getId()); // Updates objects. realm.beginTransaction(); nullPrimaryKeyObj.setName(SECONDARY_FIELD_UPDATED); realm.copyToRealmOrUpdate(nullPrimaryKeyObj); realm.commitTransaction(); assertEquals(SECONDARY_FIELD_UPDATED, realm.where(PrimaryKeyAsBoxedLong.class).findFirst().getName()); } @Test public void copyToRealmOrUpdate_noPrimaryKeyField() { realm.beginTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyToRealmOrUpdate(new AllTypes()); } @Test public void copyToRealmOrUpdate_addNewObjects() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { PrimaryKeyAsLong obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName("Foo"); realm.copyToRealm(obj); PrimaryKeyAsLong obj2 = new PrimaryKeyAsLong(); obj2.setId(2); obj2.setName("Bar"); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(2, realm.where(PrimaryKeyAsLong.class).count()); } @Test public void copyToRealmOrUpdate_updateExistingObject() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); obj.setColumnString("Foo"); obj.setColumnLong(1); obj.setColumnFloat(1.23F); obj.setColumnDouble(1.234D); obj.setColumnBoolean(false); obj.setColumnBinary(new byte[] {1, 2, 3}); obj.setColumnDate(new Date(1000)); obj.setColumnRealmObject(new DogPrimaryKey(1, "Dog1")); obj.setColumnRealmList(new RealmList<DogPrimaryKey>(new DogPrimaryKey(2, "Dog2"))); obj.setColumnBoxedBoolean(true); realm.copyToRealm(obj); AllTypesPrimaryKey obj2 = new AllTypesPrimaryKey(); obj2.setColumnString("Bar"); obj2.setColumnLong(1); obj2.setColumnFloat(2.23F); obj2.setColumnDouble(2.234D); obj2.setColumnBoolean(true); obj2.setColumnBinary(new byte[] {2, 3, 4}); obj2.setColumnDate(new Date(2000)); obj2.setColumnRealmObject(new DogPrimaryKey(3, "Dog3")); obj2.setColumnRealmList(new RealmList<DogPrimaryKey>(new DogPrimaryKey(4, "Dog4"))); obj2.setColumnBoxedBoolean(false); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(1, realm.where(AllTypesPrimaryKey.class).count()); AllTypesPrimaryKey obj = realm.where(AllTypesPrimaryKey.class).findFirst(); // Checks that the the only element has all its properties updated. assertEquals("Bar", obj.getColumnString()); assertEquals(1, obj.getColumnLong()); assertEquals(2.23F, obj.getColumnFloat(), 0); assertEquals(2.234D, obj.getColumnDouble(), 0); assertEquals(true, obj.isColumnBoolean()); assertArrayEquals(new byte[] {2, 3, 4}, obj.getColumnBinary()); assertEquals(new Date(2000), obj.getColumnDate()); assertEquals("Dog3", obj.getColumnRealmObject().getName()); assertEquals(1, obj.getColumnRealmList().size()); assertEquals("Dog4", obj.getColumnRealmList().get(0).getName()); assertFalse(obj.getColumnBoxedBoolean()); } @Test public void copyToRealmOrUpdate_cyclicObject() { CyclicTypePrimaryKey oneCyclicType = new CyclicTypePrimaryKey(1); oneCyclicType.setName("One"); CyclicTypePrimaryKey anotherCyclicType = new CyclicTypePrimaryKey(2); anotherCyclicType.setName("Two"); oneCyclicType.setObject(anotherCyclicType); anotherCyclicType.setObject(oneCyclicType); realm.beginTransaction(); realm.copyToRealm(oneCyclicType); realm.commitTransaction(); oneCyclicType.setName("Three"); anotherCyclicType.setName("Four"); realm.beginTransaction(); realm.copyToRealmOrUpdate(oneCyclicType); realm.commitTransaction(); assertEquals(2, realm.where(CyclicTypePrimaryKey.class).count()); assertEquals("Three", realm.where(CyclicTypePrimaryKey.class).equalTo("id", 1).findFirst().getName()); } // Checks that an unmanaged object with only default values can override data. @Test public void copyToRealmOrUpdate_defaultValuesOverrideExistingData() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); obj.setColumnString("Foo"); obj.setColumnLong(1); obj.setColumnFloat(1.23F); obj.setColumnDouble(1.234D); obj.setColumnBoolean(false); obj.setColumnBinary(new byte[] {1, 2, 3}); obj.setColumnDate(new Date(1000)); obj.setColumnRealmObject(new DogPrimaryKey(1, "Dog1")); obj.setColumnRealmList(new RealmList<DogPrimaryKey>(new DogPrimaryKey(2, "Dog2"))); realm.copyToRealm(obj); AllTypesPrimaryKey obj2 = new AllTypesPrimaryKey(); obj2.setColumnLong(1); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(1, realm.where(AllTypesPrimaryKey.class).count()); AllTypesPrimaryKey obj = realm.where(AllTypesPrimaryKey.class).findFirst(); assertNull(obj.getColumnString()); assertEquals(1, obj.getColumnLong()); assertEquals(0.0F, obj.getColumnFloat(), 0); assertEquals(0.0D, obj.getColumnDouble(), 0); assertEquals(false, obj.isColumnBoolean()); assertNull(obj.getColumnBinary()); assertNull(obj.getColumnDate()); assertNull(obj.getColumnRealmObject()); assertEquals(0, obj.getColumnRealmList().size()); } // Tests that if references to objects are removed, the objects are still in the Realm. @Test public void copyToRealmOrUpdate_referencesNotDeleted() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { AllTypesPrimaryKey obj = new AllTypesPrimaryKey(); obj.setColumnLong(1); obj.setColumnRealmObject(new DogPrimaryKey(1, "Dog1")); obj.setColumnRealmList(new RealmList<DogPrimaryKey>(new DogPrimaryKey(2, "Dog2"))); realm.copyToRealm(obj); AllTypesPrimaryKey obj2 = new AllTypesPrimaryKey(); obj2.setColumnLong(1); obj2.setColumnRealmObject(new DogPrimaryKey(3, "Dog3")); obj2.setColumnRealmList(new RealmList<DogPrimaryKey>(new DogPrimaryKey(4, "Dog4"))); realm.copyToRealmOrUpdate(obj2); } }); assertEquals(1, realm.where(AllTypesPrimaryKey.class).count()); assertEquals(4, realm.where(DogPrimaryKey.class).count()); } @Test public void copyToRealmOrUpdate_primaryKeyMixInObjectGraph() { // Crate Object graph where tier 2 consists of 1 object with primary key and one doesn't. // Tier 3 both have objects with primary keys. // // PK // / \ // PK nonPK // | | // PK PK DogPrimaryKey dog = new DogPrimaryKey(1, "Dog"); OwnerPrimaryKey owner = new OwnerPrimaryKey(1, "Owner"); owner.setDog(dog); Cat cat = new Cat(); cat.setScaredOfDog(dog); PrimaryKeyMix mixObject = new PrimaryKeyMix(1); mixObject.setDogOwner(owner); mixObject.setCat(cat); realm.beginTransaction(); PrimaryKeyMix realmObject = realm.copyToRealmOrUpdate(mixObject); realm.commitTransaction(); assertEquals("Dog", realmObject.getCat().getScaredOfDog().getName()); assertEquals("Dog", realmObject.getDogOwner().getDog().getName()); } @Test public void copyToRealmOrUpdate_iterable() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { PrimaryKeyAsLong obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName("Foo"); realm.copyToRealm(obj); PrimaryKeyAsLong obj2 = new PrimaryKeyAsLong(); obj2.setId(1); obj2.setName("Bar"); PrimaryKeyAsLong obj3 = new PrimaryKeyAsLong(); obj3.setId(1); obj3.setName("Baz"); realm.copyToRealmOrUpdate(Arrays.asList(obj2, obj3)); } }); assertEquals(1, realm.where(PrimaryKeyAsLong.class).count()); assertEquals("Baz", realm.where(PrimaryKeyAsLong.class).findFirst().getName()); } // Tests that a collection of objects with references all gets copied. @Test public void copyToRealmOrUpdate_iterableChildObjects() { DogPrimaryKey dog = new DogPrimaryKey(1, "Snoop"); AllTypesPrimaryKey allTypes1 = new AllTypesPrimaryKey(); allTypes1.setColumnLong(1); allTypes1.setColumnRealmObject(dog); AllTypesPrimaryKey allTypes2 = new AllTypesPrimaryKey(); allTypes1.setColumnLong(2); allTypes2.setColumnRealmObject(dog); realm.beginTransaction(); realm.copyToRealmOrUpdate(Arrays.asList(allTypes1, allTypes2)); realm.commitTransaction(); assertEquals(2, realm.where(AllTypesPrimaryKey.class).count()); assertEquals(1, realm.where(DogPrimaryKey.class).count()); } @Test public void copyToRealmOrUpdate_objectInOtherThreadThrows() { final CountDownLatch bgThreadDoneLatch = new CountDownLatch(1); realm.beginTransaction(); final OwnerPrimaryKey ownerPrimaryKey = realm.createObject(OwnerPrimaryKey.class, 0); realm.commitTransaction(); new Thread(new Runnable() { @Override public void run() { final Realm bgRealm = Realm.getInstance(realm.getConfiguration()); bgRealm.beginTransaction(); try { bgRealm.copyToRealm(ownerPrimaryKey); fail(); } catch (IllegalArgumentException expected) { assertEquals("Objects which belong to Realm instances in other threads cannot be copied into this" + " Realm instance.", expected.getMessage()); } bgRealm.cancelTransaction(); bgRealm.close(); bgThreadDoneLatch.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDoneLatch); } @Test public void copyToRealmOrUpdate_listHasObjectInOtherThreadThrows() { final CountDownLatch bgThreadDoneLatch = new CountDownLatch(1); final OwnerPrimaryKey ownerPrimaryKey = new OwnerPrimaryKey(); realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); realm.commitTransaction(); ownerPrimaryKey.setDogs(new RealmList<Dog>(dog)); new Thread(new Runnable() { @Override public void run() { final Realm bgRealm = Realm.getInstance(realm.getConfiguration()); bgRealm.beginTransaction(); try { bgRealm.copyToRealm(ownerPrimaryKey); fail(); } catch (IllegalArgumentException expected) { assertEquals("Objects which belong to Realm instances in other threads cannot be copied into this" + " Realm instance.", expected.getMessage()); } bgRealm.cancelTransaction(); bgRealm.close(); bgThreadDoneLatch.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDoneLatch); } @Test public void getInstance_differentEncryptionKeys() { byte[] key1 = TestHelper.getRandomKey(42); byte[] key2 = TestHelper.getRandomKey(42); // Makes sure the key is the same, but in two different instances. assertArrayEquals(key1, key2); assertTrue(key1 != key2); final String ENCRYPTED_REALM = "differentKeys.realm"; Realm realm1 = null; Realm realm2 = null; try { realm1 = Realm.getInstance(configFactory.createConfiguration(ENCRYPTED_REALM, key1)); try { realm2 = Realm.getInstance(configFactory.createConfiguration(ENCRYPTED_REALM, key2)); } catch (Exception e) { fail("Unexpected exception: " + e); } finally { if (realm2 != null) { realm2.close(); } } } finally { if (realm1 != null) { realm1.close(); } } } @Test public void writeEncryptedCopyTo() throws Exception { populateTestRealm(); long before = realm.where(AllTypes.class).count(); assertEquals(TEST_DATA_SIZE, before); // Configures test realms. final String ENCRYPTED_REALM_FILE_NAME = "encryptedTestRealm.realm"; final String RE_ENCRYPTED_REALM_FILE_NAME = "reEncryptedTestRealm.realm"; final String DECRYPTED_REALM_FILE_NAME = "decryptedTestRealm.realm"; RealmConfiguration encryptedRealmConfig = configFactory.createConfiguration(ENCRYPTED_REALM_FILE_NAME, TestHelper.getRandomKey()); RealmConfiguration reEncryptedRealmConfig = configFactory.createConfiguration(RE_ENCRYPTED_REALM_FILE_NAME, TestHelper.getRandomKey()); RealmConfiguration decryptedRealmConfig = configFactory.createConfiguration(DECRYPTED_REALM_FILE_NAME); // Writes encrypted copy from a unencrypted Realm. File destination = new File(encryptedRealmConfig.getPath()); realm.writeEncryptedCopyTo(destination, encryptedRealmConfig.getEncryptionKey()); Realm encryptedRealm = null; try { // Verifies encrypted Realm and writes new encrypted copy with a new key. encryptedRealm = Realm.getInstance(encryptedRealmConfig); assertEquals(TEST_DATA_SIZE, encryptedRealm.where(AllTypes.class).count()); destination = new File(reEncryptedRealmConfig.getPath()); encryptedRealm.writeEncryptedCopyTo(destination, reEncryptedRealmConfig.getEncryptionKey()); // Verifies re-encrypted copy. Realm reEncryptedRealm = null; try { reEncryptedRealm = Realm.getInstance(reEncryptedRealmConfig); assertEquals(TEST_DATA_SIZE, reEncryptedRealm.where(AllTypes.class).count()); } finally { if (reEncryptedRealm != null) { reEncryptedRealm.close(); if (!Realm.deleteRealm(reEncryptedRealmConfig)) { fail(); } } } // Writes non-encrypted copy from the encrypted version. destination = new File(decryptedRealmConfig.getPath()); encryptedRealm.writeEncryptedCopyTo(destination, null); // Verifies decrypted Realm and cleans up. Realm decryptedRealm = null; try { decryptedRealm = Realm.getInstance(decryptedRealmConfig); assertEquals(TEST_DATA_SIZE, decryptedRealm.where(AllTypes.class).count()); } finally { if (decryptedRealm != null) { decryptedRealm.close(); if (!Realm.deleteRealm(decryptedRealmConfig)) { fail(); } } } } finally { if (encryptedRealm != null) { encryptedRealm.close(); if (!Realm.deleteRealm(encryptedRealmConfig)) { fail(); } } } } @Test public void writeEncryptedCopyTo_wrongKeyLength() { byte[] wrongLengthKey = new byte[42]; File destination = new File(configFactory.getRoot(), "wrong_key.realm"); thrown.expect(IllegalArgumentException.class); realm.writeEncryptedCopyTo(destination, wrongLengthKey); } @Test public void deleteRealm_failures() { final String OTHER_REALM_NAME = "yetAnotherRealm.realm"; RealmConfiguration configA = configFactory.createConfiguration(); RealmConfiguration configB = configFactory.createConfiguration(OTHER_REALM_NAME); // This instance is already cached because of the setUp() method so this deletion should throw. try { Realm.deleteRealm(configA); fail(); } catch (IllegalStateException ignored) { } // Creates a new Realm file. Realm yetAnotherRealm = Realm.getInstance(configB); // Deleting it should fail. try { Realm.deleteRealm(configB); fail(); } catch (IllegalStateException ignored) { } // But now that we close it deletion should work. yetAnotherRealm.close(); try { Realm.deleteRealm(configB); } catch (Exception e) { fail(); } } // TODO Does this test something meaningfull not tested elsewhere? @Test public void setter_updateField() throws Exception { realm.beginTransaction(); // Creates an owner with two dogs. OwnerPrimaryKey owner = realm.createObject(OwnerPrimaryKey.class, 1); owner.setName("Jack"); Dog rex = realm.createObject(Dog.class); rex.setName("Rex"); Dog fido = realm.createObject(Dog.class); fido.setName("Fido"); owner.getDogs().add(rex); owner.getDogs().add(fido); assertEquals(2, owner.getDogs().size()); // Changing the name of the owner should not affect the number of dogs. owner.setName("Peter"); assertEquals(2, owner.getDogs().size()); // Updating the user should not affect it either. This is actually a no-op since owner is a Realm backed object. OwnerPrimaryKey owner2 = realm.copyToRealmOrUpdate(owner); assertEquals(2, owner.getDogs().size()); assertEquals(2, owner2.getDogs().size()); realm.commitTransaction(); } @Test public void deleteRealm() throws InterruptedException { File tempDir = new File(configFactory.getRoot(), "delete_test_dir"); File tempDirRenamed = new File(configFactory.getRoot(), "delete_test_dir_2"); assertTrue(tempDir.mkdir()); final RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) .directory(tempDir) .build(); final CountDownLatch bgThreadReadyLatch = new CountDownLatch(1); final CountDownLatch readyToCloseLatch = new CountDownLatch(1); final CountDownLatch closedLatch = new CountDownLatch(1); Realm realm = Realm.getInstance(configuration); // Creates another Realm to ensure the log files are generated. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(configuration); bgThreadReadyLatch.countDown(); TestHelper.awaitOrFail(readyToCloseLatch); realm.close(); closedLatch.countDown(); } }).start(); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); // Waits for bg thread's opening the same Realm. TestHelper.awaitOrFail(bgThreadReadyLatch); // A core upgrade might change the location of the files assertTrue(tempDir.renameTo(tempDirRenamed)); readyToCloseLatch.countDown(); realm.close(); TestHelper.awaitOrFail(closedLatch); // Now we get log files back! assertTrue(tempDirRenamed.renameTo(tempDir)); assertTrue(Realm.deleteRealm(configuration)); // Directory should be empty now. assertEquals(0, tempDir.listFiles().length); } // Tests that all methods that require a transaction. (ie. any function that mutates Realm data) @Test public void callMutableMethodOutsideTransaction() throws JSONException, IOException { // Prepares unmanaged object data. AllTypesPrimaryKey t = new AllTypesPrimaryKey(); List<AllTypesPrimaryKey> ts = Arrays.asList(t, t); // Prepares JSON data. String jsonObjStr = "{ \"columnLong\" : 1 }"; JSONObject jsonObj = new JSONObject(jsonObjStr); InputStream jsonObjStream = TestHelper.stringToStream(jsonObjStr); InputStream jsonObjStream2 = TestHelper.stringToStream(jsonObjStr); String jsonArrStr = " [{ \"columnLong\" : 1 }] "; JSONArray jsonArr = new JSONArray(jsonArrStr); InputStream jsonArrStream = TestHelper.stringToStream(jsonArrStr); InputStream jsonArrStream2 = TestHelper.stringToStream(jsonArrStr); // Tests all methods that should require a transaction. try { realm.createObject(AllTypes.class); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealm(t); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealm(ts); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealmOrUpdate(t); fail(); } catch (IllegalStateException expected) {} try { realm.copyToRealmOrUpdate(ts); fail(); } catch (IllegalStateException expected) {} try { realm.delete(AllTypes.class); fail(); } catch (IllegalStateException expected) {} try { realm.deleteAll(); fail(); } catch (IllegalStateException expected) {} try { realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObj); fail(); } catch (IllegalStateException expected) {} try { realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createObjectFromJson(NoPrimaryKeyNullTypes.class, jsonObjStream); fail(); } catch (IllegalStateException expected) {} } try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObj); fail(); } catch (IllegalStateException expected) {} try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStream2); fail(); } catch (IllegalStateException expected) {} } try { realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArr); fail(); } catch (IllegalStateException expected) {} try { realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createAllFromJson(NoPrimaryKeyNullTypes.class, jsonArrStream); fail(); } catch (IllegalStateException expected) {} } try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArr); fail(); } catch (IllegalStateException expected) {} try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStr); fail(); } catch (IllegalStateException expected) {} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { try { realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStream2); fail(); } catch (IllegalStateException expected) {} } } @Test public void createObject_cannotCreateDynamicRealmObject() { realm.beginTransaction(); try { realm.createObject(DynamicRealmObject.class); fail(); } catch (RealmException ignored) { } } @Test(expected = RealmException.class) public void createObject_absentPrimaryKeyThrows() { realm.beginTransaction(); realm.createObject(DogPrimaryKey.class); } @Test public void createObjectWithPrimaryKey() { realm.beginTransaction(); AllJavaTypes obj = realm.createObject(AllJavaTypes.class, 42); assertEquals(1, realm.where(AllJavaTypes.class).count()); assertEquals(42, obj.getFieldId()); } @Test public void createObjectWithPrimaryKey_noPrimaryKeyField() { realm.beginTransaction(); try { realm.createObject(AllTypes.class, 42); fail(); } catch (IllegalStateException ignored) { } } @Test public void createObjectWithPrimaryKey_wrongValueType() { realm.beginTransaction(); try { realm.createObject(AllJavaTypes.class, "fortyTwo"); fail(); } catch (IllegalArgumentException ignored) { } } @Test public void createObjectWithPrimaryKey_valueAlreadyExists() { realm.beginTransaction(); realm.createObject(AllJavaTypes.class, 42); try { realm.createObject(AllJavaTypes.class, 42); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } } @Test public void createObjectWithPrimaryKey_null() { // Byte realm.beginTransaction(); PrimaryKeyAsBoxedByte primaryKeyAsBoxedByte = realm.createObject(PrimaryKeyAsBoxedByte.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedByte.class).count()); assertNull(primaryKeyAsBoxedByte.getId()); // Short realm.beginTransaction(); PrimaryKeyAsBoxedShort primaryKeyAsBoxedShort = realm.createObject(PrimaryKeyAsBoxedShort.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedShort.class).count()); assertNull(primaryKeyAsBoxedShort.getId()); // Integer realm.beginTransaction(); PrimaryKeyAsBoxedInteger primaryKeyAsBoxedInteger = realm.createObject(PrimaryKeyAsBoxedInteger.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedInteger.class).count()); assertNull(primaryKeyAsBoxedInteger.getId()); // Long realm.beginTransaction(); PrimaryKeyAsBoxedLong primaryKeyAsBoxedLong = realm.createObject(PrimaryKeyAsBoxedLong.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsBoxedLong.class).count()); assertNull(primaryKeyAsBoxedLong.getId()); // String realm.beginTransaction(); PrimaryKeyAsString primaryKeyAsString = realm.createObject(PrimaryKeyAsString.class, null); realm.commitTransaction(); assertEquals(1, realm.where(PrimaryKeyAsString.class).count()); assertNull(primaryKeyAsString.getName()); } @Test public void createObjectWithPrimaryKey_nullOnRequired() { realm.beginTransaction(); // Byte try { realm.createObject(PrimaryKeyRequiredAsBoxedByte.class, null); fail(); } catch (IllegalArgumentException ignored) { } // Short try { realm.createObject(PrimaryKeyRequiredAsBoxedShort.class, null); fail(); } catch (IllegalArgumentException ignored) { } // Integer try { realm.createObject(PrimaryKeyRequiredAsBoxedInteger.class, null); fail(); } catch (IllegalArgumentException ignored) { } // Long try { realm.createObject(PrimaryKeyRequiredAsBoxedLong.class, null); fail(); } catch (IllegalArgumentException ignored) { } // String try { realm.createObject(PrimaryKeyRequiredAsString.class, null); fail(); } catch (IllegalArgumentException ignored) { } realm.cancelTransaction(); } @Test public void createObjectWithPrimaryKey_nullDuplicated() { realm.beginTransaction(); // Byte realm.createObject(PrimaryKeyAsBoxedByte.class, null); try { realm.createObject(PrimaryKeyAsBoxedByte.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // Short realm.createObject(PrimaryKeyAsBoxedShort.class, null); try { realm.createObject(PrimaryKeyAsBoxedShort.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // Integer realm.createObject(PrimaryKeyAsBoxedInteger.class, null); try { realm.createObject(PrimaryKeyAsBoxedInteger.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // Long realm.createObject(PrimaryKeyAsBoxedLong.class, null); try { realm.createObject(PrimaryKeyAsBoxedLong.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } // String realm.createObject(PrimaryKeyAsString.class, null); try { realm.createObject(PrimaryKeyAsString.class, null); fail(); } catch (RealmPrimaryKeyConstraintException ignored) { } realm.cancelTransaction(); } @Test public void createObject_defaultValueFromModelField() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // Creates a DefaultValueOfField with non-default primary key value. realm.createObject(DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); } }); final String createdRandomString = DefaultValueOfField.lastRandomStringValue; testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_STRING, DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_RANDOM_STRING, createdRandomString); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_SHORT, DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_INT, DefaultValueOfField.FIELD_INT_DEFAULT_VALUE); // Default value for pk must be ignored. testNoObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY, DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LONG, DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_BYTE, DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_FLOAT, DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_DOUBLE, DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_BOOLEAN, DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_DATE, DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_BINARY, DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_OBJECT + "." + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueOfField.class, DefaultValueOfField.FIELD_LIST + "." + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); } @Test public void createObject_overwriteNullifiedLinkWithDefaultValue() { final DefaultValueOverwriteNullLink created; realm.beginTransaction(); created = realm.createObject(DefaultValueOverwriteNullLink.class); realm.commitTransaction(); assertEquals(created.getExpectedKeyOfFieldObject(), created.getFieldObject().getFieldRandomPrimaryKey()); } @Test public void createObject_defaultValueFromModelConstructor() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // Creates a DefaultValueConstructor with non-default primary key value. realm.createObject(DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); } }); final String createdRandomString = DefaultValueConstructor.lastRandomStringValue; testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_STRING, DefaultValueConstructor.FIELD_STRING_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_RANDOM_STRING, createdRandomString); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_SHORT, DefaultValueConstructor.FIELD_SHORT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_INT, DefaultValueConstructor.FIELD_INT_DEFAULT_VALUE); // Default value for pk must be ignored. testNoObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY, DefaultValueConstructor.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LONG, DefaultValueConstructor.FIELD_LONG_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_BYTE, DefaultValueConstructor.FIELD_BYTE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_FLOAT, DefaultValueConstructor.FIELD_FLOAT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_DOUBLE, DefaultValueConstructor.FIELD_DOUBLE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_BOOLEAN, DefaultValueConstructor.FIELD_BOOLEAN_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_DATE, DefaultValueConstructor.FIELD_DATE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_BINARY, DefaultValueConstructor.FIELD_BINARY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_OBJECT + "." + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueConstructor.class, DefaultValueConstructor.FIELD_LIST + "." + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); } @Test public void createObject_defaultValueSetterInConstructor() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { // Creates a DefaultValueSetter with non-default primary key value. realm.createObject(DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); } }); final String createdRandomString = DefaultValueSetter.lastRandomStringValue; testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_STRING, DefaultValueSetter.FIELD_STRING_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_RANDOM_STRING, createdRandomString); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_SHORT, DefaultValueSetter.FIELD_SHORT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_INT, DefaultValueSetter.FIELD_INT_DEFAULT_VALUE); // Default value for pk must be ignored. testNoObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY, DefaultValueSetter.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE * 3); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LONG, DefaultValueSetter.FIELD_LONG_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_BYTE, DefaultValueSetter.FIELD_BYTE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_FLOAT, DefaultValueSetter.FIELD_FLOAT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_DOUBLE, DefaultValueSetter.FIELD_DOUBLE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_BOOLEAN, DefaultValueSetter.FIELD_BOOLEAN_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_DATE, DefaultValueSetter.FIELD_DATE_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_BINARY, DefaultValueSetter.FIELD_BINARY_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_OBJECT + "." + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LIST + "." + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE); testOneObjectFound(realm, DefaultValueSetter.class, DefaultValueSetter.FIELD_LIST + "." + RandomPrimaryKey.FIELD_INT, RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); } @Test public void createObject_defaultValueFromOtherConstructor() { realm.beginTransaction(); DefaultValueFromOtherConstructor obj = realm.createObject(DefaultValueFromOtherConstructor.class); realm.commitTransaction(); assertEquals(42, obj.getFieldLong()); } @Test public void copyToRealm_defaultValuesAreIgnored() { final String fieldIgnoredValue = DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified"; final String fieldStringValue = DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + ".modified"; final String fieldRandomStringValue = "non-random"; final short fieldShortValue = (short) (DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE + 1); final int fieldIntValue = DefaultValueOfField.FIELD_INT_DEFAULT_VALUE + 1; final long fieldLongPrimaryKeyValue = DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1; final long fieldLongValue = DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE + 1; final byte fieldByteValue = (byte) (DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE + 1); final float fieldFloatValue = DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE + 1; final double fieldDoubleValue = DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE + 1; final boolean fieldBooleanValue = !DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE; final Date fieldDateValue = new Date(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE.getTime() + 1); final byte[] fieldBinaryValue = {(byte) (DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE[0] - 1)}; final int fieldObjectIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1; final int fieldListIntValue = RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2; final DefaultValueOfField managedObj; realm.beginTransaction(); { final DefaultValueOfField obj = new DefaultValueOfField(); obj.setFieldIgnored(fieldIgnoredValue); obj.setFieldString(fieldStringValue); obj.setFieldRandomString(fieldRandomStringValue); obj.setFieldShort(fieldShortValue); obj.setFieldInt(fieldIntValue); obj.setFieldLongPrimaryKey(fieldLongPrimaryKeyValue); obj.setFieldLong(fieldLongValue); obj.setFieldByte(fieldByteValue); obj.setFieldFloat(fieldFloatValue); obj.setFieldDouble(fieldDoubleValue); obj.setFieldBoolean(fieldBooleanValue); obj.setFieldDate(fieldDateValue); obj.setFieldBinary(fieldBinaryValue); final RandomPrimaryKey fieldObjectValue = new RandomPrimaryKey(); fieldObjectValue.setFieldInt(fieldObjectIntValue); obj.setFieldObject(fieldObjectValue); final RealmList<RandomPrimaryKey> list = new RealmList<RandomPrimaryKey>(); final RandomPrimaryKey listItem = new RandomPrimaryKey(); listItem.setFieldInt(fieldListIntValue); list.add(listItem); obj.setFieldList(list); managedObj = realm.copyToRealm(obj); } realm.commitTransaction(); assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE/*not fieldIgnoredValue*/, managedObj.getFieldIgnored()); assertEquals(fieldStringValue, managedObj.getFieldString()); assertEquals(fieldRandomStringValue, managedObj.getFieldRandomString()); assertEquals(fieldShortValue, managedObj.getFieldShort()); assertEquals(fieldIntValue, managedObj.getFieldInt()); assertEquals(fieldLongPrimaryKeyValue, managedObj.getFieldLongPrimaryKey()); assertEquals(fieldLongValue, managedObj.getFieldLong()); assertEquals(fieldByteValue, managedObj.getFieldByte()); assertEquals(fieldFloatValue, managedObj.getFieldFloat(), 0F); assertEquals(fieldDoubleValue, managedObj.getFieldDouble(), 0D); assertEquals(fieldBooleanValue, managedObj.isFieldBoolean()); assertEquals(fieldDateValue, managedObj.getFieldDate()); assertTrue(Arrays.equals(fieldBinaryValue, managedObj.getFieldBinary())); assertEquals(fieldObjectIntValue, managedObj.getFieldObject().getFieldInt()); assertEquals(1, managedObj.getFieldList().size()); assertEquals(fieldListIntValue, managedObj.getFieldList().first().getFieldInt()); // Makes sure that excess object by default value is not created. assertEquals(2, realm.where(RandomPrimaryKey.class).count()); } @Test public void copyFromRealm_defaultValuesAreIgnored() { final DefaultValueOfField managedObj; realm.beginTransaction(); { final DefaultValueOfField obj = new DefaultValueOfField(); obj.setFieldIgnored(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE + ".modified"); obj.setFieldString(DefaultValueOfField.FIELD_STRING_DEFAULT_VALUE + ".modified"); obj.setFieldRandomString("non-random"); obj.setFieldShort((short) (DefaultValueOfField.FIELD_SHORT_DEFAULT_VALUE + 1)); obj.setFieldInt(DefaultValueOfField.FIELD_INT_DEFAULT_VALUE + 1); obj.setFieldLongPrimaryKey(DefaultValueOfField.FIELD_LONG_PRIMARY_KEY_DEFAULT_VALUE + 1); obj.setFieldLong(DefaultValueOfField.FIELD_LONG_DEFAULT_VALUE + 1); obj.setFieldByte((byte) (DefaultValueOfField.FIELD_BYTE_DEFAULT_VALUE + 1)); obj.setFieldFloat(DefaultValueOfField.FIELD_FLOAT_DEFAULT_VALUE + 1); obj.setFieldDouble(DefaultValueOfField.FIELD_DOUBLE_DEFAULT_VALUE + 1); obj.setFieldBoolean(!DefaultValueOfField.FIELD_BOOLEAN_DEFAULT_VALUE); obj.setFieldDate(new Date(DefaultValueOfField.FIELD_DATE_DEFAULT_VALUE.getTime() + 1)); obj.setFieldBinary(new byte[] {(byte) (DefaultValueOfField.FIELD_BINARY_DEFAULT_VALUE[0] - 1)}); final RandomPrimaryKey fieldObjectValue = new RandomPrimaryKey(); fieldObjectValue.setFieldInt(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 1); obj.setFieldObject(fieldObjectValue); final RealmList<RandomPrimaryKey> list = new RealmList<RandomPrimaryKey>(); final RandomPrimaryKey listItem = new RandomPrimaryKey(); listItem.setFieldInt(RandomPrimaryKey.FIELD_INT_DEFAULT_VALUE + 2); list.add(listItem); obj.setFieldList(list); managedObj = realm.copyToRealm(obj); } realm.commitTransaction(); final DefaultValueOfField copy = realm.copyFromRealm(managedObj); assertEquals(DefaultValueOfField.FIELD_IGNORED_DEFAULT_VALUE, copy.getFieldIgnored()); assertEquals(managedObj.getFieldString(), copy.getFieldString()); assertEquals(managedObj.getFieldRandomString(), copy.getFieldRandomString()); assertEquals(managedObj.getFieldShort(), copy.getFieldShort()); assertEquals(managedObj.getFieldInt(), copy.getFieldInt()); assertEquals(managedObj.getFieldLongPrimaryKey(), copy.getFieldLongPrimaryKey()); assertEquals(managedObj.getFieldLong(), copy.getFieldLong()); assertEquals(managedObj.getFieldByte(), copy.getFieldByte()); assertEquals(managedObj.getFieldFloat(), copy.getFieldFloat(), 0F); assertEquals(managedObj.getFieldDouble(), copy.getFieldDouble(), 0D); assertEquals(managedObj.isFieldBoolean(), copy.isFieldBoolean()); assertEquals(managedObj.getFieldDate(), copy.getFieldDate()); assertTrue(Arrays.equals(managedObj.getFieldBinary(), copy.getFieldBinary())); assertEquals(managedObj.getFieldObject().getFieldInt(), copy.getFieldObject().getFieldInt()); assertEquals(1, copy.getFieldList().size()); assertEquals(managedObj.getFieldList().first().getFieldInt(), copy.getFieldList().first().getFieldInt()); } // Tests close Realm in another thread different from where it is created. @Test public void close_differentThread() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AssertionFailedError threadAssertionError[] = new AssertionFailedError[1]; final Thread thatThread = new Thread(new Runnable() { @Override public void run() { try { realm.close(); threadAssertionError[0] = new AssertionFailedError( "Close realm in a different thread should throw IllegalStateException."); } catch (IllegalStateException ignored) { } latch.countDown(); } }); thatThread.start(); // Timeout should never happen. TestHelper.awaitOrFail(latch); if (threadAssertionError[0] != null) { throw threadAssertionError[0]; } // After exception thrown in another thread, nothing should be changed to the realm in this thread. realm.checkIfValid(); realm.close(); realm = null; } @Test public void isClosed() { assertFalse(realm.isClosed()); realm.close(); assertTrue(realm.isClosed()); } // Tests Realm#isClosed() in another thread different from where it is created. @Test public void isClosed_differentThread() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AssertionFailedError threadAssertionError[] = new AssertionFailedError[1]; final Thread thatThread = new Thread(new Runnable() { @Override public void run() { try { realm.isClosed(); threadAssertionError[0] = new AssertionFailedError( "Call isClosed() of Realm instance in a different thread should throw IllegalStateException."); } catch (IllegalStateException ignored) { } latch.countDown(); } }); thatThread.start(); // Timeout should never happen. TestHelper.awaitOrFail(latch); if (threadAssertionError[0] != null) { throw threadAssertionError[0]; } // After exception thrown in another thread, nothing should be changed to the realm in this thread. realm.checkIfValid(); assertFalse(realm.isClosed()); realm.close(); } // Realm validation & initialization is done once, still ColumnIndices // should be populated for the subsequent Realm sharing the same configuration // even if we skip initialization & validation. @Test public void columnIndicesIsPopulatedWhenSkippingInitialization() throws Throwable { final RealmConfiguration realmConfiguration = configFactory.createConfiguration("columnIndices"); final Exception threadError[] = new Exception[1]; final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch mainThreadRealmDone = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfiguration); // This will populate columnIndices. try { bgRealmOpened.countDown(); TestHelper.awaitOrFail(mainThreadRealmDone); realm.close(); bgRealmClosed.countDown(); } catch (Exception e) { threadError[0] = e; } finally { if (!realm.isClosed()) { realm.close(); } } } }).start(); TestHelper.awaitOrFail(bgRealmOpened); Realm realm = Realm.getInstance(realmConfiguration); realm.where(AllTypes.class).equalTo("columnString", "Foo").findAll(); // This would crash if columnIndices == null. realm.close(); mainThreadRealmDone.countDown(); TestHelper.awaitOrFail(bgRealmClosed); if (threadError[0] != null) { throw threadError[0]; } } @Test public void isInTransaction() { assertFalse(realm.isInTransaction()); realm.beginTransaction(); assertTrue(realm.isInTransaction()); realm.commitTransaction(); assertFalse(realm.isInTransaction()); realm.beginTransaction(); assertTrue(realm.isInTransaction()); realm.cancelTransaction(); assertFalse(realm.isInTransaction()); } // Test for https://github.com/realm/realm-java/issues/1646 @Test public void closingRealmWhileOtherThreadIsOpeningRealm() throws Exception { final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch endLatch = new CountDownLatch(1); final List<Exception> exception = new ArrayList<Exception>(); new Thread() { @Override public void run() { try { startLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); return; } final Realm realm = Realm.getInstance(realmConfig); try { realm.where(AllTypes.class).equalTo("columnLong", 0L).findFirst(); } catch (Exception e) { exception.add(e); } finally { endLatch.countDown(); realm.close(); } } }.start(); // Prevents for another thread to enter Realm.createAndValidate(). synchronized (BaseRealm.class) { startLatch.countDown(); // Waits for another thread's entering Realm.createAndValidate(). SystemClock.sleep(100L); realm.close(); realm = null; } TestHelper.awaitOrFail(endLatch); if (!exception.isEmpty()) { throw exception.get(0); } } // Bug reported https://github.com/realm/realm-java/issues/1728. // Root cause is validatedRealmFiles will be cleaned when any thread's Realm ref counter reach 0. @Test public void openRealmWhileTransactionInAnotherThread() throws Exception { final CountDownLatch realmOpenedInBgLatch = new CountDownLatch(1); final CountDownLatch realmClosedInFgLatch = new CountDownLatch(1); final CountDownLatch transBeganInBgLatch = new CountDownLatch(1); final CountDownLatch fgFinishedLatch = new CountDownLatch(1); final CountDownLatch bgFinishedLatch = new CountDownLatch(1); final List<Exception> exception = new ArrayList<Exception>(); // Step 1: testRealm is opened already. Thread thread = new Thread(new Runnable() { @Override public void run() { // Step 2: Opens realm in background thread. Realm realm = Realm.getInstance(realmConfig); realmOpenedInBgLatch.countDown(); try { realmClosedInFgLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); realm.close(); return; } // Step 4: Starts transaction in background. realm.beginTransaction(); transBeganInBgLatch.countDown(); try { fgFinishedLatch.await(TestHelper.STANDARD_WAIT_SECS, TimeUnit.SECONDS); } catch (InterruptedException e) { exception.add(e); } // Step 6: Cancels Transaction and closes realm in background. realm.cancelTransaction(); realm.close(); bgFinishedLatch.countDown(); } }); thread.start(); TestHelper.awaitOrFail(realmOpenedInBgLatch); // Step 3: Closes all realm instances in foreground thread. realm.close(); realmClosedInFgLatch.countDown(); TestHelper.awaitOrFail(transBeganInBgLatch); // Step 5: Gets a new Realm instance in foreground. realm = Realm.getInstance(realmConfig); fgFinishedLatch.countDown(); TestHelper.awaitOrFail(bgFinishedLatch); if (!exception.isEmpty()) { throw exception.get(0); } } @Test public void isEmpty() { RealmConfiguration realmConfig = configFactory.createConfiguration("empty_test.realm"); Realm emptyRealm = Realm.getInstance(realmConfig); assertTrue(emptyRealm.isEmpty()); emptyRealm.beginTransaction(); PrimaryKeyAsLong obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName("Foo"); emptyRealm.copyToRealm(obj); assertFalse(emptyRealm.isEmpty()); emptyRealm.cancelTransaction(); assertTrue(emptyRealm.isEmpty()); emptyRealm.beginTransaction(); obj = new PrimaryKeyAsLong(); obj.setId(1); obj.setName("Foo"); emptyRealm.copyToRealm(obj); emptyRealm.commitTransaction(); assertFalse(emptyRealm.isEmpty()); emptyRealm.close(); } @Test public void copyFromRealm_invalidObjectThrows() { realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); obj.deleteFromRealm(); realm.commitTransaction(); try { realm.copyFromRealm(obj); fail(); } catch (IllegalArgumentException ignored) { } try { realm.copyFromRealm(new AllTypes()); fail(); } catch (IllegalArgumentException ignored) { } } @Test public void copyFromRealm_invalidDepthThrows() { realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyFromRealm(obj, -1); } @Test public void copyFromRealm() { populateTestRealm(); AllTypes realmObject = realm.where(AllTypes.class).findAllSorted("columnLong").first(); AllTypes unmanagedObject = realm.copyFromRealm(realmObject); assertArrayEquals(realmObject.getColumnBinary(), unmanagedObject.getColumnBinary()); assertEquals(realmObject.getColumnString(), unmanagedObject.getColumnString()); assertEquals(realmObject.getColumnLong(), unmanagedObject.getColumnLong()); assertEquals(realmObject.getColumnFloat(), unmanagedObject.getColumnFloat(), 0.00000000001); assertEquals(realmObject.getColumnDouble(), unmanagedObject.getColumnDouble(), 0.00000000001); assertEquals(realmObject.isColumnBoolean(), unmanagedObject.isColumnBoolean()); assertEquals(realmObject.getColumnDate(), unmanagedObject.getColumnDate()); } @Test public void copyFromRealm_newCopyEachTime() { populateTestRealm(); AllTypes realmObject = realm.where(AllTypes.class).findAllSorted("columnLong").first(); AllTypes unmanagedObject1 = realm.copyFromRealm(realmObject); AllTypes unmanagedObject2 = realm.copyFromRealm(realmObject); assertFalse(unmanagedObject1 == unmanagedObject2); assertNotSame(unmanagedObject1, unmanagedObject2); } // Tests that the object graph is copied as it is and no extra copies are made. // 1) (A -> B/[B,C]) // 2) (C -> B/[B,A]) // A copy should result in only 3 distinct objects. @Test public void copyFromRealm_cyclicObjectGraph() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName("A"); CyclicType objB = realm.createObject(CyclicType.class); objB.setName("B"); CyclicType objC = realm.createObject(CyclicType.class); objC.setName("C"); objA.setObject(objB); objC.setObject(objB); objA.getObjects().add(objB); objA.getObjects().add(objC); objC.getObjects().add(objB); objC.getObjects().add(objA); realm.commitTransaction(); CyclicType copyA = realm.copyFromRealm(objA); CyclicType copyB = copyA.getObject(); CyclicType copyC = copyA.getObjects().get(1); assertEquals("A", copyA.getName()); assertEquals("B", copyB.getName()); assertEquals("C", copyC.getName()); // Asserts object equality on the object graph. assertTrue(copyA.getObject() == copyC.getObject()); assertTrue(copyA.getObjects().get(0) == copyC.getObjects().get(0)); assertTrue(copyA == copyC.getObjects().get(1)); assertTrue(copyC == copyA.getObjects().get(1)); } // Tests that for (A -> B -> C) for maxDepth = 1, result is (A -> B -> null). @Test public void copyFromRealm_checkMaxDepth() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName("A"); CyclicType objB = realm.createObject(CyclicType.class); objB.setName("B"); CyclicType objC = realm.createObject(CyclicType.class); objC.setName("C"); objA.setObject(objB); objC.setObject(objC); objA.getObjects().add(objB); objA.getObjects().add(objC); realm.commitTransaction(); CyclicType copyA = realm.copyFromRealm(objA, 1); assertNull(copyA.getObject().getObject()); } // Tests that depth restriction is calculated from the top-most encountered object, i.e. it is possible for some // objects to exceed the depth limit. // A -> B -> C -> D -> E // A -> D -> E // D is both at depth 1 and 3. For maxDepth = 3, E should still be copied. @Test public void copyFromRealm_sameObjectDifferentDepths() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName("A"); CyclicType objB = realm.createObject(CyclicType.class); objB.setName("B"); CyclicType objC = realm.createObject(CyclicType.class); objC.setName("C"); CyclicType objD = realm.createObject(CyclicType.class); objD.setName("D"); CyclicType objE = realm.createObject(CyclicType.class); objE.setName("E"); objA.setObject(objB); objB.setObject(objC); objC.setObject(objD); objD.setObject(objE); objA.setOtherObject(objD); realm.commitTransaction(); // Object is filled before otherObject. (because of field order - WARNING: Not guaranteed) // This means that the object will be encountered first time at max depth, so E will not be copied. // If the object cache does not handle this, otherObject will be wrong. CyclicType copyA = realm.copyFromRealm(objA, 3); assertEquals("E", copyA.getOtherObject().getObject().getName()); } @Test public void copyFromRealm_list_invalidListThrows() { realm.beginTransaction(); AllTypes object = realm.createObject(AllTypes.class); List<AllTypes> list = new RealmList<AllTypes>(object); object.deleteFromRealm(); realm.commitTransaction(); thrown.expect(IllegalArgumentException.class); realm.copyFromRealm(list); } @Test public void copyFromRealm_list_invalidDepthThrows() { RealmResults<AllTypes> results = realm.where(AllTypes.class).findAll(); thrown.expect(IllegalArgumentException.class); realm.copyFromRealm(results, -1); } // Tests that the same Realm objects in a list result in the same Java in-memory copy. // List: A -> [(B -> C), (B -> C)] should result in only 2 copied objects A and B and not A1, B1, A2, B2 @Test public void copyFromRealm_list_sameElements() { realm.beginTransaction(); CyclicType objA = realm.createObject(CyclicType.class); objA.setName("A"); CyclicType objB = realm.createObject(CyclicType.class); objB.setName("B"); CyclicType objC = realm.createObject(CyclicType.class); objC.setName("C"); objB.setObject(objC); objA.getObjects().add(objB); objA.getObjects().add(objB); realm.commitTransaction(); List<CyclicType> results = realm.copyFromRealm(objA.getObjects()); assertEquals(2, results.size()); assertEquals("B", results.get(0).getName()); assertTrue(results.get(0) == results.get(1)); } @Test public void copyFromRealm_dynamicRealmObjectThrows() { realm.beginTransaction(); AllTypes obj = realm.createObject(AllTypes.class); realm.commitTransaction(); DynamicRealmObject dObj = new DynamicRealmObject(obj); try { realm.copyFromRealm(dObj); fail(); } catch (IllegalArgumentException ignored) { } } @Test public void copyFromRealm_dynamicRealmListThrows() { DynamicRealm dynamicRealm = DynamicRealm.getInstance(realm.getConfiguration()); dynamicRealm.beginTransaction(); RealmList<DynamicRealmObject> dynamicList = dynamicRealm.createObject(AllTypes.CLASS_NAME).getList(AllTypes.FIELD_REALMLIST); DynamicRealmObject dObj = dynamicRealm.createObject(Dog.CLASS_NAME); dynamicList.add(dObj); dynamicRealm.commitTransaction(); try { realm.copyFromRealm(dynamicList); fail(); } catch (IllegalArgumentException ignored) { } finally { dynamicRealm.close(); } } // Tests if close can be called from Realm change listener when there is no other listeners. @Test @RunTestInLooperThread public void closeRealmInChangeListener() { final Realm realm = looperThread.getRealm(); final RealmChangeListener<Realm> listener = new RealmChangeListener<Realm>() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 1) { realm.removeChangeListener(this); realm.close(); looperThread.testComplete(); } } }; realm.addChangeListener(listener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); } // Tests if close can be called from Realm change listener when there is a listener on empty Realm Object. @Test @RunTestInLooperThread public void closeRealmInChangeListenerWhenThereIsListenerOnEmptyObject() { final Realm realm = looperThread.getRealm(); final RealmChangeListener<AllTypes> dummyListener = new RealmChangeListener<AllTypes>() { @Override public void onChange(AllTypes object) { } }; // Change listener on Realm final RealmChangeListener<Realm> listener = new RealmChangeListener<Realm>() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 1) { realm.removeChangeListener(this); realm.close(); looperThread.postRunnable(new Runnable() { @Override public void run() { looperThread.testComplete(); } }); } } }; realm.addChangeListener(listener); // Change listener on Empty Object final AllTypes allTypes = realm.where(AllTypes.class).findFirstAsync(); allTypes.addChangeListener(dummyListener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); } // Tests if close can be called from Realm change listener when there is an listener on non-empty Realm Object. @Test @RunTestInLooperThread public void closeRealmInChangeListenerWhenThereIsListenerOnObject() { final Realm realm = looperThread.getRealm(); final RealmChangeListener<AllTypes> dummyListener = new RealmChangeListener<AllTypes>() { @Override public void onChange(AllTypes object) { } }; final RealmChangeListener<Realm> listener = new RealmChangeListener<Realm>() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 2) { realm.removeChangeListener(this); realm.close(); // Ends test after next looper event to ensure that all listeners were called. looperThread.postRunnable(new Runnable() { @Override public void run() { looperThread.testComplete(); } }); } } }; realm.addChangeListener(listener); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); // Change listener on Realm Object. final AllTypes allTypes = realm.where(AllTypes.class).findFirst(); allTypes.addChangeListener(dummyListener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); } // Tests if close can be called from Realm change listener when there is an listener on RealmResults. @Test @RunTestInLooperThread public void closeRealmInChangeListenerWhenThereIsListenerOnResults() { final Realm realm = looperThread.getRealm(); final RealmChangeListener<RealmResults<AllTypes>> dummyListener = new RealmChangeListener<RealmResults<AllTypes>>() { @Override public void onChange(RealmResults<AllTypes> object) { } }; final RealmChangeListener<Realm> listener = new RealmChangeListener<Realm>() { @Override public void onChange(Realm object) { if (realm.where(AllTypes.class).count() == 1) { realm.removeChangeListener(this); realm.close(); looperThread.postRunnable(new Runnable() { @Override public void run() { looperThread.testComplete(); } }); } } }; realm.addChangeListener(listener); // Change listener on Realm results. RealmResults<AllTypes> results = realm.where(AllTypes.class).findAll(); results.addChangeListener(dummyListener); realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.createObject(AllTypes.class); } }); } @Test @RunTestInLooperThread public void addChangeListener_throwOnAddingNullListenerFromLooperThread() { final Realm realm = looperThread.getRealm(); try { realm.addChangeListener(null); fail("adding null change listener must throw an exception."); } catch (IllegalArgumentException ignore) { } finally { looperThread.testComplete(); } } @Test public void addChangeListener_throwOnAddingNullListenerFromNonLooperThread() throws Throwable { TestHelper.executeOnNonLooperThread(new TestHelper.Task() { @Override public void run() throws Exception { final Realm realm = Realm.getInstance(realmConfig); //noinspection TryFinallyCanBeTryWithResources try { realm.addChangeListener(null); fail("adding null change listener must throw an exception."); } catch (IllegalArgumentException ignore) { } finally { realm.close(); } } }); } @Test @RunTestInLooperThread public void removeChangeListener_throwOnRemovingNullListenerFromLooperThread() { final Realm realm = looperThread.getRealm(); try { realm.removeChangeListener(null); fail("removing null change listener must throw an exception."); } catch (IllegalArgumentException ignore) { } finally { looperThread.testComplete(); } } @Test public void removeChangeListener_throwOnRemovingNullListenerFromNonLooperThread() throws Throwable { TestHelper.executeOnNonLooperThread(new TestHelper.Task() { @Override public void run() throws Exception { final Realm realm = Realm.getInstance(realmConfig); //noinspection TryFinallyCanBeTryWithResources try { realm.removeChangeListener(null); fail("removing null change listener must throw an exception."); } catch (IllegalArgumentException ignore) { } finally { realm.close(); } } }); } @Test public void removeChangeListenerThrowExceptionOnNonLooperThread() { final CountDownLatch signalTestFinished = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); try { realm.removeChangeListener(new RealmChangeListener<Realm>() { @Override public void onChange(Realm object) { } }); fail("Should not be able to invoke removeChangeListener"); } catch (IllegalStateException ignored) { } finally { realm.close(); signalTestFinished.countDown(); } } }); thread.start(); try { TestHelper.awaitOrFail(signalTestFinished); } finally { thread.interrupt(); } } @Test public void removeAllChangeListenersThrowExceptionOnNonLooperThread() { final CountDownLatch signalTestFinished = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); try { realm.removeAllChangeListeners(); fail("Should not be able to invoke removeChangeListener"); } catch (IllegalStateException ignored) { } finally { realm.close(); signalTestFinished.countDown(); } } }); thread.start(); try { TestHelper.awaitOrFail(signalTestFinished); } finally { thread.interrupt(); } } @Test public void deleteAll() { realm.beginTransaction(); realm.createObject(AllTypes.class); realm.createObject(Owner.class).setCat(realm.createObject(Cat.class)); realm.commitTransaction(); assertEquals(1, realm.where(AllTypes.class).count()); assertEquals(1, realm.where(Owner.class).count()); assertEquals(1, realm.where(Cat.class).count()); realm.beginTransaction(); realm.deleteAll(); realm.commitTransaction(); assertEquals(0, realm.where(AllTypes.class).count()); assertEquals(0, realm.where(Owner.class).count()); assertEquals(0, realm.where(Cat.class).count()); assertTrue(realm.isEmpty()); } @Test public void waitForChange_emptyDataChange() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0); // Waits in background. final CountDownLatch signalTestFinished = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmWaitForChangeResult.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); realm.beginTransaction(); realm.commitTransaction(); TestHelper.awaitOrFail(bgRealmClosed); assertTrue(bgRealmChangeResult.get()); assertEquals(0, bgRealmWaitForChangeResult.get()); } @Test public void waitForChange_withDataChange() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0); // Waits in background. final CountDownLatch signalTestFinished = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmWaitForChangeResult.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); populateTestRealm(); TestHelper.awaitOrFail(bgRealmClosed); assertTrue(bgRealmChangeResult.get()); assertEquals(TEST_DATA_SIZE, bgRealmWaitForChangeResult.get()); } @Test public void waitForChange_syncBackgroundRealmResults() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmResultSize = new AtomicLong(0); // Wait in background final CountDownLatch signalTestFinished = new CountDownLatch(1); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); RealmResults<AllTypes> results = realm.where(AllTypes.class).findAll(); // First makes sure the results is empty. bgRealmResultSize.set(results.size()); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmResultSize.set(results.size()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); // Background result should be empty. assertEquals(0, bgRealmResultSize.get()); populateTestRealm(); TestHelper.awaitOrFail(bgRealmClosed); assertTrue(bgRealmChangeResult.get()); // Once RealmResults are synchronized after waitForChange, the result size should be what we expect. assertEquals(TEST_DATA_SIZE, bgRealmResultSize.get()); } @Test public void stopWaitForChange() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(true); final AtomicReference<Realm> bgRealm = new AtomicReference<Realm>(); // Waits in background. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmOpened.countDown(); bgRealmChangeResult.set(realm.waitForChange()); realm.close(); bgRealmClosed.countDown(); } }).start(); TestHelper.awaitOrFail(bgRealmOpened); Thread.sleep(200); bgRealm.get().stopWaitForChange(); TestHelper.awaitOrFail(bgRealmClosed); assertFalse(bgRealmChangeResult.get()); } // Tests if waitForChange doesn't blocks once stopWaitForChange has been called before. @Test public void waitForChange_stopWaitForChangeDisablesWaiting() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmStopped = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicBoolean bgRealmFirstWaitResult = new AtomicBoolean(true); final AtomicBoolean bgRealmSecondWaitResult = new AtomicBoolean(false); final AtomicReference<Realm> bgRealm = new AtomicReference<Realm>(); // Waits in background. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmOpened.countDown(); bgRealmFirstWaitResult.set(realm.waitForChange()); bgRealmStopped.countDown(); bgRealmSecondWaitResult.set(realm.waitForChange()); realm.close(); bgRealmClosed.countDown(); } }).start(); TestHelper.awaitOrFail(bgRealmOpened); bgRealm.get().stopWaitForChange(); TestHelper.awaitOrFail(bgRealmStopped); assertFalse(bgRealmFirstWaitResult.get()); TestHelper.awaitOrFail(bgRealmClosed); assertFalse(bgRealmSecondWaitResult.get()); } // Tests if waitForChange still blocks if stopWaitForChange has been called for a realm in a different thread. @Test public void waitForChange_blockSpecificThreadOnly() throws InterruptedException { final CountDownLatch bgRealmsOpened = new CountDownLatch(2); final CountDownLatch bgRealmsClosed = new CountDownLatch(2); final AtomicBoolean bgRealmFirstWaitResult = new AtomicBoolean(true); final AtomicBoolean bgRealmSecondWaitResult = new AtomicBoolean(false); final AtomicLong bgRealmWaitForChangeResult = new AtomicLong(0); final AtomicReference<Realm> bgRealm = new AtomicReference<Realm>(); // Waits in background. Thread thread1 = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmsOpened.countDown(); bgRealmFirstWaitResult.set(realm.waitForChange()); realm.close(); bgRealmsClosed.countDown(); } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealmsOpened.countDown(); bgRealmSecondWaitResult.set(realm.waitForChange()); bgRealmWaitForChangeResult.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmsClosed.countDown(); } }); thread1.start(); thread2.start(); TestHelper.awaitOrFail(bgRealmsOpened); bgRealm.get().stopWaitForChange(); // Waits for Thread 2 to wait. Thread.sleep(500); populateTestRealm(); TestHelper.awaitOrFail(bgRealmsClosed); assertFalse(bgRealmFirstWaitResult.get()); assertTrue(bgRealmSecondWaitResult.get()); assertEquals(TEST_DATA_SIZE, bgRealmWaitForChangeResult.get()); } // Checks if waitForChange() does not respond to Thread.interrupt(). @Test public void waitForChange_interruptingThread() throws InterruptedException { final CountDownLatch bgRealmOpened = new CountDownLatch(1); final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicReference<Boolean> bgRealmWaitResult = new AtomicReference<Boolean>(); final AtomicReference<Realm> bgRealm = new AtomicReference<Realm>(); // Waits in background. Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); bgRealmOpened.countDown(); bgRealmWaitResult.set(realm.waitForChange()); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmOpened); // Makes sure background thread goes to wait. Thread.sleep(500); // Interrupting a thread should neither cause any side effect nor terminate the Background Realm from waiting. thread.interrupt(); assertTrue(thread.isInterrupted()); assertEquals(null, bgRealmWaitResult.get()); // Now we'll stop realm from waiting. bgRealm.get().stopWaitForChange(); TestHelper.awaitOrFail(bgRealmClosed); assertFalse(bgRealmWaitResult.get()); } @Test public void waitForChange_onLooperThread() throws Throwable { final CountDownLatch bgRealmClosed = new CountDownLatch(1); final ExceptionHolder bgError = new ExceptionHolder(); Thread thread = new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Realm realm = Realm.getInstance(realmConfig); try { realm.waitForChange(); fail(); } catch (Throwable expected) { bgError.setException(expected); } finally { realm.close(); bgRealmClosed.countDown(); } } }); thread.start(); TestHelper.awaitOrFail(bgRealmClosed); if (bgError.getException() instanceof AssertionError) { throw bgError.getException(); } assertEquals(IllegalStateException.class, bgError.getException().getClass()); } // Cannot wait inside of a transaction. @Test(expected = IllegalStateException.class) public void waitForChange_illegalWaitInsideTransaction() { realm.beginTransaction(); realm.waitForChange(); } @Test public void waitForChange_stopWaitingOnClosedRealmThrows() throws InterruptedException { final CountDownLatch bgRealmClosed = new CountDownLatch(1); final AtomicReference<Realm> bgRealm = new AtomicReference<Realm>(); Thread thread = new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(realmConfig); bgRealm.set(realm); realm.close(); bgRealmClosed.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmClosed); try { bgRealm.get().stopWaitForChange(); fail("Cannot stop a closed Realm from waiting"); } catch (IllegalStateException expected) { } } // waitForChange & stopWaitForChange within a simple Thread wrapper. @Test public void waitForChange_runWithRealmThread() throws InterruptedException { final CountDownLatch bgRealmStarted = new CountDownLatch(1); final CountDownLatch bgRealmFished = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(false); final AtomicLong bgRealmResultSize = new AtomicLong(0); RealmThread thread = new RealmThread(realmConfig, new RealmThread.RealmRunnable() { @Override public void run(Realm realm) { bgRealmStarted.countDown(); bgRealmChangeResult.set(realm.waitForChange()); bgRealmResultSize.set(realm.where(AllTypes.class).count()); realm.close(); bgRealmFished.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmStarted); populateTestRealm(); TestHelper.awaitOrFail(bgRealmFished); assertTrue(bgRealmChangeResult.get()); assertEquals(TEST_DATA_SIZE, bgRealmResultSize.get()); } @Test public void waitForChange_endRealmThread() throws InterruptedException { final CountDownLatch bgRealmStarted = new CountDownLatch(1); final CountDownLatch bgRealmFished = new CountDownLatch(1); final AtomicBoolean bgRealmChangeResult = new AtomicBoolean(true); RealmThread thread = new RealmThread(realmConfig, new RealmThread.RealmRunnable() { @Override public void run(Realm realm) { bgRealmStarted.countDown(); bgRealmChangeResult.set(realm.waitForChange()); realm.close(); bgRealmFished.countDown(); } }); thread.start(); TestHelper.awaitOrFail(bgRealmStarted); thread.end(); TestHelper.awaitOrFail(bgRealmFished); assertFalse(bgRealmChangeResult.get()); } @Test public void schemaIndexCacheIsUpdatedAfterSchemaChange() { final AtomicLong nameIndexNew = new AtomicLong(-1L); // get the pre-update index for the "name" column. CatRealmProxy.CatColumnInfo catColumnInfo = (CatRealmProxy.CatColumnInfo) realm.schema.getColumnInfo(Cat.class); final long nameIndex = catColumnInfo.nameIndex; // Change the index of the column "name". realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { final Table catTable = realm.getSchema().getTable(Cat.CLASS_NAME); final long nameIndex = catTable.getColumnIndex(Cat.FIELD_NAME); catTable.removeColumn(nameIndex); final long newIndex = catTable.addColumn(RealmFieldType.STRING, Cat.FIELD_NAME, true); realm.setVersion(realm.getConfiguration().getSchemaVersion() + 1); nameIndexNew.set(newIndex); } }); // We need to update index cache if the schema version was changed in the same thread. realm.sharedRealm.invokeSchemaChangeListenerIfSchemaChanged(); // Verify that the index has changed. assertNotEquals(nameIndex, nameIndexNew); // Verify that the index in the ColumnInfo has been updated. catColumnInfo = (CatRealmProxy.CatColumnInfo) realm.schema.getColumnInfo(Cat.class); assertEquals(nameIndexNew.get(), catColumnInfo.nameIndex); assertEquals(nameIndexNew.get(), (long) catColumnInfo.getColumnIndex(Cat.FIELD_NAME)); // Checks by actual get and set. realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { final Cat cat = realm.createObject(Cat.class); cat.setName("pochi"); } }); //noinspection ConstantConditions assertEquals("pochi", realm.where(Cat.class).findFirst().getName()); } @Test public void getGlobalInstanceCount() { final CountDownLatch bgDone = new CountDownLatch(1); final RealmConfiguration config = configFactory.createConfiguration("globalCountTest"); assertEquals(0, Realm.getGlobalInstanceCount(config)); // Opens thread local Realm. Realm realm = Realm.getInstance(config); assertEquals(1, Realm.getGlobalInstanceCount(config)); // Opens thread local DynamicRealm. DynamicRealm dynRealm = DynamicRealm.getInstance(config); assertEquals(2, Realm.getGlobalInstanceCount(config)); // Opens Realm in another thread. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(config); assertEquals(3, Realm.getGlobalInstanceCount(config)); realm.close(); assertEquals(2, Realm.getGlobalInstanceCount(config)); bgDone.countDown(); } }).start(); TestHelper.awaitOrFail(bgDone); dynRealm.close(); assertEquals(1, Realm.getGlobalInstanceCount(config)); realm.close(); assertEquals(0, Realm.getGlobalInstanceCount(config)); } @Test public void getLocalInstanceCount() { final RealmConfiguration config = configFactory.createConfiguration("localInstanceCount"); assertEquals(0, Realm.getLocalInstanceCount(config)); // Opens thread local Realm. Realm realm = Realm.getInstance(config); assertEquals(1, Realm.getLocalInstanceCount(config)); // Opens thread local DynamicRealm. DynamicRealm dynRealm = DynamicRealm.getInstance(config); assertEquals(2, Realm.getLocalInstanceCount(config)); dynRealm.close(); assertEquals(1, Realm.getLocalInstanceCount(config)); realm.close(); assertEquals(0, Realm.getLocalInstanceCount(config)); } @Test public void namedPipeDirForExternalStorage() { // Test for https://github.com/realm/realm-java/issues/3140 realm.close(); realm = null; final File namedPipeDir = SharedRealm.getTemporaryDirectory(); assertTrue(namedPipeDir.isDirectory()); TestHelper.deleteRecursively(namedPipeDir); //noinspection ResultOfMethodCallIgnored namedPipeDir.mkdirs(); final File externalFilesDir = context.getExternalFilesDir(null); final RealmConfiguration config = new RealmConfiguration.Builder() .directory(externalFilesDir) .name("external.realm") .build(); Realm.deleteRealm(config); // Test if it works when the namedPipeDir is empty. Realm realmOnExternalStorage = Realm.getInstance(config); realmOnExternalStorage.close(); assertTrue(namedPipeDir.isDirectory()); Assume.assumeTrue("SELinux is not enforced on this device.", TestHelper.isSelinuxEnforcing()); // Only checks the fifo file created by call, since all Realm instances share the same fifo created by // external_commit_helper which might not be created in the newly created dir if there are Realm instances // are not deleted when TestHelper.deleteRecursively(namedPipeDir) called. File[] files = namedPipeDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.matches("realm_.*cv"); } }); assertEquals(2, files.length); // Tests if it works when the namedPipeDir and the named pipe files already exist. realmOnExternalStorage = Realm.getInstance(config); realmOnExternalStorage.close(); } @Test(expected = IllegalStateException.class) public void getInstanceAsync_nonLooperThreadShouldThrow() { Realm.getInstanceAsync(realmConfig, new Realm.Callback() { @Override public void onSuccess(Realm realm) { fail(); } }); } @Test @RunTestInLooperThread public void getInstanceAsync_nullConfigShouldThrow() { thrown.expect(IllegalArgumentException.class); Realm.getInstanceAsync(null, new Realm.Callback() { @Override public void onSuccess(Realm realm) { fail(); } }); } @Test @RunTestInLooperThread public void getInstanceAsync_nullCallbackShouldThrow() { thrown.expect(IllegalArgumentException.class); Realm.getInstanceAsync(realmConfig, null); } // Verify that the logic for waiting for the users file dir to be come available isn't totally broken // This is pretty hard to test, so forced to break encapsulation in this case. @Test public void init_waitForFilesDir() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException { java.lang.reflect.Method m = Realm.class.getDeclaredMethod("checkFilesDirAvailable", Context.class); m.setAccessible(true); // A) Check it fails if getFilesDir is never created Context mockContext = mock(Context.class); when(mockContext.getFilesDir()).thenReturn(null); try { m.invoke(null, mockContext); fail(); } catch (InvocationTargetException e) { assertEquals(IllegalStateException.class, e.getCause().getClass()); } // B) Check we return if the filesDir becomes available after a while mockContext = mock(Context.class); when(mockContext.getFilesDir()).then(new Answer<File>() { int calls = 0; File userFolder = tmpFolder.newFolder(); @Override public File answer(InvocationOnMock invocationOnMock) throws Throwable { calls++; return (calls > 5) ? userFolder : null; // Start returning the correct folder after 5 attempts } }); assertNull(m.invoke(null, mockContext)); } @Test @RunTestInLooperThread public void refresh_triggerNotifications() { final CountDownLatch bgThreadDone = new CountDownLatch(1); final AtomicBoolean listenerCalled = new AtomicBoolean(false); Realm realm = looperThread.getRealm(); RealmResults<AllTypes> results = realm.where(AllTypes.class).findAll(); assertEquals(0, results.size()); results.addChangeListener(new RealmChangeListener<RealmResults<AllTypes>>() { @Override public void onChange(RealmResults<AllTypes> results) { assertEquals(1, results.size()); listenerCalled.set(true); } }); // Advance the Realm on a background while blocking this thread. When we refresh, it should trigger // the listener. new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(looperThread.getConfiguration()); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); realm.close(); bgThreadDone.countDown(); } }).run(); TestHelper.awaitOrFail(bgThreadDone); realm.refresh(); assertTrue(listenerCalled.get()); looperThread.testComplete(); } @Test public void refresh_nonLooperThreadAdvances() { final CountDownLatch bgThreadDone = new CountDownLatch(1); RealmResults<AllTypes> results = realm.where(AllTypes.class).findAll(); assertEquals(0, results.size()); new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(RealmTests.this.realm.getConfiguration()); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); realm.close(); bgThreadDone.countDown(); } }).run(); TestHelper.awaitOrFail(bgThreadDone); realm.refresh(); assertEquals(1, results.size()); } @Test @RunTestInLooperThread public void refresh_forceSynchronousNotifications() { final CountDownLatch bgThreadDone = new CountDownLatch(1); final AtomicBoolean listenerCalled = new AtomicBoolean(false); Realm realm = looperThread.getRealm(); RealmResults<AllTypes> results = realm.where(AllTypes.class).findAllAsync(); results.addChangeListener(new RealmChangeListener<RealmResults<AllTypes>>() { @Override public void onChange(RealmResults<AllTypes> results) { // Will be forced synchronous assertEquals(1, results.size()); listenerCalled.set(true); } }); new Thread(new Runnable() { @Override public void run() { Realm realm = Realm.getInstance(looperThread.getConfiguration()); realm.beginTransaction(); realm.createObject(AllTypes.class); realm.commitTransaction(); realm.close(); bgThreadDone.countDown(); } }).start(); TestHelper.awaitOrFail(bgThreadDone); realm.refresh(); assertTrue(listenerCalled.get()); looperThread.testComplete(); } @Test public void refresh_insideTransactionThrows() { realm.beginTransaction(); try { realm.refresh(); fail(); } catch (IllegalStateException ignored) { } realm.cancelTransaction(); } @Test public void beginTransaction_readOnlyThrows() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name("readonly.realm") .schema(StringOnlyReadOnly.class) .assetFile("readonly.realm") .readOnly() .build(); Realm realm = Realm.getInstance(config); try { realm.beginTransaction(); fail(); } catch (IllegalStateException e) { assertTrue(e.getMessage().startsWith("Write transactions cannot be used ")); } finally { realm.close(); } } @Test public void getInstance_wrongSchemaInReadonlyThrows() { RealmConfiguration config = configFactory.createConfigurationBuilder() .name("readonly.realm") .schema(StringOnlyReadOnly.class, AllJavaTypes.class) .assetFile("readonly.realm") .readOnly() .build(); // This will throw because the Realm doesn't have the correct schema, and a new file cannot be re-created // because it is read only. try { realm = Realm.getInstance(config); fail(); } catch (RealmMigrationNeededException ignored) { } } }