1 /* 2 * Copyright (C) 2010 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 com.android.email.provider; 18 19 import android.content.ContentResolver; 20 import android.content.ContentUris; 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.database.CursorWrapper; 24 import android.database.MatrixCursor; 25 import android.net.Uri; 26 import android.test.ProviderTestCase2; 27 import android.test.suitebuilder.annotation.Suppress; 28 29 import com.android.email.provider.ContentCache.CacheToken; 30 import com.android.email.provider.ContentCache.CachedCursor; 31 import com.android.email.provider.ContentCache.TokenList; 32 import com.android.emailcommon.provider.Account; 33 import com.android.emailcommon.provider.EmailContent; 34 import com.android.emailcommon.provider.Mailbox; 35 import com.android.mail.utils.MatrixCursorWithCachedColumns; 36 37 /** 38 * Tests of ContentCache 39 * 40 * You can run this entire test case with: 41 * runtest -c com.android.email.provider.ContentCacheTests email 42 */ 43 @Suppress 44 public class ContentCacheTests extends ProviderTestCase2<EmailProvider> { 45 46 EmailProvider mProvider; 47 Context mMockContext; 48 49 public ContentCacheTests() { 50 super(EmailProvider.class, EmailContent.AUTHORITY); 51 } 52 53 @Override 54 public void setUp() throws Exception { 55 super.setUp(); 56 mMockContext = getMockContext(); 57 } 58 59 @Override 60 public void tearDown() throws Exception { 61 super.tearDown(); 62 } 63 64 public void testCounterMap() { 65 ContentCache.CounterMap<String> map = new ContentCache.CounterMap<String>(4); 66 // Make sure we can find added items 67 map.add("1"); 68 assertTrue(map.contains("1")); 69 map.add("2"); 70 map.add("2"); 71 // Make sure we can remove once for each add 72 map.subtract("2"); 73 assertTrue(map.contains("2")); 74 map.subtract("2"); 75 // Make sure that over-removing throws an exception 76 try { 77 map.subtract("2"); 78 fail("Removing a third time should throw an exception"); 79 } catch (IllegalStateException e) { 80 } 81 try { 82 map.subtract("3"); 83 fail("Removing object never added should throw an exception"); 84 } catch (IllegalStateException e) { 85 } 86 // There should only be one item in the map ("1") 87 assertEquals(1, map.size()); 88 assertTrue(map.contains("1")); 89 } 90 91 public void testTokenList() { 92 TokenList list = new TokenList("Name"); 93 94 // Add two tokens for "1" 95 CacheToken token1a = list.add("1"); 96 assertTrue(token1a.isValid()); 97 assertEquals("1", token1a.getId()); 98 assertEquals(1, list.size()); 99 CacheToken token1b = list.add("1"); 100 assertTrue(token1b.isValid()); 101 assertEquals("1", token1b.getId()); 102 assertTrue(token1a.equals(token1b)); 103 assertEquals(2, list.size()); 104 105 // Add a token for "2" 106 CacheToken token2 = list.add("2"); 107 assertFalse(token1a.equals(token2)); 108 assertEquals(3, list.size()); 109 110 // Invalidate "1"; there should be two tokens invalidated 111 assertEquals(2, list.invalidateTokens("1")); 112 assertFalse(token1a.isValid()); 113 assertFalse(token1b.isValid()); 114 // Token2 should still be valid 115 assertTrue(token2.isValid()); 116 // Only token2 should be in the list now (invalidation removes tokens) 117 assertEquals(1, list.size()); 118 assertEquals(token2, list.get(0)); 119 120 // Add 3 tokens for "3" 121 CacheToken token3a = list.add("3"); 122 CacheToken token3b = list.add("3"); 123 CacheToken token3c = list.add("3"); 124 // Remove two of them 125 assertTrue(list.remove(token3a)); 126 assertTrue(list.remove(token3b)); 127 // Removing tokens doesn't invalidate them 128 assertTrue(token3a.isValid()); 129 assertTrue(token3b.isValid()); 130 assertTrue(token3c.isValid()); 131 // There should be two items left "3" and "2" 132 assertEquals(2, list.size()); 133 } 134 135 public void testCachedCursors() { 136 final ContentResolver resolver = mMockContext.getContentResolver(); 137 final Context context = mMockContext; 138 139 // Create account and two mailboxes 140 Account acct = ProviderTestUtils.setupAccount("account", true, context); 141 ProviderTestUtils.setupMailbox("box1", acct.mId, true, context); 142 Mailbox box = ProviderTestUtils.setupMailbox("box2", acct.mId, true, context); 143 144 // We need to test with a query that only returns one row (others can't be put in a 145 // CachedCursor) 146 Uri uri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, box.mId); 147 Cursor cursor = 148 resolver.query(uri, Mailbox.CONTENT_PROJECTION, null, null, null); 149 // ContentResolver gives us back a wrapper 150 assertTrue(cursor instanceof CursorWrapper); 151 // The wrappedCursor should be a CachedCursor 152 Cursor wrappedCursor = ((CursorWrapper)cursor).getWrappedCursor(); 153 assertTrue(wrappedCursor instanceof CachedCursor); 154 CachedCursor cachedCursor = (CachedCursor)wrappedCursor; 155 // The cursor wrapped in cachedCursor is the underlying cursor 156 Cursor activeCursor = cachedCursor.getWrappedCursor(); 157 158 // The cursor should be in active cursors 159 int activeCount = ContentCache.sActiveCursors.getCount(activeCursor); 160 assertEquals(1, activeCount); 161 162 // Some basic functionality that shouldn't throw exceptions and should otherwise act as the 163 // underlying cursor would 164 String[] columnNames = cursor.getColumnNames(); 165 assertEquals(Mailbox.CONTENT_PROJECTION.length, columnNames.length); 166 for (int i = 0; i < Mailbox.CONTENT_PROJECTION.length; i++) { 167 assertEquals(Mailbox.CONTENT_PROJECTION[i], columnNames[i]); 168 } 169 170 assertEquals(1, cursor.getCount()); 171 cursor.moveToNext(); 172 assertEquals(0, cursor.getPosition()); 173 cursor.moveToPosition(0); 174 assertEquals(0, cursor.getPosition()); 175 assertFalse(cursor.moveToPosition(1)); 176 177 cursor.close(); 178 // We've closed the cached cursor; make sure 179 assertTrue(cachedCursor.isClosed()); 180 // The underlying cursor shouldn't be closed because it's in a cache (we'll test 181 // that in testContentCache) 182 assertFalse(activeCursor.isClosed()); 183 // Our cursor should no longer be in the active cursors map 184 assertFalse(ContentCache.sActiveCursors.contains(activeCursor)); 185 186 // TODO - change the code or the test to enforce the assertion that a cached cursor 187 // should have only zero or one rows. We cannot test this in the constructor, however, 188 // due to potential for deadlock. 189 // // Make sure that we won't accept cursors with multiple rows 190 // cursor = resolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, null, null, null); 191 // try { 192 // cursor = new CachedCursor(cursor, null, "Foo"); 193 // fail("Mustn't accept cursor with more than one row"); 194 // } catch (IllegalArgumentException e) { 195 // // Correct 196 // } 197 } 198 199 private static final String[] SIMPLE_PROJECTION = new String[] {"Foo"}; 200 private static final Object[] SIMPLE_ROW = new Object[] {"Bar"}; 201 private Cursor getOneRowCursor() { 202 MatrixCursor cursor = new MatrixCursorWithCachedColumns(SIMPLE_PROJECTION, 1); 203 cursor.addRow(SIMPLE_ROW); 204 return cursor; 205 } 206 207 public void testContentCacheRemoveEldestEntry() { 208 // Create a cache of size 2 209 ContentCache cache = new ContentCache("Name", SIMPLE_PROJECTION, 2); 210 // Random cursor; what's in it doesn't matter 211 Cursor cursor1 = getOneRowCursor(); 212 // Get a token for arbitrary object named "1" 213 CacheToken token = cache.getCacheToken("1"); 214 // Put the cursor in the cache 215 cache.putCursor(cursor1, "1", SIMPLE_PROJECTION, token); 216 assertEquals(1, cache.size()); 217 218 // Add another random cursor; what's in it doesn't matter 219 Cursor cursor2 = getOneRowCursor(); 220 // Get a token for arbitrary object named "2" 221 token = cache.getCacheToken("2"); 222 // Put the cursor in the cache 223 cache.putCursor(cursor1, "2", SIMPLE_PROJECTION, token); 224 assertEquals(2, cache.size()); 225 226 // We should be able to find both now in the cache 227 Cursor cachedCursor = cache.getCachedCursor("1", SIMPLE_PROJECTION); 228 assertNotNull(cachedCursor); 229 assertTrue(cachedCursor instanceof CachedCursor); 230 cachedCursor = cache.getCachedCursor("2", SIMPLE_PROJECTION); 231 assertNotNull(cachedCursor); 232 assertTrue(cachedCursor instanceof CachedCursor); 233 234 // Both cursors should be open 235 assertFalse(cursor1.isClosed()); 236 assertFalse(cursor2.isClosed()); 237 238 // Add another random cursor; what's in it doesn't matter 239 Cursor cursor3 = getOneRowCursor(); 240 // Get a token for arbitrary object named "3" 241 token = cache.getCacheToken("3"); 242 // Put the cursor in the cache 243 cache.putCursor(cursor1, "3", SIMPLE_PROJECTION, token); 244 // We should never have more than 2 entries in the cache 245 assertEquals(2, cache.size()); 246 247 // The first cursor we added should no longer be in the cache (it's the eldest) 248 cachedCursor = cache.getCachedCursor("1", SIMPLE_PROJECTION); 249 assertNull(cachedCursor); 250 // The cursors for 2 and 3 should be cached 251 cachedCursor = cache.getCachedCursor("2", SIMPLE_PROJECTION); 252 assertNotNull(cachedCursor); 253 assertTrue(cachedCursor instanceof CachedCursor); 254 cachedCursor = cache.getCachedCursor("3", SIMPLE_PROJECTION); 255 assertNotNull(cachedCursor); 256 assertTrue(cachedCursor instanceof CachedCursor); 257 258 // Even cursor1 should be open, since all cached cursors are in mActiveCursors until closed 259 assertFalse(cursor1.isClosed()); 260 assertFalse(cursor2.isClosed()); 261 assertFalse(cursor3.isClosed()); 262 } 263 264 public void testCloseCachedCursor() { 265 // Create a cache of size 2 266 ContentCache cache = new ContentCache("Name", SIMPLE_PROJECTION, 2); 267 // Random cursor; what's in it doesn't matter 268 Cursor underlyingCursor = getOneRowCursor(); 269 Cursor cachedCursor1 = new CachedCursor(underlyingCursor, cache, "1"); 270 Cursor cachedCursor2 = new CachedCursor(underlyingCursor, cache, "1"); 271 assertEquals(2, ContentCache.sActiveCursors.getCount(underlyingCursor)); 272 cachedCursor1.close(); 273 assertTrue(cachedCursor1.isClosed()); 274 // Underlying cursor should be open (still one cached cursor open) 275 assertFalse(underlyingCursor.isClosed()); 276 cachedCursor2.close(); 277 assertTrue(cachedCursor2.isClosed()); 278 assertEquals(0, ContentCache.sActiveCursors.getCount(underlyingCursor)); 279 // Underlying cursor should be closed (no cached cursors open) 280 assertTrue(underlyingCursor.isClosed()); 281 282 underlyingCursor = getOneRowCursor(); 283 cachedCursor1 = cache.putCursor( 284 underlyingCursor, "2", SIMPLE_PROJECTION, cache.getCacheToken("2")); 285 cachedCursor2 = new CachedCursor(underlyingCursor, cache, "2"); 286 assertEquals(2, ContentCache.sActiveCursors.getCount(underlyingCursor)); 287 cachedCursor1.close(); 288 cachedCursor2.close(); 289 assertEquals(0, ContentCache.sActiveCursors.getCount(underlyingCursor)); 290 // Underlying cursor should still be open; it's in the cache 291 assertFalse(underlyingCursor.isClosed()); 292 // Cache a new cursor 293 cachedCursor2 = new CachedCursor(underlyingCursor, cache, "2"); 294 assertEquals(1, ContentCache.sActiveCursors.getCount(underlyingCursor)); 295 // Remove "2" from the cache and close the cursor 296 cache.invalidate(); 297 cachedCursor2.close(); 298 // The underlying cursor should now be closed (not in the cache and no cached cursors) 299 assertEquals(0, ContentCache.sActiveCursors.getCount(underlyingCursor)); 300 assertTrue(underlyingCursor.isClosed()); 301 } 302 } 303