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