|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 package org.mozilla.gecko.tests; |
|
6 |
|
7 import java.io.File; |
|
8 import java.util.ArrayList; |
|
9 import java.util.LinkedList; |
|
10 import java.util.concurrent.Callable; |
|
11 |
|
12 import org.mozilla.gecko.db.BrowserContract; |
|
13 import org.mozilla.gecko.db.BrowserProvider; |
|
14 |
|
15 import android.content.ContentProvider; |
|
16 import android.content.ContentProviderOperation; |
|
17 import android.content.ContentProviderResult; |
|
18 import android.content.ContentValues; |
|
19 import android.content.Context; |
|
20 import android.content.OperationApplicationException; |
|
21 import android.content.SharedPreferences; |
|
22 import android.content.pm.ApplicationInfo; |
|
23 import android.content.res.AssetManager; |
|
24 import android.content.res.Resources; |
|
25 import android.database.ContentObserver; |
|
26 import android.database.Cursor; |
|
27 import android.net.Uri; |
|
28 import android.os.Build; |
|
29 import android.test.IsolatedContext; |
|
30 import android.test.RenamingDelegatingContext; |
|
31 import android.test.mock.MockContentResolver; |
|
32 import android.test.mock.MockContext; |
|
33 |
|
34 /* |
|
35 * ContentProviderTest provides the infrastructure to run content provider |
|
36 * tests in an controlled/isolated environment, guaranteeing that your tests |
|
37 * will not affect or be affected by any UI-related code. This is basically |
|
38 * a heavily adapted port of Android's ProviderTestCase2 to work on Mozilla's |
|
39 * infrastructure. |
|
40 * |
|
41 * For some tests, we need to have access to UI parts, or at least launch |
|
42 * the activity so the assets with test data become available, which requires |
|
43 * that we derive this test from BaseTest and consequently pull in some more |
|
44 * UI code than we'd ideally want. Furthermore, we need to pass the |
|
45 * Activity and not the instrumentation Context for the UI part to find some |
|
46 * of its required resources. |
|
47 * Similarly, we need to pass the Activity instead of the Instrumentation |
|
48 * Context down to some of our users (still wrapped in the Delegating Provider) |
|
49 * because they will stop working correctly if we do not. A typical problem |
|
50 * is that databases used in the ContentProvider will be attempted to be |
|
51 * opened twice. |
|
52 */ |
|
53 abstract class ContentProviderTest extends BaseTest { |
|
54 protected ContentProvider mProvider; |
|
55 protected ChangeRecordingMockContentResolver mResolver; |
|
56 protected ArrayList<Runnable> mTests; |
|
57 protected String mDatabaseName; |
|
58 protected String mProviderAuthority; |
|
59 protected IsolatedContext mProviderContext; |
|
60 |
|
61 private class ContentProviderMockContext extends MockContext { |
|
62 @Override |
|
63 public Resources getResources() { |
|
64 // We will fail to find some resources if we don't point |
|
65 // at the original activity. |
|
66 return ((Context)getActivity()).getResources(); |
|
67 } |
|
68 |
|
69 @Override |
|
70 public String getPackageName() { |
|
71 return getInstrumentation().getContext().getPackageName(); |
|
72 } |
|
73 |
|
74 @Override |
|
75 public String getPackageResourcePath() { |
|
76 return getInstrumentation().getContext().getPackageResourcePath(); |
|
77 } |
|
78 |
|
79 @Override |
|
80 public File getDir(String name, int mode) { |
|
81 return getInstrumentation().getContext().getDir(this.getClass().getSimpleName() + "_" + name, mode); |
|
82 } |
|
83 |
|
84 @Override |
|
85 public Context getApplicationContext() { |
|
86 return this; |
|
87 } |
|
88 |
|
89 @Override |
|
90 public SharedPreferences getSharedPreferences(String name, int mode) { |
|
91 return getInstrumentation().getContext().getSharedPreferences(name, mode); |
|
92 } |
|
93 |
|
94 @Override |
|
95 public ApplicationInfo getApplicationInfo() { |
|
96 return getInstrumentation().getContext().getApplicationInfo(); |
|
97 } |
|
98 } |
|
99 |
|
100 protected class DelegatingTestContentProvider extends ContentProvider { |
|
101 ContentProvider mTargetProvider; |
|
102 |
|
103 public DelegatingTestContentProvider(ContentProvider targetProvider) { |
|
104 super(); |
|
105 mTargetProvider = targetProvider; |
|
106 } |
|
107 |
|
108 private Uri appendTestParam(Uri uri) { |
|
109 try { |
|
110 return appendUriParam(uri, BrowserContract.PARAM_IS_TEST, "1"); |
|
111 } catch (Exception e) {} |
|
112 |
|
113 return null; |
|
114 } |
|
115 |
|
116 @Override |
|
117 public boolean onCreate() { |
|
118 return mTargetProvider.onCreate(); |
|
119 } |
|
120 |
|
121 @Override |
|
122 public String getType(Uri uri) { |
|
123 return mTargetProvider.getType(uri); |
|
124 } |
|
125 |
|
126 @Override |
|
127 public int delete(Uri uri, String selection, String[] selectionArgs) { |
|
128 return mTargetProvider.delete(appendTestParam(uri), selection, selectionArgs); |
|
129 } |
|
130 |
|
131 @Override |
|
132 public Uri insert(Uri uri, ContentValues values) { |
|
133 return mTargetProvider.insert(appendTestParam(uri), values); |
|
134 } |
|
135 |
|
136 @Override |
|
137 public int update(Uri uri, ContentValues values, String selection, |
|
138 String[] selectionArgs) { |
|
139 return mTargetProvider.update(appendTestParam(uri), values, |
|
140 selection, selectionArgs); |
|
141 } |
|
142 |
|
143 @Override |
|
144 public Cursor query(Uri uri, String[] projection, String selection, |
|
145 String[] selectionArgs, String sortOrder) { |
|
146 return mTargetProvider.query(appendTestParam(uri), projection, selection, |
|
147 selectionArgs, sortOrder); |
|
148 } |
|
149 |
|
150 @Override |
|
151 public ContentProviderResult[] applyBatch (ArrayList<ContentProviderOperation> operations) |
|
152 throws OperationApplicationException { |
|
153 return mTargetProvider.applyBatch(operations); |
|
154 } |
|
155 |
|
156 @Override |
|
157 public int bulkInsert(Uri uri, ContentValues[] values) { |
|
158 return mTargetProvider.bulkInsert(appendTestParam(uri), values); |
|
159 } |
|
160 |
|
161 public ContentProvider getTargetProvider() { |
|
162 return mTargetProvider; |
|
163 } |
|
164 } |
|
165 |
|
166 /* |
|
167 * A MockContentResolver that records each URI that is supplied to |
|
168 * notifyChange. Warning: the list of changed URIs is not |
|
169 * synchronized. |
|
170 */ |
|
171 protected class ChangeRecordingMockContentResolver extends MockContentResolver { |
|
172 public final LinkedList<Uri> notifyChangeList = new LinkedList<Uri>(); |
|
173 |
|
174 @Override |
|
175 public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { |
|
176 notifyChangeList.addLast(uri); |
|
177 |
|
178 super.notifyChange(uri, observer, syncToNetwork); |
|
179 } |
|
180 } |
|
181 |
|
182 /** |
|
183 * Factory function that makes new ContentProvider instances. |
|
184 * <p> |
|
185 * We want a fresh provider each test, so this should be invoked in |
|
186 * <code>setUp</code> before each individual test. |
|
187 */ |
|
188 protected static Callable<ContentProvider> sBrowserProviderCallable = new Callable<ContentProvider>() { |
|
189 @Override |
|
190 public ContentProvider call() { |
|
191 return new BrowserProvider(); |
|
192 } |
|
193 }; |
|
194 |
|
195 private void setUpContentProvider(ContentProvider targetProvider) throws Exception { |
|
196 mResolver = new ChangeRecordingMockContentResolver(); |
|
197 |
|
198 final String filenamePrefix = this.getClass().getSimpleName() + "."; |
|
199 RenamingDelegatingContext targetContextWrapper = |
|
200 new RenamingDelegatingContext( |
|
201 new ContentProviderMockContext(), |
|
202 (Context)getActivity(), |
|
203 filenamePrefix); |
|
204 |
|
205 mProviderContext = new IsolatedContext(mResolver, targetContextWrapper); |
|
206 |
|
207 targetProvider.attachInfo(mProviderContext, null); |
|
208 |
|
209 mProvider = new DelegatingTestContentProvider(targetProvider); |
|
210 mProvider.attachInfo(mProviderContext, null); |
|
211 |
|
212 mResolver.addProvider(mProviderAuthority, mProvider); |
|
213 } |
|
214 |
|
215 public static Uri appendUriParam(Uri uri, String param, String value) { |
|
216 return uri.buildUpon().appendQueryParameter(param, value).build(); |
|
217 } |
|
218 |
|
219 public void setTestName(String testName) { |
|
220 mAsserter.setTestName(this.getClass().getName() + " - " + testName); |
|
221 } |
|
222 |
|
223 @Override |
|
224 public void setUp() throws Exception { |
|
225 throw new UnsupportedOperationException("You should call setUp(authority, databaseName) instead"); |
|
226 } |
|
227 |
|
228 public void setUp(Callable<ContentProvider> contentProviderFactory, String authority, String databaseName) throws Exception { |
|
229 super.setUp(); |
|
230 |
|
231 mTests = new ArrayList<Runnable>(); |
|
232 mDatabaseName = databaseName; |
|
233 |
|
234 mProviderAuthority = authority; |
|
235 |
|
236 setUpContentProvider(contentProviderFactory.call()); |
|
237 } |
|
238 |
|
239 @Override |
|
240 public void tearDown() throws Exception { |
|
241 if (Build.VERSION.SDK_INT >= 11) { |
|
242 mProvider.shutdown(); |
|
243 } |
|
244 |
|
245 if (mDatabaseName != null) { |
|
246 mProviderContext.deleteDatabase(mDatabaseName); |
|
247 } |
|
248 |
|
249 super.tearDown(); |
|
250 } |
|
251 |
|
252 public AssetManager getAssetManager() { |
|
253 return getInstrumentation().getContext().getAssets(); |
|
254 } |
|
255 } |