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 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