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 setUp()48 protected void setUp() throws Exception { 49 super.setUp(); 50 } 51 52 @Override tearDown()53 protected void tearDown() throws Exception { 54 SystemUtil.runShellCommand("settings delete global " + SQLITE_COMPATIBILITY_WAL_FLAGS); 55 56 super.tearDown(); 57 } 58 setCompatibilityWalFlags(String value)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 copyFile(String from, String to)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 backupFile(String from)80 private void backupFile(String from) throws Exception { 81 copyFile(from, from + BACKUP_SUFFIX); 82 } 83 restoreFile(String from)84 private void restoreFile(String from) throws Exception { 85 copyFile(from + BACKUP_SUFFIX, from); 86 } 87 openDatabase()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 assertTestTableExists(SQLiteDatabase db)95 private void assertTestTableExists(SQLiteDatabase db) { 96 assertEquals(1, DatabaseUtils.longForQuery(db, "SELECT count(*) FROM test", null)); 97 } 98 prepareDatabase()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 */ testWalTruncate()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 */ testWalNoTruncate()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 */ testWalTruncateDisabled()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 doOperation(String message)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 listFiles(String message)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