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