• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ContentCacheTests()46     public ContentCacheTests() {
47         super(EmailProvider.class, EmailContent.AUTHORITY);
48     }
49 
50     @Override
setUp()51     public void setUp() throws Exception {
52         super.setUp();
53         mMockContext = getMockContext();
54     }
55 
56     @Override
tearDown()57     public void tearDown() throws Exception {
58         super.tearDown();
59     }
60 
testCounterMap()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 
testTokenList()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 
testCachedCursors()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"};
getOneRowCursor()198     private Cursor getOneRowCursor() {
199         MatrixCursor cursor = new MatrixCursor(SIMPLE_PROJECTION, 1);
200         cursor.addRow(SIMPLE_ROW);
201         return cursor;
202     }
203 
testContentCacheRemoveEldestEntry()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 
testCloseCachedCursor()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