io.realm.RealmTests.java Source code

Java tutorial

Introduction

Here is the source code for io.realm.RealmTests.java

Source

/*
 * 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.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
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.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
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.atomic.AtomicBoolean;
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.AnnotationIndexTypes;
import io.realm.entities.Cat;
import io.realm.entities.CyclicType;
import io.realm.entities.CyclicTypePrimaryKey;
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.PrimaryKeyAsLong;
import io.realm.entities.PrimaryKeyAsString;
import io.realm.entities.PrimaryKeyMix;
import io.realm.entities.StringOnly;
import io.realm.exceptions.RealmException;
import io.realm.exceptions.RealmIOException;
import io.realm.internal.log.RealmLog;
import io.realm.rule.RunInLooperThread;
import io.realm.rule.RunTestInLooperThread;
import io.realm.rule.TestRealmConfigurationFactory;

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.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;

@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 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(3.1415);
        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(expected = IllegalArgumentException.class)
    public void getInstance_nullDir() {
        Realm.getInstance(new RealmConfiguration.Builder((File) null).build());
    }

    @Test
    public void getInstance_writeProtectedDir() {
        File folder = new File("/");
        thrown.expect(IllegalArgumentException.class);
        Realm.getInstance(new RealmConfiguration.Builder(folder).build());
    }

    @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));

        thrown.expect(RealmIOException.class);
        Realm.getInstance(new RealmConfiguration.Builder(folder).name(REALM_FILE).build());
    }

    @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
    @UiThreadTest
    public void internalRealmChangedHandlersRemoved() {
        realm.close(); // Clear handler created by testRealm in setUp()
        assertEquals(0, Realm.getHandlers().size());
        final String REALM_NAME = "test-internalhandlers";
        RealmConfiguration realmConfig = configFactory.createConfiguration(REALM_NAME);
        Realm.deleteRealm(realmConfig);

        // Open and close first instance of a Realm
        Realm realm = null;
        try {
            realm = Realm.getInstance(realmConfig);
            assertFalse(this.realm == realm);
            assertEquals(1, Realm.getHandlers().size());
            realm.close();

            // All Realms closed. No handlers should be alive.
            assertEquals(0, Realm.getHandlers().size());

            // Open instance the 2nd time. Old handler should now be gone
            realm = Realm.getInstance(realmConfig);
            assertEquals(1, Realm.getHandlers().size());
            realm.close();

        } finally {
            if (realm != null) {
                realm.close();
            }
        }
    }

    @Test
    public void getInstance() {
        assertNotNull("Realm.getInstance unexpectedly returns null", realm);
        assertTrue("Realm.getInstance does not contain expected table", realm.contains(AllTypes.class));
    }

    @Test
    public void getInstance_context() {
        RealmConfiguration config = new RealmConfiguration.Builder(context).build();
        Realm.deleteRealm(config);

        Realm testRealm = Realm.getInstance(context);
        assertNotNull("Realm.getInstance unexpectedly returns null", testRealm);
        assertTrue("Realm.getInstance does not contain expected table", testRealm.contains(AllTypes.class));
        config = testRealm.getConfiguration();
        config.getRealmFolder().equals(context.getFilesDir());
        testRealm.close();
        Realm.deleteRealm(config);
    }

    @Test
    public void getInstance_nullContext() {
        Realm realm = null;
        try {
            realm = Realm.getInstance((Context) null); // throws when context.getFilesDir() is called;
            // has nothing to do with Realm
            fail("Should throw an exception");
        } catch (IllegalArgumentException ignored) {
        } finally {
            if (realm != null) {
                realm.close();
            }
        }
    }

    // Private API
    @Test
    public void remove() {
        populateTestRealm();
        realm.beginTransaction();
        realm.remove(AllTypes.class, 0);
        realm.commitTransaction();

        RealmResults<AllTypes> resultList = realm.where(AllTypes.class).findAll();
        assertEquals(TEST_DATA_SIZE - 1, resultList.size());
    }

    // Private API
    @Test
    public void get() {
        populateTestRealm();
        AllTypes allTypes = realm.get(AllTypes.class, 0);
        assertNotNull(allTypes);
        assertEquals("test data 0", allTypes.getColumnString());
    }

    // Private API
    @Test
    public void contains() {
        assertTrue("contains returns false for table that should exists", realm.contains(Dog.class));
        assertFalse("contains returns true for non-existing table", realm.contains(null));
    }

    @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", 3.1415d).findAll();
            fail("Invalid field name");
        } catch (Exception ignored) {
        }

        try {
            realm.where(AllTypes.class).equalTo("invalidcolumnname", 3.1415f).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 allObjects() {
        populateTestRealm();
        RealmResults<AllTypes> resultList = realm.allObjects(AllTypes.class);
        assertEquals("Realm.get is returning wrong result set", TEST_DATA_SIZE, resultList.size());
    }

    @Test
    public void allObjectsSorted() {
        populateTestRealm();
        RealmResults<AllTypes> sortedList = realm.allObjectsSorted(AllTypes.class, AllTypes.FIELD_STRING,
                Sort.ASCENDING);
        assertEquals(TEST_DATA_SIZE, sortedList.size());
        assertEquals("test data 0", sortedList.first().getColumnString());

        RealmResults<AllTypes> reverseList = realm.allObjectsSorted(AllTypes.class, AllTypes.FIELD_STRING,
                Sort.DESCENDING);
        assertEquals(TEST_DATA_SIZE, reverseList.size());
        assertEquals("test data 0", reverseList.last().getColumnString());

        try {
            realm.allObjectsSorted(AllTypes.class, "invalid", Sort.ASCENDING);
            fail();
        } catch (IllegalArgumentException ignored) {
        }
    }

    @Test
    public void allObjectsSorted_singleField() {
        populateTestRealm();
        RealmResults<AllTypes> sortedList = realm.allObjectsSorted(AllTypes.class,
                new String[] { AllTypes.FIELD_LONG }, new Sort[] { Sort.DESCENDING });
        assertEquals(TEST_DATA_SIZE, sortedList.size());
        assertEquals(TEST_DATA_SIZE - 1, sortedList.first().getColumnLong());
        assertEquals(0, sortedList.last().getColumnLong());
    }

    @Test
    public void allObjectsSorted_twoFields() {
        TestHelper.populateForMultiSort(realm);

        RealmResults<AllTypes> results1 = realm.allObjectsSorted(AllTypes.class,
                new String[] { AllTypes.FIELD_STRING, AllTypes.FIELD_LONG },
                new Sort[] { Sort.ASCENDING, Sort.ASCENDING });

        assertEquals(3, results1.size());

        assertEquals("Adam", results1.get(0).getColumnString());
        assertEquals(4, results1.get(0).getColumnLong());

        assertEquals("Adam", results1.get(1).getColumnString());
        assertEquals(5, results1.get(1).getColumnLong());

        assertEquals("Brian", results1.get(2).getColumnString());
        assertEquals(4, results1.get(2).getColumnLong());

        RealmResults<AllTypes> results2 = realm.allObjectsSorted(AllTypes.class,
                new String[] { AllTypes.FIELD_LONG, AllTypes.FIELD_STRING },
                new Sort[] { Sort.ASCENDING, Sort.ASCENDING });

        assertEquals(3, results2.size());

        assertEquals("Adam", results2.get(0).getColumnString());
        assertEquals(4, results2.get(0).getColumnLong());

        assertEquals("Brian", results2.get(1).getColumnString());
        assertEquals(4, results2.get(1).getColumnLong());

        assertEquals("Adam", results2.get(2).getColumnString());
        assertEquals(5, results2.get(2).getColumnLong());
    }

    @Test
    public void allObjectsSorted_failures() {
        // zero fields specified
        try {
            realm.allObjectsSorted(AllTypes.class, new String[] {}, new Sort[] {});
            fail();
        } catch (IllegalArgumentException ignored) {
        }

        // number of fields and sorting orders don't match
        try {
            realm.allObjectsSorted(AllTypes.class, new String[] { AllTypes.FIELD_STRING },
                    new Sort[] { Sort.ASCENDING, Sort.ASCENDING });
            fail();
        } catch (IllegalArgumentException ignored) {
        }

        // null is not allowed
        try {
            realm.allObjectsSorted(AllTypes.class, null, (Sort[]) null);
            fail();
        } catch (IllegalArgumentException ignored) {
        }
        try {
            realm.allObjectsSorted(AllTypes.class, new String[] { AllTypes.FIELD_STRING }, null);
            fail();
        } catch (IllegalArgumentException ignored) {
        }

        // non-existing field name
        try {
            realm.allObjectsSorted(AllTypes.class, new String[] { AllTypes.FIELD_STRING, "dont-exist" },
                    new Sort[] { Sort.ASCENDING, Sort.ASCENDING });
            fail();
        } catch (IllegalArgumentException ignored) {
        }
    }

    @Test
    public void beginTransaction() throws IOException {
        populateTestRealm();

        realm.beginTransaction();
        AllTypes allTypes = realm.createObject(AllTypes.class);
        allTypes.setColumnFloat(3.1415f);
        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.1415f).findAll();
        assertEquals(1, resultList.size());
    }

    @Test
    public void nestedTransaction() {
        realm.beginTransaction();
        try {
            realm.beginTransaction();
            fail();
        } catch (IllegalStateException e) {
            assertEquals(
                    "Nested transactions are not allowed. Use commitTransaction() after each beginTransaction().",
                    e.getMessage());
        }
        realm.commitTransaction();
    }

    private enum Method {
        METHOD_BEGIN, METHOD_COMMIT, METHOD_CANCEL, METHOD_DELETE_TYPE, METHOD_DELETE_ALL, METHOD_DISTINCT, METHOD_CREATE_OBJECT, 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
    }

    // 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_DISTINCT:
                        realm.distinct(AllTypesPrimaryKey.class, "columnLong");
                        break;
                    case METHOD_CREATE_OBJECT:
                        realm.createObject(AllTypes.class);
                        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}]");
                        break;
                    case METHOD_CREATE_FROM_JSON:
                        realm.createObjectFromJson(AllTypes.class, "{}");
                        break;
                    case METHOD_CREATE_OR_UPDATE_FROM_JSON:
                        realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, "{\"columnLong\":1}");
                        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.allObjects(AllTypes.class).size());

        try {
            realm.cancelTransaction();
            fail();
        } catch (IllegalStateException ignored) {
        }
    }

    @Test
    public void executeTransaction_null() {
        try {
            realm.executeTransaction(null);
            fail("null transaction should throw");
        } catch (IllegalArgumentException ignored) {

        }
        assertFalse(realm.hasChanged());
    }

    @Test
    public void executeTransaction_success() {
        assertEquals(0, realm.allObjects(Owner.class).size());
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                Owner owner = realm.createObject(Owner.class);
                owner.setName("Owner");
            }
        });
        assertEquals(1, realm.allObjects(Owner.class).size());
    }

    @Test
    public void executeTransaction_canceled() {
        final AtomicReference<RuntimeException> thrownException = new AtomicReference<>(null);

        assertEquals(0, realm.allObjects(Owner.class).size());
        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.allObjects(Owner.class).size());
    }

    @Test
    public void executeTransaction_cancelInsideClosureThrowsException() {
        assertEquals(0, realm.allObjects(Owner.class).size());
        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) {
            // Ensure 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.allObjects(Owner.class).size());
    }

    @Test
    public void delete_type() {
        // ** delete non existing table should succeed
        realm.beginTransaction();
        realm.delete(AllTypes.class);
        realm.commitTransaction();

        // ** delete existing class, but leave other classes classes

        // Add two classes
        populateTestRealm();
        realm.beginTransaction();
        Dog dog = realm.createObject(Dog.class);
        dog.setName("Castro");
        realm.commitTransaction();
        // Clear
        realm.beginTransaction();
        realm.delete(Dog.class);
        realm.commitTransaction();
        // Check 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.allObjects(Dog.class).first();
        assertEquals(language, "Rex", dog2.getName());
        realm2.close();
    }

    // TODO Move to RealmConfigurationTests?
    @Test
    public void realmConfiguration_fileName() {
        createAndTestFilename("American", "Washington");
        createAndTestFilename("Danish", "Kbenhavn");
        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;
    }

    // This test is slow. Move it to another testsuite that runs once a day on Jenkins.
    // The test writes and reads random Strings.
    // @Test TODO AndroidJUnit4 runner doesn't seem to respect the @Ignore annotation?
    @Ignore
    public void unicodeStrings() {
        List<String> chars_array = getCharacterArray();
        // Change seed value for new random values.
        long seed = 20;
        Random random = new Random(seed);

        int random_value = 0;

        String test_char = "";
        String test_char_old = "";
        String get_data = "";

        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.allObjects(StringOnly.class).get(0).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();
        }

        // Make sure the reference counter is per realm file
        RealmConfiguration anotherConfig = configFactory.createConfiguration("anotherRealm.realm");
        Realm.deleteRealm(anotherConfig);
        Realm otherRealm = Realm.getInstance(anotherConfig);

        // Raise 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(); // Count 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.allObjects(AllTypes.class);
        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.allObjects(AllTypes.class);
            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();
        // TODO: remove try/catch block when compacting encrypted Realms is supported
        try {
            assertTrue(Realm.compactRealm(realmConfig));
            fail();
        } catch (IllegalArgumentException expected) {
        }
    }

    @Test
    public void compactRealm_encryptedPopulatedRealm() {
        RealmConfiguration realmConfig = configFactory.createConfiguration("enc.realm", TestHelper.getRandomKey());
        Realm realm = Realm.getInstance(realmConfig);

        populateTestRealm(realm, 100);
        realm.close();
        // TODO: remove try/catch block when compacting encrypted Realms is supported
        try {
            assertTrue(Realm.compactRealm(realmConfig));
            fail();
        } catch (IllegalArgumentException expected) {
        }
    }

    @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 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();
        date.setTime(1000); // Remove ms. precision as Realm doesn't support it yet.
        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.allObjects(CyclicType.class).size());
    }

    @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.allObjects(CyclicType.class).size());
    }

    // Check 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);
        realm.copyToRealm(new OwnerPrimaryKey(1, "Foo"));
        realm.commitTransaction();
        assertEquals(2, realm.where(OwnerPrimaryKey.class).count());
    }

    @Test
    public void copyToRealm_primaryKeyIsNull() {
        realm.beginTransaction();
        thrown.expect(IllegalArgumentException.class);
        realm.copyToRealm(new PrimaryKeyAsString());
    }

    @Test
    public void copyToRealm_doNotCopyReferencedObjectIfManaged() {
        realm.beginTransaction();

        // Child object is managed by Realm
        CyclicTypePrimaryKey childObj = realm.createObject(CyclicTypePrimaryKey.class);
        childObj.setName("Child");
        childObj.setId(1);

        // Parent object is a standalone 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_primaryKeyFieldIsNull() {
        realm.beginTransaction();
        thrown.expect(IllegalArgumentException.class);
        realm.copyToRealmOrUpdate(new PrimaryKeyAsString());
    }

    @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.allObjects(PrimaryKeyAsLong.class).size());
    }

    @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.allObjects(AllTypesPrimaryKey.class).size());
        AllTypesPrimaryKey obj = realm.allObjects(AllTypesPrimaryKey.class).first();

        // Check 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.allObjects(CyclicTypePrimaryKey.class).size());
        assertEquals("Three", realm.where(CyclicTypePrimaryKey.class).equalTo("id", 1).findFirst().getName());
    }

    // Checks that a standalone 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.allObjects(AllTypesPrimaryKey.class).size());

        AllTypesPrimaryKey obj = realm.allObjects(AllTypesPrimaryKey.class).first();
        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.allObjects(AllTypesPrimaryKey.class).size());
        assertEquals(4, realm.allObjects(DogPrimaryKey.class).size());
    }

    @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.allObjects(PrimaryKeyAsLong.class).size());
        assertEquals("Baz", realm.allObjects(PrimaryKeyAsLong.class).first().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.allObjects(AllTypesPrimaryKey.class).size());
        assertEquals(1, realm.allObjects(DogPrimaryKey.class).size());
    }

    @Test
    public void copyToRealmOrUpdate_objectInOtherThreadThrows() {
        final CountDownLatch bgThreadDoneLatch = new CountDownLatch(1);

        realm.beginTransaction();
        final OwnerPrimaryKey ownerPrimaryKey = realm.createObject(OwnerPrimaryKey.class);
        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);

        // Make 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();
            } 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);

        // Configure 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);

        // Write encrypted copy from a unencrypted Realm
        File destination = new File(encryptedRealmConfig.getPath());
        try {
            realm.writeEncryptedCopyTo(destination, encryptedRealmConfig.getEncryptionKey());
        } catch (Exception e) {
            fail(e.getMessage());
        }

        Realm encryptedRealm = null;
        try {

            // Verify encrypted Realm and write 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());
            try {
                encryptedRealm.writeEncryptedCopyTo(destination, reEncryptedRealmConfig.getEncryptionKey());
            } catch (Exception e) {
                fail(e.getMessage());
            }

            // Verify 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();
                    }
                }
            }

            // Write non-encrypted copy from the encrypted version
            destination = new File(decryptedRealmConfig.getPath());
            try {
                encryptedRealm.writeEncryptedCopyTo(destination, null);
            } catch (Exception e) {
                fail(e.getMessage());
            }

            // Verify decrypted Realm and cleanup
            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 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) {
        }

        // Create 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();

        // Create an owner with two dogs
        OwnerPrimaryKey owner = realm.createObject(OwnerPrimaryKey.class);
        owner.setId(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(context.getFilesDir(), "delete_test_dir");
        if (!tempDir.exists()) {
            assertTrue(tempDir.mkdir());
        }

        assertTrue(tempDir.isDirectory());

        // Delete all files in the directory
        File[] files = tempDir.listFiles();
        if (files != null) {
            for (File file : files) {
                assertTrue(file.delete());
            }
        }

        final RealmConfiguration configuration = new RealmConfiguration.Builder(tempDir).build();

        final CountDownLatch readyToCloseLatch = new CountDownLatch(1);
        final CountDownLatch closedLatch = new CountDownLatch(1);

        Realm realm = Realm.getInstance(configuration);
        // Create another Realm to ensure the log files are generated
        new Thread(new Runnable() {
            @Override
            public void run() {
                Realm realm = Realm.getInstance(configuration);
                try {
                    readyToCloseLatch.await();
                } catch (InterruptedException ignored) {
                }
                realm.close();
                closedLatch.countDown();
            }
        }).start();

        realm.beginTransaction();
        realm.createObject(AllTypes.class);
        realm.commitTransaction();
        readyToCloseLatch.countDown();
        realm.close();
        closedLatch.await();

        // ATTENTION: log, log_a, log_b will be deleted when the other thread close the Realm peacefully. And we force
        // user to close all Realm instances before deleting. It would be difficult to simulate a case that log files
        // exist before deletion. Let's keep the case like this for now, we might allow user to delete Realm even there
        // are instances opened in the future.
        assertTrue(Realm.deleteRealm(configuration));

        // Directory should be empty now
        assertEquals(0, tempDir.listFiles().length);
    }

    // Test that all methods that require a transaction (ie. any function that mutates Realm data)
    @Test
    public void callMutableMethodOutsideTransaction() throws JSONException, IOException {

        // Prepare standalone object data
        AllTypesPrimaryKey t = new AllTypesPrimaryKey();
        List<AllTypesPrimaryKey> ts = Arrays.asList(t, t);

        // Prepare 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);

        // Test 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.remove(AllTypes.class, 0);
            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 (RealmException expected) {
        }
        try {
            realm.createObjectFromJson(AllTypesPrimaryKey.class, jsonObjStr);
            fail();
        } catch (RealmException expected) {
        }
        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) {
        }
        try {
            realm.createOrUpdateObjectFromJson(AllTypesPrimaryKey.class, jsonObjStream2);
            fail();
        } catch (IllegalStateException expected) {
        }

        try {
            realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArr);
            fail();
        } catch (RealmException expected) {
        }
        try {
            realm.createAllFromJson(AllTypesPrimaryKey.class, jsonArrStr);
            fail();
        } catch (RealmException expected) {
        }
        try {
            realm.createAllFromJson(NoPrimaryKeyNullTypes.class, jsonArrStream);
            fail();
        } catch (IllegalStateException expected) {
        }
        try {
            realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArr);
            fail();
        } catch (RealmException expected) {
        }
        try {
            realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStr);
            fail();
        } catch (RealmException expected) {
        }
        try {
            realm.createOrUpdateAllFromJson(AllTypesPrimaryKey.class, jsonArrStream2);
            fail();
        } catch (IllegalStateException expected) {
        }
    }

    // TODO: re-introduce this test mocking the ReferenceQueue instead of relying on the GC
    /*    // Check that FinalizerRunnable can free native resources (phantom refs)
        public void testReferenceCleaning() throws NoSuchFieldException, IllegalAccessException {
    testRealm.close();
        
    RealmConfiguration config = new RealmConfiguration.Builder(getContext()).name("myown").build();
    Realm.deleteRealm(config);
    testRealm = Realm.getInstance(config);
        
    // Manipulate field accessibility to facilitate testing
    Field realmFileReference = BaseRealm.class.getDeclaredField("sharedGroupManager");
    realmFileReference.setAccessible(true);
    Field contextField = SharedGroup.class.getDeclaredField("context");
    contextField.setAccessible(true);
    Field rowReferencesField = io.realm.internal.Context.class.getDeclaredField("rowReferences");
    rowReferencesField.setAccessible(true);
        
    SharedGroupManager realmFile = (SharedGroupManager) realmFileReference.get(testRealm);
    assertNotNull(realmFile);
        
    io.realm.internal.Context context = (io.realm.internal.Context) contextField.get(realmFile.getSharedGroup());
    assertNotNull(context);
        
    Map<Reference<?>, Integer> rowReferences = (Map<Reference<?>, Integer>) rowReferencesField.get(context);
    assertNotNull(rowReferences);
        
    // insert some rows, then give the thread some time to cleanup
    // we have 8 reference so far let's add more
    final int numberOfPopulateTest = 1000;
    final int numberOfObjects = 20;
    final int totalNumberOfReferences = 8 + numberOfObjects * 2 * numberOfPopulateTest;
        
    long tic = System.currentTimeMillis();
    for (int i = 0; i < numberOfPopulateTest; i++) {
        populateTestRealm(testRealm, numberOfObjects);
    }
    long toc = System.currentTimeMillis();
    Log.d(RealmTest.class.getName(), "Insertion time: " + (toc - tic));
        
    final int MAX_GC_RETRIES = 5;
    int numberOfRetries = 0;
    Log.i("GCing", "Hoping for the best");
    while (rowReferences.size() > 0 && numberOfRetries < MAX_GC_RETRIES) {
        SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); //1s
        TestHelper.allocGarbage(0);
        numberOfRetries++;
        System.gc();
    }
    context.cleanNativeReferences();
        
    // we can't guarantee that all references have been GC'ed but we should detect a decrease
    boolean isDecreasing = rowReferences.size() < totalNumberOfReferences;
    if (!isDecreasing) {
        fail("Native resources are not being closed");
        
    } else {
        android.util.Log.d(RealmTest.class.getName(), "References freed : "
                + (totalNumberOfReferences - rowReferences.size()) + " out of " + totalNumberOfReferences);
    }
        }*/

    @Test
    public void createObject_cannotCreateDynamicRealmObject() {
        realm.beginTransaction();
        try {
            realm.createObject(DynamicRealmObject.class);
            fail();
        } catch (RealmException ignored) {
        }
    }

    // Test 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
        latch.await();
        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());
    }

    // Test 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
        latch.await();
        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];
        }
    }

    // FIXME HandlerThread
    @Test
    public void processLocalListenersAfterRefresh() throws InterruptedException {
        // Used to validate the result
        final AtomicBoolean listenerWasCalled = new AtomicBoolean(false);
        final AtomicBoolean typeListenerWasCalled = new AtomicBoolean(false);

        // Used by the background thread to wait for the main thread to do the write operation
        final CountDownLatch bgThreadLatch = new CountDownLatch(1);
        final CountDownLatch bgClosedLatch = new CountDownLatch(1);
        final CountDownLatch bgThreadReadyLatch = new CountDownLatch(1);

        Thread backgroundThread = new Thread() {
            @Override
            public void run() {
                // this will allow to register a listener.
                // we don't start looping to prevent the callback to be invoked via
                // the handler mechanism, the purpose of this test is to make sure refresh calls
                // the listeners.
                Looper.prepare();

                Realm bgRealm = Realm.getInstance(realmConfig);
                RealmResults<Dog> dogs = bgRealm.where(Dog.class).findAll();
                try {
                    bgRealm.addChangeListener(new RealmChangeListener() {
                        @Override
                        public void onChange() {
                            listenerWasCalled.set(true);
                        }
                    });
                    dogs.addChangeListener(new RealmChangeListener() {
                        @Override
                        public void onChange() {
                            typeListenerWasCalled.set(true);
                        }
                    });

                    bgThreadReadyLatch.countDown();
                    bgThreadLatch.await(); // Wait for the main thread to do a write operation
                    bgRealm.refresh(); // This should call the listener
                    assertTrue(listenerWasCalled.get());
                    assertTrue(typeListenerWasCalled.get());
                    bgRealm.close();
                    bgRealm = null;
                    // DON'T count down in the final block! The test will fail silently!!!
                    bgClosedLatch.countDown();
                } catch (InterruptedException e) {
                    fail(e.getMessage());
                } finally {
                    if (bgRealm != null) {
                        bgRealm.close();
                    }
                }
            }
        };
        backgroundThread.start();

        // Wait until bgThread finishes adding listener to the RealmResults. Otherwise same TableView version won't
        // trigger the listener.
        bgThreadReadyLatch.await();
        realm.beginTransaction();
        realm.createObject(Dog.class);
        realm.commitTransaction();
        bgThreadLatch.countDown();
        bgClosedLatch.await();
    }

    private void populateForDistinct(Realm realm, long numberOfBlocks, long numberOfObjects, boolean withNull) {
        realm.beginTransaction();
        for (int i = 0; i < numberOfObjects * numberOfBlocks; i++) {
            for (int j = 0; j < numberOfBlocks; j++) {
                AnnotationIndexTypes obj = realm.createObject(AnnotationIndexTypes.class);
                obj.setIndexBoolean(j % 2 == 0);
                obj.setIndexLong(j);
                obj.setIndexDate(withNull ? null : new Date(1000 * (long) j));
                obj.setIndexString(withNull ? null : "Test " + j);
                obj.setNotIndexBoolean(j % 2 == 0);
                obj.setNotIndexLong(j);
                obj.setNotIndexDate(withNull ? null : new Date(1000 * (long) j));
                obj.setNotIndexString(withNull ? null : "Test " + j);
            }
        }
        realm.commitTransaction();
    }

    private void populateForDistinctInvalidTypesLinked(Realm realm) {
        realm.beginTransaction();
        AllJavaTypes notEmpty = new AllJavaTypes();
        notEmpty.setFieldBinary(new byte[] { 1, 2, 3 });
        notEmpty.setFieldObject(notEmpty);
        notEmpty.setFieldList(new RealmList<AllJavaTypes>(notEmpty));
        realm.copyToRealm(notEmpty);
        realm.commitTransaction();
    }

    // Realm.distinct(): requires indexing, and type = boolean, integer, date, string
    @Test
    public void distinct() {
        final long numberOfBlocks = 25;
        final long numberOfObjects = 10; // must be greater than 1
        populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);

        RealmResults<AnnotationIndexTypes> distinctBool = realm.distinct(AnnotationIndexTypes.class,
                AnnotationIndexTypes.FIELD_INDEX_BOOL);
        assertEquals(2, distinctBool.size());
        for (String field : new String[] { AnnotationIndexTypes.FIELD_INDEX_LONG,
                AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING }) {
            RealmResults<AnnotationIndexTypes> distinct = realm.distinct(AnnotationIndexTypes.class, field);
            assertEquals(field, numberOfBlocks, distinct.size());
        }
    }

    @Test
    public void distinct_withNullValues() {
        final long numberOfBlocks = 25;
        final long numberOfObjects = 10; // must be greater than 1
        populateForDistinct(realm, numberOfBlocks, numberOfObjects, true);

        for (String field : new String[] { AnnotationIndexTypes.FIELD_INDEX_DATE,
                AnnotationIndexTypes.FIELD_INDEX_STRING }) {
            RealmResults<AnnotationIndexTypes> distinct = realm.distinct(AnnotationIndexTypes.class, field);
            assertEquals(field, 1, distinct.size());
        }
    }

    @Test
    public void distinct_notIndexedFieldsThrows() {
        final long numberOfBlocks = 25;
        final long numberOfObjects = 10; // must be greater than 1
        populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);

        for (String field : AnnotationIndexTypes.NOT_INDEX_FIELDS) {
            try {
                realm.distinct(AnnotationIndexTypes.class, field);
                fail(field);
            } catch (IllegalArgumentException ignored) {
            }
        }
    }

    @Test
    public void distinct_unknownFieldThrows() {
        final long numberOfBlocks = 25;
        final long numberOfObjects = 10; // must be greater than 1
        populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);
        thrown.expect(IllegalArgumentException.class);

        realm.distinct(AnnotationIndexTypes.class, "doesNotExist");
    }

    @Test
    public void distinct_invalidTypeThrows() {
        populateTestRealm();

        for (String field : new String[] { AllTypes.FIELD_REALMOBJECT, AllTypes.FIELD_REALMLIST,
                AllTypes.FIELD_DOUBLE, AllTypes.FIELD_FLOAT }) {
            try {
                realm.distinct(AllTypes.class, field);
                fail(field);
            } catch (IllegalArgumentException ignored) {
            }
        }
    }

    @Test
    public void distinctMultiArgs() {
        final long numberOfBlocks = 25;
        final long numberOfObjects = 10; // must be greater than 1
        populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);

        RealmResults<AnnotationIndexTypes> distinctMulti = realm.distinct(AnnotationIndexTypes.class,
                AnnotationIndexTypes.FIELD_INDEX_BOOL, AnnotationIndexTypes.INDEX_FIELDS);
        assertEquals(numberOfBlocks, distinctMulti.size());
    }

    @Test
    public void distinctMultiArgs_switchedFieldsOrder() {
        final long numberOfBlocks = 25;
        TestHelper.populateForDistinctFieldsOrder(realm, numberOfBlocks);

        // Regardless of the block size defined above, the output size is expected to be the same, 4 in this case, due to receiving unique combinations of tuples
        RealmResults<AnnotationIndexTypes> distinctStringLong = realm.distinct(AnnotationIndexTypes.class,
                AnnotationIndexTypes.FIELD_INDEX_STRING, AnnotationIndexTypes.FIELD_INDEX_LONG);
        RealmResults<AnnotationIndexTypes> distinctLongString = realm.distinct(AnnotationIndexTypes.class,
                AnnotationIndexTypes.FIELD_INDEX_LONG, AnnotationIndexTypes.FIELD_INDEX_STRING);
        assertEquals(4, distinctStringLong.size());
        assertEquals(4, distinctLongString.size());
        assertEquals(distinctStringLong.size(), distinctLongString.size());
    }

    @Test
    public void distinctMultiArgs_emptyFields() {
        final long numberOfBlocks = 25;
        final long numberOfObjects = 10;
        populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);

        // an empty string field in the middle
        try {
            realm.distinct(AnnotationIndexTypes.class, AnnotationIndexTypes.FIELD_INDEX_BOOL, "",
                    AnnotationIndexTypes.FIELD_INDEX_INT);
        } catch (IllegalArgumentException ignored) {
        }
        // an empty string field at the end
        try {
            realm.distinct(AnnotationIndexTypes.class, AnnotationIndexTypes.FIELD_INDEX_BOOL,
                    AnnotationIndexTypes.FIELD_INDEX_INT, "");
        } catch (IllegalArgumentException ignored) {
        }
        // a null string field in the middle
        try {
            realm.distinct(AnnotationIndexTypes.class, AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null,
                    AnnotationIndexTypes.FIELD_INDEX_INT);
        } catch (IllegalArgumentException ignored) {
        }
        // a null string field at the end
        try {
            realm.distinct(AnnotationIndexTypes.class, AnnotationIndexTypes.FIELD_INDEX_BOOL,
                    AnnotationIndexTypes.FIELD_INDEX_INT, (String) null);
        } catch (IllegalArgumentException ignored) {
        }
        // (String)null makes varargs a null array.
        try {
            realm.distinct(AnnotationIndexTypes.class, AnnotationIndexTypes.FIELD_INDEX_BOOL, (String) null);
        } catch (IllegalArgumentException ignored) {
        }
        // Two (String)null for first and varargs fields
        try {
            realm.distinct(AnnotationIndexTypes.class, (String) null, (String) null);
        } catch (IllegalArgumentException ignored) {
        }
        // "" & (String)null combination
        try {
            realm.distinct(AnnotationIndexTypes.class, "", (String) null);
        } catch (IllegalArgumentException ignored) {
        }
        // "" & (String)null combination
        try {
            realm.distinct(AnnotationIndexTypes.class, (String) null, "");
        } catch (IllegalArgumentException ignored) {
        }
        // Two empty fields tests
        try {
            realm.distinct(AnnotationIndexTypes.class, "", "");
        } catch (IllegalArgumentException ignored) {
        }
    }

    @Test
    public void distinctMultiArgs_withNullValues() {
        final long numberOfBlocks = 25;
        final long numberOfObjects = 10;
        populateForDistinct(realm, numberOfBlocks, numberOfObjects, true);

        RealmResults<AnnotationIndexTypes> distinctMulti = realm.distinct(AnnotationIndexTypes.class,
                AnnotationIndexTypes.FIELD_INDEX_DATE, AnnotationIndexTypes.FIELD_INDEX_STRING);
        assertEquals(1, distinctMulti.size());
    }

    @Test
    public void distinctMultiArgs_notIndexedFields() {
        final long numberOfBlocks = 25;
        final long numberOfObjects = 10;
        populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);

        try {
            realm.distinct(AnnotationIndexTypes.class, AnnotationIndexTypes.FIELD_NOT_INDEX_STRING,
                    AnnotationIndexTypes.NOT_INDEX_FIELDS);
        } catch (IllegalArgumentException ignored) {
        }
    }

    @Test
    public void distinctMultiArgs_doesNotExistField() {
        final long numberOfBlocks = 25;
        final long numberOfObjects = 10;
        populateForDistinct(realm, numberOfBlocks, numberOfObjects, false);

        try {
            realm.distinct(AnnotationIndexTypes.class, AnnotationIndexTypes.FIELD_INDEX_INT,
                    AnnotationIndexTypes.NONEXISTANT_MIX_FIELDS);
        } catch (IllegalArgumentException ignored) {
        }
    }

    @Test
    public void distinctMultiArgs_invalidTypesFields() {
        populateTestRealm();

        try {
            realm.distinct(AllTypes.class, AllTypes.FIELD_REALMOBJECT, AllTypes.INVALID_TYPES_FIELDS_FOR_DISTINCT);
        } catch (IllegalArgumentException ignored) {
        }
    }

    @Test
    public void distinctMultiArgs_indexedLinkedFields() {
        final long numberOfBlocks = 25;
        final long numberOfObjects = 10;
        populateForDistinct(realm, numberOfBlocks, numberOfObjects, true);

        try {
            realm.distinct(AnnotationIndexTypes.class, AnnotationIndexTypes.INDEX_LINKED_FIELD_STRING,
                    AnnotationIndexTypes.INDEX_LINKED_FIELDS);
        } catch (IllegalArgumentException ignored) {
        }
    }

    @Test
    public void distinctMultiArgs_notIndexedLinkedFields() {
        final long numberOfBlocks = 25;
        final long numberOfObjects = 10;
        populateForDistinct(realm, numberOfBlocks, numberOfObjects, true);

        try {
            realm.distinct(AnnotationIndexTypes.class, AnnotationIndexTypes.NOT_INDEX_LINKED_FILED_STRING,
                    AnnotationIndexTypes.NOT_INDEX_LINKED_FIELDS);
        } catch (IllegalArgumentException ignored) {
        }
    }

    @Test
    public void distinctMultiArgs_invalidTypesLinkedFields() {
        populateForDistinctInvalidTypesLinked(realm);

        try {
            realm.distinct(AllJavaTypes.class, AllJavaTypes.INVALID_LINKED_BINARY_FIELD_FOR_DISTINCT,
                    AllJavaTypes.INVALID_LINKED_TYPES_FIELDS_FOR_DISTINCT);
        } catch (IllegalArgumentException ignored) {
        }
    }

    @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();
                } 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();

        // prevent for another thread to enter Realm.createAndValidate().
        synchronized (BaseRealm.class) {
            startLatch.countDown();

            // wait for another thread's entering Realm.createAndValidate().
            SystemClock.sleep(100L);

            realm.close();
            realm = null;
        }

        endLatch.await();

        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: Open realm in background thread.
                Realm realm = Realm.getInstance(realmConfig);
                realmOpenedInBgLatch.countDown();
                try {
                    realmClosedInFgLatch.await();
                } catch (InterruptedException e) {
                    exception.add(e);
                    realm.close();
                    return;
                }

                // Step 4: Start transaction in background
                realm.beginTransaction();
                transBeganInBgLatch.countDown();
                try {
                    fgFinishedLatch.await();
                } catch (InterruptedException e) {
                    exception.add(e);
                }
                // Step 6: Cancel Transaction and close realm in background
                realm.cancelTransaction();
                realm.close();
                bgFinishedLatch.countDown();
            }
        });
        thread.start();

        realmOpenedInBgLatch.await();
        // Step 3: Close all realm instances in foreground thread.
        realm.close();
        realmClosedInFgLatch.countDown();
        transBeganInBgLatch.await();

        // Step 5: Get a new Realm instance in foreground
        realm = Realm.getInstance(realmConfig);
        fgFinishedLatch.countDown();
        bgFinishedLatch.await();

        if (!exception.isEmpty()) {
            throw exception.get(0);
        }
    }

    @Test
    public void refresh_insideTransactionThrows() {
        realm.beginTransaction();
        thrown.expect(IllegalStateException.class);
        realm.refresh();
    }

    @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 standaloneObject = realm.copyFromRealm(realmObject);
        assertArrayEquals(realmObject.getColumnBinary(), standaloneObject.getColumnBinary());
        assertEquals(realmObject.getColumnString(), standaloneObject.getColumnString());
        assertEquals(realmObject.getColumnLong(), standaloneObject.getColumnLong());
        assertEquals(realmObject.getColumnFloat(), standaloneObject.getColumnFloat(), 0.00000000001);
        assertEquals(realmObject.getColumnDouble(), standaloneObject.getColumnDouble(), 0.00000000001);
        assertEquals(realmObject.isColumnBoolean(), standaloneObject.isColumnBoolean());
        assertEquals(realmObject.getColumnDate(), standaloneObject.getColumnDate());
    }

    @Test
    public void copyFromRealm_newCopyEachTime() {
        populateTestRealm();
        AllTypes realmObject = realm.where(AllTypes.class).findAllSorted("columnLong").first();
        AllTypes standaloneObject1 = realm.copyFromRealm(realmObject);
        AllTypes standaloneObject2 = realm.copyFromRealm(realmObject);
        assertFalse(standaloneObject1 == standaloneObject2);
        assertNotSame(standaloneObject1, standaloneObject2);
    }

    // Test 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());

        // Assert 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));
    }

    // Test 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());
    }

    // Test 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.allObjects(AllTypes.class);
        thrown.expect(IllegalArgumentException.class);
        realm.copyFromRealm(results, -1);
    }

    // Test 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();
        }
    }

    // Test if close can be called from Realm change listener when there is no other listeners
    @Test
    public void closeRealmInChangeListener() {
        realm.close();
        final CountDownLatch signalTestFinished = new CountDownLatch(1);
        HandlerThread handlerThread = new HandlerThread("background");
        handlerThread.start();
        final Handler handler = new Handler(handlerThread.getLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                final Realm realm = Realm.getInstance(realmConfig);
                final RealmChangeListener listener = new RealmChangeListener() {
                    @Override
                    public void onChange() {
                        if (realm.where(AllTypes.class).count() == 1) {
                            realm.removeChangeListener(this);
                            realm.close();
                            signalTestFinished.countDown();
                        }
                    }
                };

                realm.addChangeListener(listener);

                realm.executeTransactionAsync(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        realm.createObject(AllTypes.class);
                    }
                });
            }
        });
        TestHelper.awaitOrFail(signalTestFinished);
    }

    // Test 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 = Realm.getInstance(looperThread.createConfiguration());
        final RealmChangeListener dummyListener = new RealmChangeListener() {
            @Override
            public void onChange() {
            }
        };

        // Change listener on Realm
        final RealmChangeListener listener = new RealmChangeListener() {
            @Override
            public void onChange() {
                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);
            }
        });
    }

    // Test 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 = Realm.getInstance(looperThread.createConfiguration());
        final RealmChangeListener dummyListener = new RealmChangeListener() {
            @Override
            public void onChange() {
            }
        };
        final RealmChangeListener listener = new RealmChangeListener() {
            @Override
            public void onChange() {
                if (realm.where(AllTypes.class).count() == 2) {
                    realm.removeChangeListener(this);
                    realm.close();

                    // End 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();

        // Step 1: 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);
            }
        });
    }

    // Test if close can be called from Realm change listener when there is an listener on RealmResults
    @Test
    @RunTestInLooperThread
    public void closeRealmInChangeListenerWhenThereIsListenerOnResults() {
        final Realm realm = Realm.getInstance(looperThread.createConfiguration());
        final RealmChangeListener dummyListener = new RealmChangeListener() {
            @Override
            public void onChange() {
            }
        };
        final RealmChangeListener listener = new RealmChangeListener() {
            @Override
            public void onChange() {
                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);

        // Step 1: 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
    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() {
                        @Override
                        public void onChange() {
                        }
                    });
                    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());
    }
}