Home | History | Annotate | Download | only in cts
      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