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 android.content.cts; 18 19 import android.app.QueuedWork; 20 import android.content.Context; 21 import android.content.ContextWrapper; 22 import android.content.SharedPreferences; 23 import android.os.StrictMode; 24 import android.preference.PreferenceManager; 25 import android.test.AndroidTestCase; 26 import android.util.Log; 27 import java.io.File; 28 import java.io.FileInputStream; 29 import java.io.FileOutputStream; 30 import java.io.IOException; 31 import java.util.HashMap; 32 import java.util.Map; 33 import java.util.Random; 34 35 /** 36 * Test {@link SharedPreferences}. 37 */ 38 public class SharedPreferencesTest extends AndroidTestCase { 39 private static final String TAG = "SharedPreferencesTest"; 40 41 private Context mContext; 42 private ContextWrapper mContextWrapper; 43 44 private File mPrefsFile; 45 46 @Override 47 protected void setUp() throws Exception { 48 super.setUp(); 49 mContext = getContext(); 50 mContextWrapper = new ContextWrapper(mContext); 51 52 SharedPreferences prefs = getPrefs(); 53 prefs.edit().clear().commit(); 54 55 // Duplicated from ContextImpl.java. Not ideal, but there wasn't a better 56 // way to reach into Context{Wrapper,Impl} to ask where this file lives. 57 mPrefsFile = new File("/data/data/com.android.cts.content/shared_prefs", 58 "com.android.cts.content_preferences.xml"); 59 mPrefsFile.delete(); 60 } 61 62 private SharedPreferences getPrefs() { 63 return PreferenceManager.getDefaultSharedPreferences(mContext); 64 } 65 66 public void testNoFileInitially() { 67 assertFalse(mPrefsFile.exists()); 68 } 69 70 public void testCommitCreatesFiles() { 71 SharedPreferences prefs = getPrefs(); 72 assertFalse(mPrefsFile.exists()); 73 prefs.edit().putString("foo", "bar").commit(); 74 assertTrue(mPrefsFile.exists()); 75 } 76 77 public void testDefaults() { 78 SharedPreferences prefs = getPrefs(); 79 String key = "not-set"; 80 assertFalse(prefs.contains(key)); 81 assertEquals(0, prefs.getAll().size()); 82 assertTrue(prefs.getAll().isEmpty()); 83 assertEquals(false, prefs.getBoolean(key, false)); 84 assertEquals(true, prefs.getBoolean(key, true)); 85 assertEquals(0.5f, prefs.getFloat(key, 0.5f)); 86 assertEquals(123, prefs.getInt(key, 123)); 87 assertEquals(999L, prefs.getLong(key, 999L)); 88 assertEquals("default", prefs.getString(key, "default")); 89 } 90 91 public void testPutNullRemovesKey() { 92 SharedPreferences prefs = getPrefs(); 93 prefs.edit().putString("test-key", "test-value").commit(); 94 assertEquals("test-value", prefs.getString("test-key", null)); 95 96 SharedPreferences.Editor editor = prefs.edit().putString("test-key", null); 97 assertEquals("test-value", prefs.getString("test-key", null)); 98 editor.commit(); 99 100 assertNull(prefs.getString("test-key", null)); 101 assertFalse(prefs.contains("test-key")); 102 } 103 104 private abstract class RedundantWriteTest { 105 // Do some initial operation on editor. No commit needed. 106 public abstract void setUp(SharedPreferences.Editor editor); 107 108 // Do some later operation on editor (e.g. a redundant edit). 109 // No commit needed. 110 public abstract void subsequentEdit(SharedPreferences.Editor editor); 111 112 public boolean expectingMutation() { 113 return false; 114 } 115 116 // Tests that a redundant edit after an initital setup doesn't 117 // result in a duplicate write-out to disk. 118 public final void test() throws Exception { 119 SharedPreferences prefs = getPrefs(); 120 SharedPreferences.Editor editor; 121 122 assertFalse(mPrefsFile.exists()); 123 prefs.edit().commit(); 124 assertTrue(mPrefsFile.exists()); 125 126 editor = prefs.edit(); 127 setUp(editor); 128 editor.commit(); 129 long modtimeMillis1 = mPrefsFile.lastModified(); 130 131 // Wait a second and modify the preferences in a dummy, 132 // redundant way. Wish I could inject a clock or disk mock 133 // here, but can't. Instead relying on checking modtime of 134 // file on disk. 135 Thread.sleep(1000); // ms 136 137 editor = prefs.edit(); 138 subsequentEdit(editor); 139 editor.commit(); 140 141 long modtimeMillis2 = mPrefsFile.lastModified(); 142 assertEquals(expectingMutation(), modtimeMillis1 != modtimeMillis2); 143 } 144 }; 145 146 public void testRedundantBoolean() throws Exception { 147 new RedundantWriteTest() { 148 public void setUp(SharedPreferences.Editor editor) { 149 editor.putBoolean("foo", true); 150 } 151 public void subsequentEdit(SharedPreferences.Editor editor) { 152 editor.putBoolean("foo", true); 153 } 154 }.test(); 155 } 156 157 public void testRedundantString() throws Exception { 158 new RedundantWriteTest() { 159 public void setUp(SharedPreferences.Editor editor) { 160 editor.putString("foo", "bar"); 161 } 162 public void subsequentEdit(SharedPreferences.Editor editor) { 163 editor.putString("foo", "bar"); 164 } 165 }.test(); 166 } 167 168 public void testNonRedundantString() throws Exception { 169 new RedundantWriteTest() { 170 public void setUp(SharedPreferences.Editor editor) { 171 editor.putString("foo", "bar"); 172 } 173 public void subsequentEdit(SharedPreferences.Editor editor) { 174 editor.putString("foo", "baz"); 175 } 176 public boolean expectingMutation() { 177 return true; 178 } 179 }.test(); 180 } 181 182 public void testRedundantClear() throws Exception { 183 new RedundantWriteTest() { 184 public void setUp(SharedPreferences.Editor editor) { 185 editor.clear(); 186 } 187 public void subsequentEdit(SharedPreferences.Editor editor) { 188 editor.clear(); 189 } 190 }.test(); 191 } 192 193 public void testNonRedundantClear() throws Exception { 194 new RedundantWriteTest() { 195 public void setUp(SharedPreferences.Editor editor) { 196 editor.putString("foo", "bar"); 197 } 198 public void subsequentEdit(SharedPreferences.Editor editor) { 199 editor.clear(); 200 } 201 public boolean expectingMutation() { 202 return true; 203 } 204 }.test(); 205 } 206 207 public void testRedundantRemove() throws Exception { 208 new RedundantWriteTest() { 209 public void setUp(SharedPreferences.Editor editor) { 210 editor.putString("foo", "bar"); 211 } 212 public void subsequentEdit(SharedPreferences.Editor editor) { 213 editor.remove("not-exist-key"); 214 } 215 }.test(); 216 } 217 218 public void testRedundantCommitWritesFileIfNotAlreadyExisting() { 219 SharedPreferences prefs = getPrefs(); 220 assertFalse(mPrefsFile.exists()); 221 prefs.edit().putString("foo", "bar").commit(); 222 assertTrue(mPrefsFile.exists()); 223 224 // Delete the file out from under it. (not sure why this 225 // would happen in practice, but perhaps the app did it for 226 // some reason...) 227 mPrefsFile.delete(); 228 229 // And verify that a redundant edit (which would otherwise not 230 // write do disk), still does write to disk if the file isn't 231 // there. 232 prefs.edit().putString("foo", "bar").commit(); 233 assertTrue(mPrefsFile.exists()); 234 } 235 236 public void testTorture() { 237 Map<String, String> expectedMap = new HashMap<String, String>(); 238 Random rand = new Random(); 239 240 SharedPreferences prefs = mContext.getSharedPreferences("torture", Context.MODE_PRIVATE); 241 prefs.edit().clear().commit(); 242 243 for (int i = 0; i < 100; i++) { 244 prefs = mContext.getSharedPreferences("torture", Context.MODE_PRIVATE); 245 assertEquals(expectedMap, prefs.getAll()); 246 247 String key = new Integer(rand.nextInt(25)).toString(); 248 String value = new Integer(i).toString(); 249 SharedPreferences.Editor editor = prefs.edit(); 250 251 if (rand.nextInt(100) < 85) { 252 Log.d(TAG, "Setting " + key + "=" + value); 253 editor.putString(key, value); 254 expectedMap.put(key, value); 255 } else { 256 Log.d(TAG, "Removing " + key); 257 editor.remove(key); 258 expectedMap.remove(key); 259 } 260 261 // Use apply on most, but commit some too. 262 if (rand.nextInt(100) < 85) { 263 Log.d(TAG, "apply."); 264 editor.apply(); 265 } else { 266 Log.d(TAG, "commit."); 267 editor.commit(); 268 } 269 270 assertEquals(expectedMap, prefs.getAll()); 271 } 272 } 273 274 // Checks that an in-memory commit doesn't mutate a data structure 275 // still being used while writing out to disk. 276 public void testTorture2() { 277 Random rand = new Random(); 278 279 for (int fi = 0; fi < 100; fi++) { 280 String prefsName = "torture_" + fi; 281 SharedPreferences prefs = mContext.getSharedPreferences(prefsName, Context.MODE_PRIVATE); 282 prefs.edit().clear().commit(); 283 Map<String, String> expectedMap = new HashMap<String, String>(); 284 285 for (int applies = 0; applies < 3; applies++) { 286 SharedPreferences.Editor editor = prefs.edit(); 287 for (int n = 0; n < 1000; n++) { 288 String key = new Integer(rand.nextInt(25)).toString(); 289 String value = new Integer(n).toString(); 290 editor.putString(key, value); 291 expectedMap.put(key, value); 292 } 293 editor.apply(); 294 } 295 QueuedWork.waitToFinish(); 296 297 String clonePrefsName = prefsName + "_clone"; 298 File prefsFile = mContext.getSharedPrefsFile(prefsName); 299 File prefsFileClone = mContext.getSharedPrefsFile(clonePrefsName); 300 prefsFileClone.delete(); 301 try { 302 FileOutputStream fos = new FileOutputStream(prefsFileClone); 303 FileInputStream fis = new FileInputStream(prefsFile); 304 byte buf[] = new byte[1024]; 305 int n; 306 while ((n = fis.read(buf)) > 0) { 307 fos.write(buf, 0, n); 308 } 309 fis.close(); 310 fos.close(); 311 } catch (IOException e) { 312 } 313 314 SharedPreferences clonePrefs = mContext.getSharedPreferences( 315 clonePrefsName, Context.MODE_PRIVATE); 316 assertEquals(expectedMap, clonePrefs.getAll()); 317 318 prefsFile.delete(); 319 prefsFileClone.delete(); 320 } 321 } 322 323 public void testModeMultiProcess() { 324 // Pre-load it. 325 mContext.getSharedPreferences("multiprocessTest", 0); 326 327 final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); 328 try { 329 StrictMode.ThreadPolicy diskReadDeath = 330 new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyDeath().build(); 331 StrictMode.setThreadPolicy(diskReadDeath); 332 333 // This shouldn't hit disk. (it was already pre-loaded above) 334 mContext.getSharedPreferences("multiprocessTest", 0); 335 336 boolean didRead = false; 337 // This SHOULD hit disk. (multi-process flag is set) 338 try { 339 mContext.getSharedPreferences("multiprocessTest", Context.MODE_MULTI_PROCESS); 340 fail(); // we shouldn't get here. 341 } catch (StrictMode.StrictModeViolation e) { 342 didRead = true; 343 } 344 assertTrue(didRead); 345 } finally { 346 StrictMode.setThreadPolicy(oldPolicy); 347 } 348 } 349 } 350