| /* |
| * Copyright (C) 2010 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 com.android.email.provider; |
| |
| import com.android.email.provider.ContentCache.CacheToken; |
| import com.android.email.provider.ContentCache.CachedCursor; |
| import com.android.email.provider.ContentCache.TokenList; |
| import com.android.emailcommon.provider.Account; |
| import com.android.emailcommon.provider.EmailContent; |
| import com.android.emailcommon.provider.Mailbox; |
| |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.Context; |
| import android.database.Cursor; |
| import android.database.CursorWrapper; |
| import android.database.MatrixCursor; |
| import android.net.Uri; |
| import android.test.ProviderTestCase2; |
| |
| /** |
| * Tests of ContentCache |
| * |
| * You can run this entire test case with: |
| * runtest -c com.android.email.provider.ContentCacheTests email |
| */ |
| public class ContentCacheTests extends ProviderTestCase2<EmailProvider> { |
| |
| EmailProvider mProvider; |
| Context mMockContext; |
| |
| public ContentCacheTests() { |
| super(EmailProvider.class, EmailContent.AUTHORITY); |
| } |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| mMockContext = getMockContext(); |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| super.tearDown(); |
| } |
| |
| public void testCounterMap() { |
| ContentCache.CounterMap<String> map = new ContentCache.CounterMap<String>(4); |
| // Make sure we can find added items |
| map.add("1"); |
| assertTrue(map.contains("1")); |
| map.add("2"); |
| map.add("2"); |
| // Make sure we can remove once for each add |
| map.subtract("2"); |
| assertTrue(map.contains("2")); |
| map.subtract("2"); |
| // Make sure that over-removing throws an exception |
| try { |
| map.subtract("2"); |
| fail("Removing a third time should throw an exception"); |
| } catch (IllegalStateException e) { |
| } |
| try { |
| map.subtract("3"); |
| fail("Removing object never added should throw an exception"); |
| } catch (IllegalStateException e) { |
| } |
| // There should only be one item in the map ("1") |
| assertEquals(1, map.size()); |
| assertTrue(map.contains("1")); |
| } |
| |
| public void testTokenList() { |
| TokenList list = new TokenList("Name"); |
| |
| // Add two tokens for "1" |
| CacheToken token1a = list.add("1"); |
| assertTrue(token1a.isValid()); |
| assertEquals("1", token1a.getId()); |
| assertEquals(1, list.size()); |
| CacheToken token1b = list.add("1"); |
| assertTrue(token1b.isValid()); |
| assertEquals("1", token1b.getId()); |
| assertTrue(token1a.equals(token1b)); |
| assertEquals(2, list.size()); |
| |
| // Add a token for "2" |
| CacheToken token2 = list.add("2"); |
| assertFalse(token1a.equals(token2)); |
| assertEquals(3, list.size()); |
| |
| // Invalidate "1"; there should be two tokens invalidated |
| assertEquals(2, list.invalidateTokens("1")); |
| assertFalse(token1a.isValid()); |
| assertFalse(token1b.isValid()); |
| // Token2 should still be valid |
| assertTrue(token2.isValid()); |
| // Only token2 should be in the list now (invalidation removes tokens) |
| assertEquals(1, list.size()); |
| assertEquals(token2, list.get(0)); |
| |
| // Add 3 tokens for "3" |
| CacheToken token3a = list.add("3"); |
| CacheToken token3b = list.add("3"); |
| CacheToken token3c = list.add("3"); |
| // Remove two of them |
| assertTrue(list.remove(token3a)); |
| assertTrue(list.remove(token3b)); |
| // Removing tokens doesn't invalidate them |
| assertTrue(token3a.isValid()); |
| assertTrue(token3b.isValid()); |
| assertTrue(token3c.isValid()); |
| // There should be two items left "3" and "2" |
| assertEquals(2, list.size()); |
| } |
| |
| public void testCachedCursors() { |
| final ContentResolver resolver = mMockContext.getContentResolver(); |
| final Context context = mMockContext; |
| |
| // Create account and two mailboxes |
| Account acct = ProviderTestUtils.setupAccount("account", true, context); |
| ProviderTestUtils.setupMailbox("box1", acct.mId, true, context); |
| Mailbox box = ProviderTestUtils.setupMailbox("box2", acct.mId, true, context); |
| |
| // We need to test with a query that only returns one row (others can't be put in a |
| // CachedCursor) |
| Uri uri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, box.mId); |
| Cursor cursor = |
| resolver.query(uri, Mailbox.CONTENT_PROJECTION, null, null, null); |
| // ContentResolver gives us back a wrapper |
| assertTrue(cursor instanceof CursorWrapper); |
| // The wrappedCursor should be a CachedCursor |
| Cursor wrappedCursor = ((CursorWrapper)cursor).getWrappedCursor(); |
| assertTrue(wrappedCursor instanceof CachedCursor); |
| CachedCursor cachedCursor = (CachedCursor)wrappedCursor; |
| // The cursor wrapped in cachedCursor is the underlying cursor |
| Cursor activeCursor = cachedCursor.getWrappedCursor(); |
| |
| // The cursor should be in active cursors |
| int activeCount = ContentCache.sActiveCursors.getCount(activeCursor); |
| assertEquals(1, activeCount); |
| |
| // Some basic functionality that shouldn't throw exceptions and should otherwise act as the |
| // underlying cursor would |
| String[] columnNames = cursor.getColumnNames(); |
| assertEquals(Mailbox.CONTENT_PROJECTION.length, columnNames.length); |
| for (int i = 0; i < Mailbox.CONTENT_PROJECTION.length; i++) { |
| assertEquals(Mailbox.CONTENT_PROJECTION[i], columnNames[i]); |
| } |
| |
| assertEquals(1, cursor.getCount()); |
| cursor.moveToNext(); |
| assertEquals(0, cursor.getPosition()); |
| cursor.moveToPosition(0); |
| assertEquals(0, cursor.getPosition()); |
| assertFalse(cursor.moveToPosition(1)); |
| |
| cursor.close(); |
| // We've closed the cached cursor; make sure |
| assertTrue(cachedCursor.isClosed()); |
| // The underlying cursor shouldn't be closed because it's in a cache (we'll test |
| // that in testContentCache) |
| assertFalse(activeCursor.isClosed()); |
| // Our cursor should no longer be in the active cursors map |
| assertFalse(ContentCache.sActiveCursors.contains(activeCursor)); |
| |
| // TODO - change the code or the test to enforce the assertion that a cached cursor |
| // should have only zero or one rows. We cannot test this in the constructor, however, |
| // due to potential for deadlock. |
| // // Make sure that we won't accept cursors with multiple rows |
| // cursor = resolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, null, null, null); |
| // try { |
| // cursor = new CachedCursor(cursor, null, "Foo"); |
| // fail("Mustn't accept cursor with more than one row"); |
| // } catch (IllegalArgumentException e) { |
| // // Correct |
| // } |
| } |
| |
| private static final String[] SIMPLE_PROJECTION = new String[] {"Foo"}; |
| private static final Object[] SIMPLE_ROW = new Object[] {"Bar"}; |
| private Cursor getOneRowCursor() { |
| MatrixCursor cursor = new MatrixCursor(SIMPLE_PROJECTION, 1); |
| cursor.addRow(SIMPLE_ROW); |
| return cursor; |
| } |
| |
| public void testContentCacheRemoveEldestEntry() { |
| // Create a cache of size 2 |
| ContentCache cache = new ContentCache("Name", SIMPLE_PROJECTION, 2); |
| // Random cursor; what's in it doesn't matter |
| Cursor cursor1 = getOneRowCursor(); |
| // Get a token for arbitrary object named "1" |
| CacheToken token = cache.getCacheToken("1"); |
| // Put the cursor in the cache |
| cache.putCursor(cursor1, "1", SIMPLE_PROJECTION, token); |
| assertEquals(1, cache.size()); |
| |
| // Add another random cursor; what's in it doesn't matter |
| Cursor cursor2 = getOneRowCursor(); |
| // Get a token for arbitrary object named "2" |
| token = cache.getCacheToken("2"); |
| // Put the cursor in the cache |
| cache.putCursor(cursor1, "2", SIMPLE_PROJECTION, token); |
| assertEquals(2, cache.size()); |
| |
| // We should be able to find both now in the cache |
| Cursor cachedCursor = cache.getCachedCursor("1", SIMPLE_PROJECTION); |
| assertNotNull(cachedCursor); |
| assertTrue(cachedCursor instanceof CachedCursor); |
| cachedCursor = cache.getCachedCursor("2", SIMPLE_PROJECTION); |
| assertNotNull(cachedCursor); |
| assertTrue(cachedCursor instanceof CachedCursor); |
| |
| // Both cursors should be open |
| assertFalse(cursor1.isClosed()); |
| assertFalse(cursor2.isClosed()); |
| |
| // Add another random cursor; what's in it doesn't matter |
| Cursor cursor3 = getOneRowCursor(); |
| // Get a token for arbitrary object named "3" |
| token = cache.getCacheToken("3"); |
| // Put the cursor in the cache |
| cache.putCursor(cursor1, "3", SIMPLE_PROJECTION, token); |
| // We should never have more than 2 entries in the cache |
| assertEquals(2, cache.size()); |
| |
| // The first cursor we added should no longer be in the cache (it's the eldest) |
| cachedCursor = cache.getCachedCursor("1", SIMPLE_PROJECTION); |
| assertNull(cachedCursor); |
| // The cursors for 2 and 3 should be cached |
| cachedCursor = cache.getCachedCursor("2", SIMPLE_PROJECTION); |
| assertNotNull(cachedCursor); |
| assertTrue(cachedCursor instanceof CachedCursor); |
| cachedCursor = cache.getCachedCursor("3", SIMPLE_PROJECTION); |
| assertNotNull(cachedCursor); |
| assertTrue(cachedCursor instanceof CachedCursor); |
| |
| // Even cursor1 should be open, since all cached cursors are in mActiveCursors until closed |
| assertFalse(cursor1.isClosed()); |
| assertFalse(cursor2.isClosed()); |
| assertFalse(cursor3.isClosed()); |
| } |
| |
| public void testCloseCachedCursor() { |
| // Create a cache of size 2 |
| ContentCache cache = new ContentCache("Name", SIMPLE_PROJECTION, 2); |
| // Random cursor; what's in it doesn't matter |
| Cursor underlyingCursor = getOneRowCursor(); |
| Cursor cachedCursor1 = new CachedCursor(underlyingCursor, cache, "1"); |
| Cursor cachedCursor2 = new CachedCursor(underlyingCursor, cache, "1"); |
| assertEquals(2, ContentCache.sActiveCursors.getCount(underlyingCursor)); |
| cachedCursor1.close(); |
| assertTrue(cachedCursor1.isClosed()); |
| // Underlying cursor should be open (still one cached cursor open) |
| assertFalse(underlyingCursor.isClosed()); |
| cachedCursor2.close(); |
| assertTrue(cachedCursor2.isClosed()); |
| assertEquals(0, ContentCache.sActiveCursors.getCount(underlyingCursor)); |
| // Underlying cursor should be closed (no cached cursors open) |
| assertTrue(underlyingCursor.isClosed()); |
| |
| underlyingCursor = getOneRowCursor(); |
| cachedCursor1 = cache.putCursor( |
| underlyingCursor, "2", SIMPLE_PROJECTION, cache.getCacheToken("2")); |
| cachedCursor2 = new CachedCursor(underlyingCursor, cache, "2"); |
| assertEquals(2, ContentCache.sActiveCursors.getCount(underlyingCursor)); |
| cachedCursor1.close(); |
| cachedCursor2.close(); |
| assertEquals(0, ContentCache.sActiveCursors.getCount(underlyingCursor)); |
| // Underlying cursor should still be open; it's in the cache |
| assertFalse(underlyingCursor.isClosed()); |
| // Cache a new cursor |
| cachedCursor2 = new CachedCursor(underlyingCursor, cache, "2"); |
| assertEquals(1, ContentCache.sActiveCursors.getCount(underlyingCursor)); |
| // Remove "2" from the cache and close the cursor |
| cache.invalidate(); |
| cachedCursor2.close(); |
| // The underlying cursor should now be closed (not in the cache and no cached cursors) |
| assertEquals(0, ContentCache.sActiveCursors.getCount(underlyingCursor)); |
| assertTrue(underlyingCursor.isClosed()); |
| } |
| } |