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