1 /* 2 * Copyright (C) 2009 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 static android.database.sqlite.cts.DatabaseTestUtils.getDbInfoOutput; 20 import static android.database.sqlite.cts.DatabaseTestUtils.waitForConnectionToClose; 21 22 import android.app.ActivityManager; 23 import android.content.Context; 24 import android.database.Cursor; 25 import android.database.DatabaseUtils; 26 import android.database.sqlite.SQLiteCursor; 27 import android.database.sqlite.SQLiteCursorDriver; 28 import android.database.sqlite.SQLiteDatabase; 29 import android.database.sqlite.SQLiteDatabase.CursorFactory; 30 import android.database.sqlite.SQLiteDebug; 31 import android.database.sqlite.SQLiteGlobal; 32 import android.database.sqlite.SQLiteOpenHelper; 33 import android.database.sqlite.SQLiteQuery; 34 import android.test.AndroidTestCase; 35 import android.util.Log; 36 37 import java.io.File; 38 import java.util.Arrays; 39 40 /** 41 * Test {@link SQLiteOpenHelper}. 42 */ 43 public class SQLiteOpenHelperTest extends AndroidTestCase { 44 private static final String TAG = "SQLiteOpenHelperTest"; 45 private static final String TEST_DATABASE_NAME = "database_test.db"; 46 private static final int TEST_VERSION = 1; 47 private static final int TEST_ILLEGAL_VERSION = 0; 48 private MockOpenHelper mOpenHelper; 49 private SQLiteDatabase.CursorFactory mFactory = MockCursor::new; 50 51 @Override 52 protected void setUp() throws Exception { 53 super.setUp(); 54 SQLiteDatabase.deleteDatabase(mContext.getDatabasePath(TEST_DATABASE_NAME)); 55 mOpenHelper = getOpenHelper(); 56 } 57 58 @Override 59 protected void tearDown() throws Exception { 60 mOpenHelper.close(); 61 SQLiteDatabase.deleteDatabase(mContext.getDatabasePath(TEST_DATABASE_NAME)); 62 super.tearDown(); 63 } 64 65 public void testConstructor() { 66 new MockOpenHelper(mContext, TEST_DATABASE_NAME, mFactory, TEST_VERSION); 67 68 // Test with illegal version number. 69 try { 70 new MockOpenHelper(mContext, TEST_DATABASE_NAME, mFactory, TEST_ILLEGAL_VERSION); 71 fail("Constructor of SQLiteOpenHelp should throws a IllegalArgumentException here."); 72 } catch (IllegalArgumentException e) { 73 } 74 75 // Test with null factory 76 new MockOpenHelper(mContext, TEST_DATABASE_NAME, null, TEST_VERSION); 77 } 78 79 public void testGetDatabase() { 80 SQLiteDatabase database = null; 81 assertFalse(mOpenHelper.hasCalledOnOpen()); 82 // Test getReadableDatabase. 83 database = mOpenHelper.getReadableDatabase(); 84 assertNotNull(database); 85 assertTrue(database.isOpen()); 86 assertTrue(mOpenHelper.hasCalledOnOpen()); 87 88 // Database has been opened, so onOpen can not be invoked. 89 mOpenHelper.resetStatus(); 90 assertFalse(mOpenHelper.hasCalledOnOpen()); 91 // Test getWritableDatabase. 92 SQLiteDatabase database2 = mOpenHelper.getWritableDatabase(); 93 assertSame(database, database2); 94 assertTrue(database.isOpen()); 95 assertFalse(mOpenHelper.hasCalledOnOpen()); 96 97 mOpenHelper.close(); 98 assertFalse(database.isOpen()); 99 100 // After close(), onOpen() will be invoked by getWritableDatabase. 101 mOpenHelper.resetStatus(); 102 assertFalse(mOpenHelper.hasCalledOnOpen()); 103 SQLiteDatabase database3 = mOpenHelper.getWritableDatabase(); 104 assertNotNull(database); 105 assertNotSame(database, database3); 106 assertTrue(mOpenHelper.hasCalledOnOpen()); 107 assertTrue(database3.isOpen()); 108 mOpenHelper.close(); 109 assertFalse(database3.isOpen()); 110 } 111 112 public void testLookasideDefault() throws Exception { 113 assertNotNull(mOpenHelper.getWritableDatabase()); 114 // Lookaside is always disabled on low-RAM devices 115 boolean expectDisabled = mContext.getSystemService(ActivityManager.class).isLowRamDevice(); 116 verifyLookasideStats(mOpenHelper.getDatabaseName(), expectDisabled); 117 } 118 119 public void testLookasideDisabled() throws Exception { 120 mOpenHelper.setLookasideConfig(0, 0); 121 assertNotNull(mOpenHelper.getWritableDatabase()); 122 verifyLookasideStats(mOpenHelper.getDatabaseName(), true); 123 } 124 125 public void testLookasideCustom() throws Exception { 126 mOpenHelper.setLookasideConfig(10000, 10); 127 assertNotNull(mOpenHelper.getWritableDatabase()); 128 // Lookaside is always disabled on low-RAM devices 129 boolean expectDisabled = mContext.getSystemService(ActivityManager.class).isLowRamDevice(); 130 verifyLookasideStats(mOpenHelper.getDatabaseName(), expectDisabled); 131 } 132 133 public void testSetLookasideConfigValidation() { 134 try { 135 mOpenHelper.setLookasideConfig(-1, 0); 136 fail("Negative slot size should be rejected"); 137 } catch (IllegalArgumentException expected) { 138 } 139 try { 140 mOpenHelper.setLookasideConfig(0, -10); 141 fail("Negative slot count should be rejected"); 142 } catch (IllegalArgumentException expected) { 143 } 144 try { 145 mOpenHelper.setLookasideConfig(1, 0); 146 fail("Illegal config should be rejected"); 147 } catch (IllegalArgumentException expected) { 148 } 149 try { 150 mOpenHelper.setLookasideConfig(0, 1); 151 fail("Illegal config should be rejected"); 152 } catch (IllegalArgumentException expected) { 153 } 154 } 155 156 private static void verifyLookasideStats(String dbName, boolean expectDisabled) { 157 boolean dbStatFound = false; 158 SQLiteDebug.PagerStats info = SQLiteDebug.getDatabaseInfo(); 159 for (SQLiteDebug.DbStats dbStat : info.dbStats) { 160 if (dbStat.dbName.endsWith(dbName)) { 161 dbStatFound = true; 162 Log.i(TAG, "Lookaside for " + dbStat.dbName + " " + dbStat.lookaside); 163 if (expectDisabled) { 164 assertTrue("lookaside slots count should be zero", dbStat.lookaside == 0); 165 } else { 166 assertTrue("lookaside slots count should be greater than zero", 167 dbStat.lookaside > 0); 168 } 169 } 170 } 171 assertTrue("No dbstat found for " + dbName, dbStatFound); 172 } 173 174 public void testCloseIdleConnection() throws Exception { 175 mOpenHelper.setIdleConnectionTimeout(1000); 176 mOpenHelper.getReadableDatabase(); 177 // Wait a bit and check that connection is still open 178 Thread.sleep(600); 179 String output = getDbInfoOutput(); 180 assertTrue("Connection #0 should be open. Output: " + output, 181 output.contains("Connection #0:")); 182 183 // Now cause idle timeout and check that connection is closed 184 // We wait up to 5 seconds, which is longer than required 1 s to accommodate for delays in 185 // message processing when system is busy 186 boolean connectionWasClosed = waitForConnectionToClose(10, 500); 187 assertTrue("Connection #0 should be closed", connectionWasClosed); 188 } 189 190 public void testSetIdleConnectionTimeoutValidation() throws Exception { 191 try { 192 mOpenHelper.setIdleConnectionTimeout(-1); 193 fail("Negative timeout should be rejected"); 194 } catch (IllegalArgumentException expected) { 195 } 196 } 197 198 public void testCloseIdleConnectionDefaultDisabled() throws Exception { 199 // Make sure system default timeout is not changed 200 assertEquals(30000, SQLiteGlobal.getIdleConnectionTimeout()); 201 202 mOpenHelper.getReadableDatabase(); 203 // Wait past the timeout and verify that connection is still open 204 Log.w(TAG, "Waiting for 35 seconds..."); 205 Thread.sleep(35000); 206 String output = getDbInfoOutput(); 207 assertTrue("Connection #0 should be open. Output: " + output, 208 output.contains("Connection #0:")); 209 } 210 211 public void testOpenParamsConstructor() { 212 SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder() 213 .build(); 214 215 MockOpenHelper helper = new MockOpenHelper(mContext, null, 1, params); 216 SQLiteDatabase database = helper.getWritableDatabase(); 217 assertNotNull(database); 218 helper.close(); 219 } 220 221 /** 222 * Test for {@link SQLiteOpenHelper#setOpenParams(SQLiteDatabase.OpenParams)}. 223 * <p>Opens the database using the helper and verifies that params have been applied</p> 224 */ 225 public void testSetOpenParams() { 226 mOpenHelper.close(); 227 228 SQLiteDatabase.OpenParams.Builder paramsBuilder = new SQLiteDatabase.OpenParams.Builder(); 229 paramsBuilder.addOpenFlags(SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING); 230 231 MockOpenHelper helper = new MockOpenHelper(mContext, TEST_DATABASE_NAME, null, 1); 232 helper.setOpenParams(paramsBuilder.build()); 233 assertTrue("database must be opened with ENABLE_WRITE_AHEAD_LOGGING flag", 234 helper.getWritableDatabase().isWriteAheadLoggingEnabled()); 235 } 236 237 /** 238 * Verifies that {@link SQLiteOpenHelper#setOpenParams(SQLiteDatabase.OpenParams)} cannot be 239 * called after opening the database. 240 */ 241 public void testSetOpenParamsFailsIfDbIsOpen() { 242 mOpenHelper.getWritableDatabase(); 243 try { 244 mOpenHelper.setOpenParams(new SQLiteDatabase.OpenParams.Builder().build()); 245 fail("setOpenParams should fail if the database is open"); 246 } catch (IllegalStateException e) { 247 // Expected 248 } 249 } 250 251 /** 252 * Tests a scenario in WAL mode with multiple connections, when a connection should see schema 253 * changes made from another connection. 254 */ 255 public void testWalSchemaChangeVisibilityOnUpgrade() { 256 File dbPath = mContext.getDatabasePath(TEST_DATABASE_NAME); 257 SQLiteDatabase.deleteDatabase(dbPath); 258 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null); 259 db.execSQL("CREATE TABLE test_table (_id INTEGER PRIMARY KEY AUTOINCREMENT)"); 260 db.setVersion(1); 261 db.close(); 262 mOpenHelper = new MockOpenHelper(mContext, TEST_DATABASE_NAME, null, 2) { 263 { 264 setWriteAheadLoggingEnabled(true); 265 } 266 267 @Override 268 public void onCreate(SQLiteDatabase db) { 269 } 270 271 @Override 272 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 273 if (oldVersion == 1) { 274 db.execSQL("ALTER TABLE test_table ADD column2 INT DEFAULT 1234"); 275 db.execSQL("CREATE TABLE test_table2 (_id INTEGER PRIMARY KEY AUTOINCREMENT)"); 276 } 277 } 278 }; 279 // Check if can see the new column 280 try (Cursor cursor = mOpenHelper.getReadableDatabase() 281 .rawQuery("select * from test_table", null)) { 282 assertEquals("Newly added column should be visible. Returned columns: " + Arrays 283 .toString(cursor.getColumnNames()), 2, cursor.getColumnCount()); 284 } 285 // Check if can see the new table 286 try (Cursor cursor = mOpenHelper.getReadableDatabase() 287 .rawQuery("select * from test_table2", null)) { 288 assertEquals(1, cursor.getColumnCount()); 289 } 290 } 291 292 public void testSetWriteAheadLoggingDisablesCompatibilityWal() { 293 // Verify that compatibility WAL is not enabled, if an application explicitly disables WAL 294 295 mOpenHelper.setWriteAheadLoggingEnabled(false); 296 String journalMode = DatabaseUtils 297 .stringForQuery(mOpenHelper.getWritableDatabase(), "PRAGMA journal_mode", null); 298 assertFalse("Default journal mode should not be WAL", "WAL".equalsIgnoreCase(journalMode)); 299 } 300 301 private MockOpenHelper getOpenHelper() { 302 return new MockOpenHelper(mContext, TEST_DATABASE_NAME, mFactory, TEST_VERSION); 303 } 304 305 private static class MockOpenHelper extends SQLiteOpenHelper { 306 private boolean mHasCalledOnOpen = false; 307 308 MockOpenHelper(Context context, String name, CursorFactory factory, int version) { 309 super(context, name, factory, version); 310 } 311 312 MockOpenHelper(Context context, String name, int version, 313 SQLiteDatabase.OpenParams openParams) { 314 super(context, name, version, openParams); 315 } 316 317 @Override 318 public void onCreate(SQLiteDatabase db) { 319 } 320 321 @Override 322 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 323 } 324 325 @Override 326 public void onOpen(SQLiteDatabase db) { 327 mHasCalledOnOpen = true; 328 } 329 330 public boolean hasCalledOnOpen() { 331 return mHasCalledOnOpen; 332 } 333 334 public void resetStatus() { 335 mHasCalledOnOpen = false; 336 } 337 } 338 339 private static class MockCursor extends SQLiteCursor { 340 MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable, 341 SQLiteQuery query) { 342 super(db, driver, editTable, query); 343 } 344 } 345 } 346