Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2018 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.database.sqlite.cts;
     18 
     19 import android.content.Context;
     20 import android.database.DatabaseUtils;
     21 import android.database.sqlite.SQLiteCompatibilityWalFlags;
     22 import android.database.sqlite.SQLiteDatabase;
     23 import android.test.AndroidTestCase;
     24 import android.util.Log;
     25 
     26 import com.android.compatibility.common.util.SystemUtil;
     27 
     28 import java.io.File;
     29 import java.io.FileInputStream;
     30 import java.io.FileNotFoundException;
     31 import java.io.FileOutputStream;
     32 import java.io.InputStream;
     33 import java.io.OutputStream;
     34 
     35 public class SQLiteWalTest extends AndroidTestCase {
     36     private static final String TAG = "SQLiteWalTest";
     37 
     38     private static final String DB_FILE = "SQLiteWalTest.db";
     39     private static final String SHM_SUFFIX = "-shm";
     40     private static final String WAL_SUFFIX = "-wal";
     41 
     42     private static final String BACKUP_SUFFIX = ".bak";
     43 
     44     private static final String SQLITE_COMPATIBILITY_WAL_FLAGS = "sqlite_compatibility_wal_flags";
     45     private static final String TRUNCATE_SIZE_KEY = "truncate_size";
     46 
     47     @Override
     48     protected void setUp() throws Exception {
     49         super.setUp();
     50     }
     51 
     52     @Override
     53     protected void tearDown() throws Exception {
     54         SystemUtil.runShellCommand("settings delete global " + SQLITE_COMPATIBILITY_WAL_FLAGS);
     55 
     56         super.tearDown();
     57     }
     58 
     59     private void setCompatibilityWalFlags(String value) {
     60         // settings put global sqlite_compatibility_wal_flags truncate_size=0
     61 
     62         SystemUtil.runShellCommand("settings put global " + SQLITE_COMPATIBILITY_WAL_FLAGS + " "
     63                 + value);
     64     }
     65 
     66     private void copyFile(String from, String to) throws Exception {
     67         (new File(to)).delete();
     68 
     69         try (InputStream in = new FileInputStream(from)) {
     70             try (OutputStream out = new FileOutputStream(to)) {
     71                 byte[] buf = new byte[1024 * 32];
     72                 int len;
     73                 while ((len = in.read(buf)) > 0) {
     74                     out.write(buf, 0, len);
     75                 }
     76             }
     77         }
     78     }
     79 
     80     private void backupFile(String from) throws Exception {
     81         copyFile(from, from + BACKUP_SUFFIX);
     82     }
     83 
     84     private void restoreFile(String from) throws Exception {
     85         copyFile(from + BACKUP_SUFFIX, from);
     86     }
     87 
     88     private SQLiteDatabase openDatabase() {
     89         SQLiteDatabase db = mContext.openOrCreateDatabase(
     90                 DB_FILE, Context.MODE_ENABLE_WRITE_AHEAD_LOGGING, null);
     91         db.execSQL("PRAGMA synchronous=FULL");
     92         return db;
     93     }
     94 
     95     private void assertTestTableExists(SQLiteDatabase db) {
     96         assertEquals(1, DatabaseUtils.longForQuery(db, "SELECT count(*) FROM test", null));
     97     }
     98 
     99     private SQLiteDatabase prepareDatabase() {
    100         SQLiteDatabase db = openDatabase();
    101 
    102         db.execSQL("CREATE TABLE test (column TEXT);");
    103         db.execSQL("INSERT INTO test (column) VALUES ("
    104                 + "'12345678901234567890123456789012345678901234567890')");
    105 
    106         // Make sure all the 3 files exist and are bigger than 0 bytes.
    107         assertTrue((new File(db.getPath())).exists());
    108         assertTrue((new File(db.getPath() + SHM_SUFFIX)).exists());
    109         assertTrue((new File(db.getPath() + WAL_SUFFIX)).exists());
    110 
    111         assertTrue((new File(db.getPath())).length() > 0);
    112         assertTrue((new File(db.getPath() + SHM_SUFFIX)).length() > 0);
    113         assertTrue((new File(db.getPath() + WAL_SUFFIX)).length() > 0);
    114 
    115         // Make sure the table has 1 row.
    116         assertTestTableExists(db);
    117 
    118         return db;
    119     }
    120 
    121     /**
    122      * Open a WAL database when the WAL file size is bigger than the threshold, and make sure
    123      * the file gets truncated.
    124      */
    125     public void testWalTruncate() throws Exception {
    126         mContext.deleteDatabase(DB_FILE);
    127 
    128         // Truncate the WAL file if it's bigger than 1 byte.
    129         setCompatibilityWalFlags(TRUNCATE_SIZE_KEY + "=1");
    130         SQLiteCompatibilityWalFlags.reset();
    131 
    132         SQLiteDatabase db = doOperation("testWalTruncate");
    133 
    134         // Make sure the WAL file is truncated into 0 bytes.
    135         assertEquals(0, (new File(db.getPath() + WAL_SUFFIX)).length());
    136     }
    137 
    138     /**
    139      * Open a WAL database when the WAL file size is smaller than the threshold, and make sure
    140      * the file does *not* get truncated.
    141      */
    142     public void testWalNoTruncate() throws Exception {
    143         mContext.deleteDatabase(DB_FILE);
    144 
    145         setCompatibilityWalFlags(TRUNCATE_SIZE_KEY + "=1000000");
    146         SQLiteCompatibilityWalFlags.reset();
    147 
    148         SQLiteDatabase db = doOperation("testWalNoTruncate");
    149 
    150         assertTrue((new File(db.getPath() + WAL_SUFFIX)).length() > 0);
    151     }
    152 
    153     /**
    154      * When "truncate size" is set to 0, we don't truncate the wal file.
    155      */
    156     public void testWalTruncateDisabled() throws Exception {
    157         mContext.deleteDatabase(DB_FILE);
    158 
    159         setCompatibilityWalFlags(TRUNCATE_SIZE_KEY + "=0");
    160         SQLiteCompatibilityWalFlags.reset();
    161 
    162         SQLiteDatabase db = doOperation("testWalTruncateDisabled");
    163 
    164         assertTrue((new File(db.getPath() + WAL_SUFFIX)).length() > 0);
    165     }
    166 
    167     private SQLiteDatabase doOperation(String message) throws Exception {
    168         listFiles(message + ": start");
    169 
    170         SQLiteDatabase db = prepareDatabase();
    171 
    172         listFiles(message + ": DB created and prepared");
    173 
    174         // db.close() will remove the wal file, so back the files up.
    175         backupFile(db.getPath());
    176         backupFile(db.getPath() + SHM_SUFFIX);
    177         backupFile(db.getPath() + WAL_SUFFIX);
    178 
    179         listFiles(message + ": backup created");
    180 
    181         // Close the DB, this will remove the WAL file.
    182         db.close();
    183 
    184         listFiles(message + ": DB closed");
    185 
    186         // Restore the files.
    187         restoreFile(db.getPath());
    188         restoreFile(db.getPath() + SHM_SUFFIX);
    189         restoreFile(db.getPath() + WAL_SUFFIX);
    190 
    191         listFiles(message + ": DB restored");
    192 
    193         // Open the DB again.
    194         db = openDatabase();
    195 
    196         listFiles(message + ": DB re-opened");
    197 
    198         // Make sure the table still exists.
    199         assertTestTableExists(db);
    200 
    201         return db;
    202     }
    203 
    204     private void listFiles(String message) {
    205         final File dir = mContext.getDatabasePath("a").getParentFile();
    206         Log.i(TAG, "Listing files: " + message + " (" + dir.getAbsolutePath() + ")");
    207 
    208         final File[] files = mContext.getDatabasePath("a").getParentFile().listFiles();
    209         if (files == null || files.length == 0) {
    210             Log.i(TAG, "  No files found");
    211             return;
    212         }
    213         for (File f : files) {
    214             Log.i(TAG, "  file: " + f.getName() + " " + f.length() + " bytes");
    215         }
    216     }
    217 }
    218