diff options
author | Jason Monk <jmonk@google.com> | 2017-10-19 09:30:56 -0400 |
---|---|---|
committer | Jason Monk <jmonk@google.com> | 2017-10-19 09:30:56 -0400 |
commit | d439404c9988df6001e4ff8bce31537e2692660e (patch) | |
tree | b1462a7177b8a2791140964761eb49d173cdc878 /android/arch/persistence/room/integration/testapp/test | |
parent | 93b7ee4fce01df52a6607f0b1965cbafdfeaf1a6 (diff) | |
download | android-28-d439404c9988df6001e4ff8bce31537e2692660e.tar.gz |
Import Android SDK Platform P [4402356]
/google/data/ro/projects/android/fetch_artifact \
--bid 4386628 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4402356.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: Ie49e24e1f4ae9dc96306111e953d3db1e1495b53
Diffstat (limited to 'android/arch/persistence/room/integration/testapp/test')
3 files changed, 509 insertions, 90 deletions
diff --git a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java index 84f20ec5..33f40183 100644 --- a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java +++ b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java @@ -17,20 +17,17 @@ package android.arch.persistence.room.integration.testapp.test; import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; -import android.arch.core.executor.ArchTaskExecutor; -import android.arch.core.executor.TaskExecutor; +import android.arch.core.executor.testing.CountingTaskExecutorRule; import android.arch.persistence.room.InvalidationTracker; import android.arch.persistence.room.Room; import android.arch.persistence.room.integration.testapp.TestDatabase; import android.arch.persistence.room.integration.testapp.dao.UserDao; import android.arch.persistence.room.integration.testapp.vo.User; import android.content.Context; -import android.os.Handler; -import android.os.Looper; import android.support.annotation.NonNull; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; @@ -38,17 +35,13 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Set; -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.FutureTask; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * Tests invalidation tracking. @@ -56,138 +49,97 @@ import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidJUnit4.class) public class InvalidationTest { + @Rule + public CountingTaskExecutorRule executorRule = new CountingTaskExecutorRule(); private UserDao mUserDao; private TestDatabase mDb; @Before - public void createDb() { + public void createDb() throws TimeoutException, InterruptedException { Context context = InstrumentationRegistry.getTargetContext(); mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build(); mUserDao = mDb.getUserDao(); - } - - @Before - public void setSingleThreadedIO() { - ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() { - ExecutorService mIOExecutor = Executors.newSingleThreadExecutor(); - Handler mHandler = new Handler(Looper.getMainLooper()); - - @Override - public void executeOnDiskIO(Runnable runnable) { - mIOExecutor.execute(runnable); - } - - @Override - public void postToMainThread(Runnable runnable) { - mHandler.post(runnable); - } - - @Override - public boolean isMainThread() { - return Thread.currentThread() == Looper.getMainLooper().getThread(); - } - }); + drain(); } @After - public void clearExecutor() { - ArchTaskExecutor.getInstance().setDelegate(null); + public void closeDb() throws TimeoutException, InterruptedException { + mDb.close(); + drain(); } - private void waitUntilIOThreadIsIdle() { - FutureTask<Void> future = new FutureTask<>(new Callable<Void>() { - @Override - public Void call() throws Exception { - return null; - } - }); - ArchTaskExecutor.getInstance().executeOnDiskIO(future); - //noinspection TryWithIdenticalCatches - try { - future.get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } + private void drain() throws TimeoutException, InterruptedException { + executorRule.drainTasks(1, TimeUnit.MINUTES); } @Test - public void testInvalidationOnUpdate() throws InterruptedException { + public void testInvalidationOnUpdate() throws InterruptedException, TimeoutException { User user = TestUtil.createUser(3); mUserDao.insert(user); - LatchObserver observer = new LatchObserver(1, "User"); + LoggingObserver observer = new LoggingObserver("User"); mDb.getInvalidationTracker().addObserver(observer); + drain(); mUserDao.updateById(3, "foo2"); - waitUntilIOThreadIsIdle(); - assertThat(observer.await(), is(true)); + drain(); assertThat(observer.getInvalidatedTables(), hasSize(1)); assertThat(observer.getInvalidatedTables(), hasItem("User")); } @Test - public void testInvalidationOnDelete() throws InterruptedException { + public void testInvalidationOnDelete() throws InterruptedException, TimeoutException { User user = TestUtil.createUser(3); mUserDao.insert(user); - LatchObserver observer = new LatchObserver(1, "User"); + LoggingObserver observer = new LoggingObserver("User"); mDb.getInvalidationTracker().addObserver(observer); + drain(); mUserDao.delete(user); - waitUntilIOThreadIsIdle(); - assertThat(observer.await(), is(true)); + drain(); assertThat(observer.getInvalidatedTables(), hasSize(1)); assertThat(observer.getInvalidatedTables(), hasItem("User")); } @Test - public void testInvalidationOnInsert() throws InterruptedException { - LatchObserver observer = new LatchObserver(1, "User"); + public void testInvalidationOnInsert() throws InterruptedException, TimeoutException { + LoggingObserver observer = new LoggingObserver("User"); mDb.getInvalidationTracker().addObserver(observer); + drain(); mUserDao.insert(TestUtil.createUser(3)); - waitUntilIOThreadIsIdle(); - assertThat(observer.await(), is(true)); + drain(); assertThat(observer.getInvalidatedTables(), hasSize(1)); assertThat(observer.getInvalidatedTables(), hasItem("User")); } @Test - public void testDontInvalidateOnLateInsert() throws InterruptedException { - LatchObserver observer = new LatchObserver(1, "User"); + public void testDontInvalidateOnLateInsert() throws InterruptedException, TimeoutException { + LoggingObserver observer = new LoggingObserver("User"); mUserDao.insert(TestUtil.createUser(3)); - waitUntilIOThreadIsIdle(); + drain(); mDb.getInvalidationTracker().addObserver(observer); - waitUntilIOThreadIsIdle(); - assertThat(observer.await(), is(false)); + drain(); + assertThat(observer.getInvalidatedTables(), nullValue()); } @Test - public void testMultipleTables() throws InterruptedException { - LatchObserver observer = new LatchObserver(1, "User", "Pet"); + public void testMultipleTables() throws InterruptedException, TimeoutException { + LoggingObserver observer = new LoggingObserver("User", "Pet"); mDb.getInvalidationTracker().addObserver(observer); + drain(); mUserDao.insert(TestUtil.createUser(3)); - waitUntilIOThreadIsIdle(); - assertThat(observer.await(), is(true)); + drain(); assertThat(observer.getInvalidatedTables(), hasSize(1)); assertThat(observer.getInvalidatedTables(), hasItem("User")); } - private static class LatchObserver extends InvalidationTracker.Observer { - CountDownLatch mLatch; - + private static class LoggingObserver extends InvalidationTracker.Observer { private Set<String> mInvalidatedTables; - LatchObserver(int permits, String... tables) { + LoggingObserver(String... tables) { super(tables); - mLatch = new CountDownLatch(permits); - } - - boolean await() throws InterruptedException { - return mLatch.await(5, TimeUnit.SECONDS); } @Override public void onInvalidated(@NonNull Set<String> tables) { mInvalidatedTables = tables; - mLatch.countDown(); } Set<String> getInvalidatedTables() { diff --git a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java index e11117e4..2735c05a 100644 --- a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java +++ b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java @@ -166,17 +166,13 @@ public class QueryDataSourceTest extends TestDatabaseTest { p = dataSource.loadBefore(15, list.get(0), 10); assertNotNull(p); - for (User u : p) { - list.add(0, u); - } + list.addAll(0, p); assertArrayEquals(Arrays.copyOfRange(expected, 5, 35), list.toArray()); p = dataSource.loadBefore(5, list.get(0), 10); assertNotNull(p); - for (User u : p) { - list.add(0, u); - } + list.addAll(0, p); assertArrayEquals(Arrays.copyOfRange(expected, 0, 35), list.toArray()); } diff --git a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java new file mode 100644 index 00000000..854c8627 --- /dev/null +++ b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java @@ -0,0 +1,471 @@ +/* + * 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 android.arch.persistence.room.integration.testapp.test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +import android.arch.core.executor.ArchTaskExecutor; +import android.arch.core.executor.testing.CountingTaskExecutorRule; +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.paging.LivePagedListProvider; +import android.arch.paging.PagedList; +import android.arch.paging.TiledDataSource; +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Database; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.Ignore; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.PrimaryKey; +import android.arch.persistence.room.Query; +import android.arch.persistence.room.Relation; +import android.arch.persistence.room.Room; +import android.arch.persistence.room.RoomDatabase; +import android.arch.persistence.room.RoomWarnings; +import android.arch.persistence.room.Transaction; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Collections; +import java.util.List; +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; +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.Flowable; +import io.reactivex.Maybe; +import io.reactivex.Single; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subscribers.TestSubscriber; + +@SmallTest +@RunWith(Parameterized.class) +public class QueryTransactionTest { + @Rule + public CountingTaskExecutorRule countingTaskExecutorRule = new CountingTaskExecutorRule(); + private static final AtomicInteger sStartedTransactionCount = new AtomicInteger(0); + private TransactionDb mDb; + private final boolean mUseTransactionDao; + private Entity1Dao mDao; + private final LiveDataQueryTest.TestLifecycleOwner mLifecycleOwner = new LiveDataQueryTest + .TestLifecycleOwner(); + + @NonNull + @Parameterized.Parameters(name = "useTransaction_{0}") + public static Boolean[] getParams() { + return new Boolean[]{false, true}; + } + + public QueryTransactionTest(boolean useTransactionDao) { + mUseTransactionDao = useTransactionDao; + } + + @Before + public void initDb() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + mLifecycleOwner.handleEvent(Lifecycle.Event.ON_START); + } + }); + + resetTransactionCount(); + mDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(), + TransactionDb.class).build(); + mDao = mUseTransactionDao ? mDb.transactionDao() : mDb.dao(); + drain(); + } + + @After + public void closeDb() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + mLifecycleOwner.handleEvent(Lifecycle.Event.ON_DESTROY); + } + }); + drain(); + mDb.close(); + } + + @Test + public void readList() { + mDao.insert(new Entity1(1, "foo")); + resetTransactionCount(); + + int expectedTransactionCount = mUseTransactionDao ? 1 : 0; + List<Entity1> allEntities = mDao.allEntities(); + assertTransactionCount(allEntities, expectedTransactionCount); + } + + @Test + public void liveData() { + LiveData<List<Entity1>> listLiveData = mDao.liveData(); + observeForever(listLiveData); + drain(); + assertThat(listLiveData.getValue(), is(Collections.<Entity1>emptyList())); + + resetTransactionCount(); + mDao.insert(new Entity1(1, "foo")); + drain(); + + //noinspection ConstantConditions + assertThat(listLiveData.getValue().size(), is(1)); + int expectedTransactionCount = mUseTransactionDao ? 2 : 1; + assertTransactionCount(listLiveData.getValue(), expectedTransactionCount); + } + + @Test + public void flowable() { + Flowable<List<Entity1>> flowable = mDao.flowable(); + TestSubscriber<List<Entity1>> subscriber = observe(flowable); + drain(); + assertThat(subscriber.values().size(), is(1)); + + resetTransactionCount(); + mDao.insert(new Entity1(1, "foo")); + drain(); + + List<Entity1> allEntities = subscriber.values().get(1); + assertThat(allEntities.size(), is(1)); + int expectedTransactionCount = mUseTransactionDao ? 2 : 1; + assertTransactionCount(allEntities, expectedTransactionCount); + } + + @Test + public void maybe() { + mDao.insert(new Entity1(1, "foo")); + resetTransactionCount(); + + int expectedTransactionCount = mUseTransactionDao ? 1 : 0; + Maybe<List<Entity1>> listMaybe = mDao.maybe(); + TestObserver<List<Entity1>> observer = observe(listMaybe); + drain(); + List<Entity1> allEntities = observer.values().get(0); + assertTransactionCount(allEntities, expectedTransactionCount); + } + + @Test + public void single() { + mDao.insert(new Entity1(1, "foo")); + resetTransactionCount(); + + int expectedTransactionCount = mUseTransactionDao ? 1 : 0; + Single<List<Entity1>> listMaybe = mDao.single(); + TestObserver<List<Entity1>> observer = observe(listMaybe); + drain(); + List<Entity1> allEntities = observer.values().get(0); + assertTransactionCount(allEntities, expectedTransactionCount); + } + + @Test + public void relation() { + mDao.insert(new Entity1(1, "foo")); + mDao.insert(new Child(1, 1)); + mDao.insert(new Child(2, 1)); + resetTransactionCount(); + + List<Entity1WithChildren> result = mDao.withRelation(); + int expectedTransactionCount = mUseTransactionDao ? 1 : 0; + assertTransactionCountWithChildren(result, expectedTransactionCount); + } + + @Test + public void pagedList() { + LiveData<PagedList<Entity1>> pagedList = mDao.pagedList().create(null, 10); + observeForever(pagedList); + drain(); + assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0)); + + mDao.insert(new Entity1(1, "foo")); + drain(); + //noinspection ConstantConditions + assertThat(pagedList.getValue().size(), is(1)); + assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 2 : 1); + + mDao.insert(new Entity1(2, "bar")); + drain(); + assertThat(pagedList.getValue().size(), is(2)); + assertTransactionCount(pagedList.getValue(), mUseTransactionDao ? 4 : 2); + } + + @Test + public void dataSource() { + mDao.insert(new Entity1(2, "bar")); + drain(); + resetTransactionCount(); + TiledDataSource<Entity1> dataSource = mDao.dataSource(); + dataSource.loadRange(0, 10); + assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0)); + } + + private void assertTransactionCount(List<Entity1> allEntities, int expectedTransactionCount) { + assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount)); + assertThat(allEntities.isEmpty(), is(false)); + for (Entity1 entity1 : allEntities) { + assertThat(entity1.transactionId, is(expectedTransactionCount)); + } + } + + private void assertTransactionCountWithChildren(List<Entity1WithChildren> allEntities, + int expectedTransactionCount) { + assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount)); + assertThat(allEntities.isEmpty(), is(false)); + for (Entity1WithChildren entity1 : allEntities) { + assertThat(entity1.transactionId, is(expectedTransactionCount)); + assertThat(entity1.children, notNullValue()); + assertThat(entity1.children.isEmpty(), is(false)); + for (Child child : entity1.children) { + assertThat(child.transactionId, is(expectedTransactionCount)); + } + } + } + + private void resetTransactionCount() { + sStartedTransactionCount.set(0); + } + + private void drain() { + try { + countingTaskExecutorRule.drainTasks(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new AssertionError("interrupted", e); + } catch (TimeoutException e) { + throw new AssertionError("drain timed out", e); + } + } + + private <T> TestSubscriber<T> observe(final Flowable<T> flowable) { + TestSubscriber<T> subscriber = new TestSubscriber<>(); + flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor())) + .subscribeWith(subscriber); + return subscriber; + } + + private <T> TestObserver<T> observe(final Maybe<T> maybe) { + TestObserver<T> observer = new TestObserver<>(); + maybe.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor())) + .subscribeWith(observer); + return observer; + } + + private <T> TestObserver<T> observe(final Single<T> single) { + TestObserver<T> observer = new TestObserver<>(); + single.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor())) + .subscribeWith(observer); + return observer; + } + + private <T> void observeForever(final LiveData<T> liveData) { + FutureTask<Void> futureTask = new FutureTask<>(new Callable<Void>() { + @Override + public Void call() throws Exception { + liveData.observe(mLifecycleOwner, new Observer<T>() { + @Override + public void onChanged(@Nullable T t) { + + } + }); + return null; + } + }); + ArchTaskExecutor.getMainThreadExecutor().execute(futureTask); + try { + futureTask.get(); + } catch (InterruptedException e) { + throw new AssertionError("interrupted", e); + } catch (ExecutionException e) { + throw new AssertionError("execution error", e); + } + } + + @SuppressWarnings("WeakerAccess") + static class Entity1WithChildren extends Entity1 { + @Relation(entity = Child.class, parentColumn = "id", + entityColumn = "entity1Id") + public List<Child> children; + + Entity1WithChildren(int id, String value) { + super(id, value); + } + } + + @SuppressWarnings("WeakerAccess") + @Entity + static class Child { + @PrimaryKey(autoGenerate = true) + public int id; + public int entity1Id; + @Ignore + public final int transactionId = sStartedTransactionCount.get(); + + Child(int id, int entity1Id) { + this.id = id; + this.entity1Id = entity1Id; + } + } + + @SuppressWarnings("WeakerAccess") + @Entity + static class Entity1 { + @PrimaryKey(autoGenerate = true) + public int id; + public String value; + @Ignore + public final int transactionId = sStartedTransactionCount.get(); + + Entity1(int id, String value) { + this.id = id; + this.value = value; + } + } + + // we don't support dao inheritance for queries so for now, go with this + interface Entity1Dao { + String SELECT_ALL = "select * from Entity1"; + + List<Entity1> allEntities(); + + Flowable<List<Entity1>> flowable(); + + Maybe<List<Entity1>> maybe(); + + Single<List<Entity1>> single(); + + LiveData<List<Entity1>> liveData(); + + List<Entity1WithChildren> withRelation(); + + LivePagedListProvider<Integer, Entity1> pagedList(); + + TiledDataSource<Entity1> dataSource(); + + @Insert + void insert(Entity1 entity1); + + @Insert + void insert(Child entity1); + } + + @Dao + interface EntityDao extends Entity1Dao { + @Override + @Query(SELECT_ALL) + List<Entity1> allEntities(); + + @Override + @Query(SELECT_ALL) + Flowable<List<Entity1>> flowable(); + + @Override + @Query(SELECT_ALL) + LiveData<List<Entity1>> liveData(); + + @Override + @Query(SELECT_ALL) + Maybe<List<Entity1>> maybe(); + + @Override + @Query(SELECT_ALL) + Single<List<Entity1>> single(); + + @Override + @Query(SELECT_ALL) + @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION) + List<Entity1WithChildren> withRelation(); + + @Override + @Query(SELECT_ALL) + LivePagedListProvider<Integer, Entity1> pagedList(); + + @Override + @Query(SELECT_ALL) + TiledDataSource<Entity1> dataSource(); + } + + @Dao + interface TransactionDao extends Entity1Dao { + @Override + @Transaction + @Query(SELECT_ALL) + List<Entity1> allEntities(); + + @Override + @Transaction + @Query(SELECT_ALL) + Flowable<List<Entity1>> flowable(); + + @Override + @Transaction + @Query(SELECT_ALL) + LiveData<List<Entity1>> liveData(); + + @Override + @Transaction + @Query(SELECT_ALL) + Maybe<List<Entity1>> maybe(); + + @Override + @Transaction + @Query(SELECT_ALL) + Single<List<Entity1>> single(); + + @Override + @Transaction + @Query(SELECT_ALL) + List<Entity1WithChildren> withRelation(); + + @Override + @Transaction + @Query(SELECT_ALL) + LivePagedListProvider<Integer, Entity1> pagedList(); + + @Override + @Transaction + @Query(SELECT_ALL) + TiledDataSource<Entity1> dataSource(); + } + + @Database(version = 1, entities = {Entity1.class, Child.class}, exportSchema = false) + abstract static class TransactionDb extends RoomDatabase { + abstract EntityDao dao(); + + abstract TransactionDao transactionDao(); + + @Override + public void beginTransaction() { + super.beginTransaction(); + sStartedTransactionCount.incrementAndGet(); + } + } +} |