blob: 2b32a620080ffad4869f11b9fef1daa322a2a9db [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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 androidx.room.integration.testapp.test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.filters.SdkSuppress;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import androidx.annotation.Nullable;
import androidx.arch.core.executor.ArchTaskExecutor;
import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.room.InvalidationTrackerTrojan;
import androidx.room.integration.testapp.vo.AvgWeightByAge;
import androidx.room.integration.testapp.vo.Pet;
import androidx.room.integration.testapp.vo.PetsToys;
import androidx.room.integration.testapp.vo.Toy;
import androidx.room.integration.testapp.vo.User;
import androidx.room.integration.testapp.vo.UserAndAllPets;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Tests invalidation tracking.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class LiveDataQueryTest extends TestDatabaseTest {
@Rule
public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
@Test
public void observeById() throws InterruptedException, ExecutionException, TimeoutException {
final LiveData<User> userLiveData = mUserDao.liveUserById(5);
final TestLifecycleOwner testOwner = new TestLifecycleOwner();
testOwner.handleEvent(Lifecycle.Event.ON_CREATE);
final TestObserver<User> observer = new TestObserver<>();
observe(userLiveData, testOwner, observer);
assertThat(observer.hasValue(), is(false));
observer.reset();
testOwner.handleEvent(Lifecycle.Event.ON_START);
assertThat(observer.get(), is(nullValue()));
// another id
observer.reset();
mUserDao.insert(TestUtil.createUser(7));
assertThat(observer.get(), is(nullValue()));
observer.reset();
final User u5 = TestUtil.createUser(5);
mUserDao.insert(u5);
assertThat(observer.get(), is(notNullValue()));
u5.setName("foo-foo-foo");
observer.reset();
mUserDao.insertOrReplace(u5);
final User updated = observer.get();
assertThat(updated, is(notNullValue()));
assertThat(updated.getName(), is("foo-foo-foo"));
testOwner.handleEvent(Lifecycle.Event.ON_STOP);
observer.reset();
u5.setName("baba");
mUserDao.insertOrReplace(u5);
assertThat(observer.hasValue(), is(false));
}
@Test
public void observeListQuery() throws InterruptedException, ExecutionException,
TimeoutException {
final LiveData<List<User>> userLiveData = mUserDao.liveUsersListByName("frida");
final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
final TestObserver<List<User>> observer = new TestObserver<>();
observe(userLiveData, lifecycleOwner, observer);
assertThat(observer.get(), is(Collections.<User>emptyList()));
observer.reset();
final User user1 = TestUtil.createUser(3);
user1.setName("dog frida");
mUserDao.insert(user1);
assertThat(observer.get(), is(Collections.singletonList(user1)));
observer.reset();
final User user2 = TestUtil.createUser(5);
user2.setName("does not match");
mUserDao.insert(user2);
assertThat(observer.get(), is(Collections.singletonList(user1)));
observer.reset();
user1.setName("i don't match either");
mUserDao.insertOrReplace(user1);
assertThat(observer.get(), is(Collections.<User>emptyList()));
lifecycleOwner.handleEvent(Lifecycle.Event.ON_STOP);
observer.reset();
final User user3 = TestUtil.createUser(9);
user3.setName("painter frida");
mUserDao.insertOrReplace(user3);
assertThat(observer.hasValue(), is(false));
observer.reset();
final User user4 = TestUtil.createUser(11);
user4.setName("friday");
mUserDao.insertOrReplace(user4);
assertThat(observer.hasValue(), is(false));
lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
assertThat(observer.get(), is(Arrays.asList(user4, user3)));
}
@Test
public void liveDataWithPojo() throws ExecutionException, InterruptedException,
TimeoutException {
User[] users = TestUtil.createUsersArray(3, 5, 7, 9);
users[0].setAge(10);
users[0].setWeight(15);
users[1].setAge(20);
users[1].setWeight(25);
users[2].setAge(20);
users[2].setWeight(26);
users[3].setAge(10);
users[3].setWeight(21);
final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
final TestObserver<AvgWeightByAge> observer = new TestObserver<>();
LiveData<AvgWeightByAge> liveData = mUserDao.maxWeightByAgeGroup();
observe(liveData, lifecycleOwner, observer);
assertThat(observer.get(), is(nullValue()));
observer.reset();
mUserDao.insertAll(users);
assertThat(observer.get(), is(new AvgWeightByAge(20, 25.5f)));
observer.reset();
User user3 = mUserDao.load(3);
user3.setWeight(79);
mUserDao.insertOrReplace(user3);
assertThat(observer.get(), is(new AvgWeightByAge(10, 50)));
}
@Test
public void withRelation() throws ExecutionException, InterruptedException, TimeoutException {
final LiveData<UserAndAllPets> liveData = mUserPetDao.liveUserWithPets(3);
final TestObserver<UserAndAllPets> observer = new TestObserver<>();
final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
observe(liveData, lifecycleOwner, observer);
assertThat(observer.get(), is(nullValue()));
observer.reset();
User user = TestUtil.createUser(3);
mUserDao.insert(user);
final UserAndAllPets noPets = observer.get();
assertThat(noPets.user, is(user));
observer.reset();
Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
mPetDao.insertAll(pets);
final UserAndAllPets withPets = observer.get();
assertThat(withPets.user, is(user));
assertThat(withPets.pets, is(Arrays.asList(pets)));
}
@Test
public void withRelationOnly() throws ExecutionException, InterruptedException,
TimeoutException {
LiveData<PetsToys> liveData = mSpecificDogDao.getSpecificDogsToys();
PetsToys expected = new PetsToys();
expected.petId = 123;
Toy toy = new Toy();
toy.setId(1);
toy.setPetId(123);
toy.setName("ball");
final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
final TestObserver<PetsToys> observer = new TestObserver<>();
observe(liveData, lifecycleOwner, observer);
assertThat(observer.get(), is(expected));
observer.reset();
expected.toys.add(toy);
mToyDao.insert(toy);
assertThat(observer.get(), is(expected));
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
public void withWithClause() throws ExecutionException, InterruptedException,
TimeoutException {
LiveData<List<String>> actual =
mWithClauseDao.getUsersWithFactorialIdsLiveData(0);
List<String> expected = new ArrayList<>();
final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
final TestObserver<List<String>> observer = new TestObserver<>();
observe(actual, lifecycleOwner, observer);
assertThat(observer.get(), is(expected));
observer.reset();
User user = new User();
user.setId(0);
user.setName("Zero");
mUserDao.insert(user);
assertThat(observer.get(), is(expected));
observer.reset();
user = new User();
user.setId(1);
user.setName("One");
mUserDao.insert(user);
expected.add("One");
assertThat(observer.get(), is(expected));
observer.reset();
user = new User();
user.setId(6);
user.setName("Six");
mUserDao.insert(user);
assertThat(observer.get(), is(expected));
actual = mWithClauseDao.getUsersWithFactorialIdsLiveData(3);
observe(actual, lifecycleOwner, observer);
expected.add("Six");
assertThat(observer.get(), is(expected));
}
@MediumTest
@Test
public void handleGc() throws ExecutionException, InterruptedException, TimeoutException {
LiveData<User> liveData = mUserDao.liveUserById(3);
final TestObserver<User> observer = new TestObserver<>();
final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
observe(liveData, lifecycleOwner, observer);
assertThat(observer.get(), is(nullValue()));
observer.reset();
final User user = TestUtil.createUser(3);
mUserDao.insert(user);
assertThat(observer.get(), is(notNullValue()));
observer.reset();
forceGc();
String name = UUID.randomUUID().toString();
mUserDao.updateById(3, name);
assertThat(observer.get().getName(), is(name));
// release references
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
lifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY);
}
});
WeakReference<LiveData> weakLiveData = new WeakReference<LiveData>(liveData);
//noinspection UnusedAssignment
liveData = null;
forceGc();
mUserDao.updateById(3, "Bar");
forceGc();
assertThat(InvalidationTrackerTrojan.countObservers(mDatabase.getInvalidationTracker()),
is(0));
assertThat(weakLiveData.get(), nullValue());
}
@Test
public void booleanLiveData() throws ExecutionException, InterruptedException,
TimeoutException {
User user = TestUtil.createUser(3);
user.setAdmin(false);
LiveData<Boolean> adminLiveData = mUserDao.isAdminLiveData(3);
final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
final TestObserver<Boolean> observer = new TestObserver<>();
observe(adminLiveData, lifecycleOwner, observer);
assertThat(observer.get(), is(nullValue()));
mUserDao.insert(user);
assertThat(observer.get(), is(false));
user.setAdmin(true);
mUserDao.insertOrReplace(user);
assertThat(observer.get(), is(true));
}
private void observe(final LiveData liveData, final LifecycleOwner provider,
final Observer observer) throws ExecutionException, InterruptedException {
FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() {
@Override
public Void call() throws Exception {
//noinspection unchecked
liveData.observe(provider, observer);
return null;
}
});
ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
futureTask.get();
}
private void drain() throws TimeoutException, InterruptedException {
mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
}
private static void forceGc() {
Runtime.getRuntime().gc();
Runtime.getRuntime().runFinalization();
Runtime.getRuntime().gc();
Runtime.getRuntime().runFinalization();
}
static class TestLifecycleOwner implements LifecycleOwner {
private LifecycleRegistry mLifecycle;
TestLifecycleOwner() {
mLifecycle = new LifecycleRegistry(this);
}
@Override
public Lifecycle getLifecycle() {
return mLifecycle;
}
void handleEvent(Lifecycle.Event event) {
mLifecycle.handleLifecycleEvent(event);
}
}
private class TestObserver<T> implements Observer<T> {
private T mLastData;
private boolean mHasValue = false;
void reset() {
mHasValue = false;
mLastData = null;
}
@Override
public void onChanged(@Nullable T o) {
mLastData = o;
mHasValue = true;
}
boolean hasValue() throws TimeoutException, InterruptedException {
drain();
return mHasValue;
}
T get() throws InterruptedException, TimeoutException {
drain();
assertThat(hasValue(), is(true));
return mLastData;
}
}
}