• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.room.integration.testapp.test;
18 
19 import static org.hamcrest.CoreMatchers.equalTo;
20 import static org.hamcrest.CoreMatchers.hasItem;
21 import static org.hamcrest.CoreMatchers.is;
22 import static org.hamcrest.CoreMatchers.not;
23 import static org.hamcrest.MatcherAssert.assertThat;
24 import static org.hamcrest.Matchers.equalToIgnoringCase;
25 import static org.hamcrest.Matchers.hasSize;
26 import static org.mockito.ArgumentMatchers.argThat;
27 import static org.mockito.Mockito.mock;
28 import static org.mockito.Mockito.timeout;
29 import static org.mockito.Mockito.verify;
30 
31 import android.content.Context;
32 import android.database.Cursor;
33 import android.support.test.InstrumentationRegistry;
34 import android.support.test.filters.LargeTest;
35 import android.support.test.filters.SdkSuppress;
36 import android.support.test.runner.AndroidJUnit4;
37 
38 import androidx.annotation.NonNull;
39 import androidx.lifecycle.LiveData;
40 import androidx.lifecycle.Observer;
41 import androidx.room.InvalidationTracker;
42 import androidx.room.Room;
43 import androidx.room.RoomDatabase;
44 import androidx.room.integration.testapp.TestDatabase;
45 import androidx.room.integration.testapp.dao.UserDao;
46 import androidx.room.integration.testapp.vo.User;
47 import androidx.sqlite.db.SupportSQLiteDatabase;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 
54 import java.util.ArrayList;
55 import java.util.List;
56 import java.util.Set;
57 import java.util.concurrent.CountDownLatch;
58 import java.util.concurrent.ExecutionException;
59 import java.util.concurrent.ExecutorService;
60 import java.util.concurrent.Executors;
61 import java.util.concurrent.Future;
62 import java.util.concurrent.TimeUnit;
63 
64 @RunWith(AndroidJUnit4.class)
65 @LargeTest
66 @SdkSuppress(minSdkVersion = 16)
67 public class WriteAheadLoggingTest {
68 
69     private static final String DATABASE_NAME = "wal.db";
70     private TestDatabase mDatabase;
71 
72     @Before
openDatabase()73     public void openDatabase() {
74         Context context = InstrumentationRegistry.getTargetContext();
75         context.deleteDatabase(DATABASE_NAME);
76         mDatabase = Room.databaseBuilder(context, TestDatabase.class, DATABASE_NAME)
77                 .setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
78                 .build();
79     }
80 
81     @After
closeDatabase()82     public void closeDatabase() {
83         mDatabase.close();
84         Context context = InstrumentationRegistry.getTargetContext();
85         context.deleteDatabase(DATABASE_NAME);
86     }
87 
88     @Test
checkJournalMode()89     public void checkJournalMode() {
90         Cursor c = null;
91         try {
92             SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
93             c = db.query("PRAGMA journal_mode");
94             c.moveToFirst();
95             String journalMode = c.getString(0);
96             assertThat(journalMode, is(equalToIgnoringCase("wal")));
97         } finally {
98             if (c != null) {
99                 c.close();
100             }
101         }
102     }
103 
104     @Test
disableWal()105     public void disableWal() {
106         Context context = InstrumentationRegistry.getTargetContext();
107         mDatabase.close();
108         mDatabase = Room.databaseBuilder(context, TestDatabase.class, DATABASE_NAME)
109                 .setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
110                 .build();
111         Cursor c = null;
112         try {
113             SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
114             c = db.query("PRAGMA journal_mode");
115             c.moveToFirst();
116             String journalMode = c.getString(0);
117             assertThat(journalMode, is(not(equalToIgnoringCase("wal"))));
118         } finally {
119             if (c != null) {
120                 c.close();
121             }
122         }
123     }
124 
125     @Test
observeLiveData()126     public void observeLiveData() {
127         UserDao dao = mDatabase.getUserDao();
128         LiveData<User> user1 = dao.liveUserById(1);
129         Observer<User> observer = startObserver(user1);
130         dao.insert(TestUtil.createUser(1));
131         verify(observer, timeout(3000).atLeastOnce())
132                 .onChanged(argThat(user -> user != null && user.getId() == 1));
133         stopObserver(user1, observer);
134     }
135 
136     @Test
observeLiveDataWithTransaction()137     public void observeLiveDataWithTransaction() {
138         UserDao dao = mDatabase.getUserDao();
139         LiveData<User> user1 = dao.liveUserByIdInTransaction(1);
140         Observer<User> observer = startObserver(user1);
141         dao.insert(TestUtil.createUser(1));
142         verify(observer, timeout(3000).atLeastOnce())
143                 .onChanged(argThat(user -> user != null && user.getId() == 1));
144         stopObserver(user1, observer);
145     }
146 
147     @Test
parallelWrites()148     public void parallelWrites() throws InterruptedException, ExecutionException {
149         int numberOfThreads = 10;
150         ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);
151         ArrayList<Future<Boolean>> futures = new ArrayList<>();
152         for (int i = 0; i < numberOfThreads; i++) {
153             final int id = i + 1;
154             futures.add(i, executor.submit(() -> {
155                 User user = TestUtil.createUser(id);
156                 user.setName("user" + id);
157                 mDatabase.getUserDao().insert(user);
158                 return true;
159             }));
160         }
161         LiveData<List<User>> usersList = mDatabase.getUserDao().liveUsersListByName("user");
162         Observer<List<User>> observer = startObserver(usersList);
163         for (Future future : futures) {
164             assertThat(future.get(), is(true));
165         }
166         verify(observer, timeout(3000).atLeastOnce())
167                 .onChanged(argThat(users -> users != null && users.size() == numberOfThreads));
168         stopObserver(usersList, observer);
169     }
170 
171     @Test
readInBackground()172     public void readInBackground() throws InterruptedException, ExecutionException {
173         final UserDao dao = mDatabase.getUserDao();
174         final User user1 = TestUtil.createUser(1);
175         dao.insert(user1);
176         try {
177             mDatabase.beginTransaction();
178             dao.delete(user1);
179             ExecutorService executor = Executors.newSingleThreadExecutor();
180             Future<?> future = executor.submit(() ->
181                     assertThat(dao.load(1), is(equalTo(user1))));
182             future.get();
183             mDatabase.setTransactionSuccessful();
184         } finally {
185             mDatabase.endTransaction();
186         }
187         assertThat(dao.count(), is(0));
188     }
189 
190     @Test
191     @LargeTest
observeInvalidationInBackground()192     public void observeInvalidationInBackground() throws InterruptedException, ExecutionException {
193         final UserDao dao = mDatabase.getUserDao();
194         final User user1 = TestUtil.createUser(1);
195         final CountDownLatch observerRegistered = new CountDownLatch(1);
196         final CountDownLatch onInvalidatedCalled = new CountDownLatch(1);
197         dao.insert(user1);
198         Future future;
199         try {
200             mDatabase.beginTransaction();
201             dao.delete(user1);
202             future = Executors.newSingleThreadExecutor().submit(() -> {
203                 // Adding this observer will be blocked by the surrounding transaction.
204                 mDatabase.getInvalidationTracker().addObserver(
205                         new InvalidationTracker.Observer("User") {
206                             @Override
207                             public void onInvalidated(@NonNull Set<String> tables) {
208                                 onInvalidatedCalled.countDown(); // This should not happen
209                             }
210                         });
211                 observerRegistered.countDown();
212             });
213             mDatabase.setTransactionSuccessful();
214         } finally {
215             assertThat(observerRegistered.getCount(), is(1L));
216             mDatabase.endTransaction();
217         }
218         assertThat(dao.count(), is(0));
219         assertThat(observerRegistered.await(3000, TimeUnit.MILLISECONDS), is(true));
220         future.get();
221         assertThat(onInvalidatedCalled.await(500, TimeUnit.MILLISECONDS), is(false));
222     }
223 
224     @Test
invalidation()225     public void invalidation() throws InterruptedException {
226         final CountDownLatch latch = new CountDownLatch(1);
227         mDatabase.getInvalidationTracker().addObserver(new InvalidationTracker.Observer("User") {
228             @Override
229             public void onInvalidated(@NonNull Set<String> tables) {
230                 assertThat(tables, hasSize(1));
231                 assertThat(tables, hasItem("User"));
232                 latch.countDown();
233             }
234         });
235         mDatabase.getUserDao().insert(TestUtil.createUser(1));
236         assertThat(latch.await(3000, TimeUnit.MILLISECONDS), is(true));
237     }
238 
startObserver(LiveData<T> liveData)239     private static <T> Observer<T> startObserver(LiveData<T> liveData) {
240         @SuppressWarnings("unchecked")
241         Observer<T> observer = mock(Observer.class);
242         InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
243                 liveData.observeForever(observer));
244         return observer;
245     }
246 
stopObserver(LiveData<T> liveData, Observer<T> observer)247     private static <T> void stopObserver(LiveData<T> liveData, Observer<T> observer) {
248         InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
249                 liveData.removeObserver(observer));
250     }
251 }
252