blob: a620071ed9903d41a09c6d7f91c266754db3c180 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database.sqlite.cts;
import android.app.ActivityManager;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteCursor;
import android.database.sqlite.SQLiteCursorDriver;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteGlobal;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQuery;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.UiDevice;
import android.test.AndroidTestCase;
import android.util.Log;
import java.io.File;
import java.util.Arrays;
import static android.database.sqlite.cts.DatabaseTestUtils.getDbInfoOutput;
import static android.database.sqlite.cts.DatabaseTestUtils.waitForConnectionToClose;
/**
* Test {@link SQLiteOpenHelper}.
*/
public class SQLiteOpenHelperTest extends AndroidTestCase {
private static final String TAG = "SQLiteOpenHelperTest";
private static final String TEST_DATABASE_NAME = "database_test.db";
private static final int TEST_VERSION = 1;
private static final int TEST_ILLEGAL_VERSION = 0;
private MockOpenHelper mOpenHelper;
private SQLiteDatabase.CursorFactory mFactory = MockCursor::new;
@Override
protected void setUp() throws Exception {
super.setUp();
SQLiteDatabase.deleteDatabase(mContext.getDatabasePath(TEST_DATABASE_NAME));
mOpenHelper = getOpenHelper();
}
@Override
protected void tearDown() throws Exception {
mOpenHelper.close();
SQLiteDatabase.deleteDatabase(mContext.getDatabasePath(TEST_DATABASE_NAME));
super.tearDown();
}
public void testConstructor() {
new MockOpenHelper(mContext, TEST_DATABASE_NAME, mFactory, TEST_VERSION);
// Test with illegal version number.
try {
new MockOpenHelper(mContext, TEST_DATABASE_NAME, mFactory, TEST_ILLEGAL_VERSION);
fail("Constructor of SQLiteOpenHelp should throws a IllegalArgumentException here.");
} catch (IllegalArgumentException e) {
}
// Test with null factory
new MockOpenHelper(mContext, TEST_DATABASE_NAME, null, TEST_VERSION);
}
public void testGetDatabase() {
SQLiteDatabase database = null;
assertFalse(mOpenHelper.hasCalledOnOpen());
// Test getReadableDatabase.
database = mOpenHelper.getReadableDatabase();
assertNotNull(database);
assertTrue(database.isOpen());
assertTrue(mOpenHelper.hasCalledOnOpen());
// Database has been opened, so onOpen can not be invoked.
mOpenHelper.resetStatus();
assertFalse(mOpenHelper.hasCalledOnOpen());
// Test getWritableDatabase.
SQLiteDatabase database2 = mOpenHelper.getWritableDatabase();
assertSame(database, database2);
assertTrue(database.isOpen());
assertFalse(mOpenHelper.hasCalledOnOpen());
mOpenHelper.close();
assertFalse(database.isOpen());
// After close(), onOpen() will be invoked by getWritableDatabase.
mOpenHelper.resetStatus();
assertFalse(mOpenHelper.hasCalledOnOpen());
SQLiteDatabase database3 = mOpenHelper.getWritableDatabase();
assertNotNull(database);
assertNotSame(database, database3);
assertTrue(mOpenHelper.hasCalledOnOpen());
assertTrue(database3.isOpen());
mOpenHelper.close();
assertFalse(database3.isOpen());
}
public void testLookasideDefault() throws Exception {
assertNotNull(mOpenHelper.getWritableDatabase());
// Lookaside is always disabled on low-RAM devices
boolean expectDisabled = mContext.getSystemService(ActivityManager.class).isLowRamDevice();
verifyLookasideStats(mOpenHelper.getDatabaseName(), expectDisabled);
}
public void testLookasideDisabled() throws Exception {
mOpenHelper.setLookasideConfig(0, 0);
assertNotNull(mOpenHelper.getWritableDatabase());
verifyLookasideStats(mOpenHelper.getDatabaseName(), true);
}
public void testLookasideCustom() throws Exception {
mOpenHelper.setLookasideConfig(10000, 10);
assertNotNull(mOpenHelper.getWritableDatabase());
// Lookaside is always disabled on low-RAM devices
boolean expectDisabled = mContext.getSystemService(ActivityManager.class).isLowRamDevice();
verifyLookasideStats(mOpenHelper.getDatabaseName(), expectDisabled);
}
public void testSetLookasideConfigValidation() {
try {
mOpenHelper.setLookasideConfig(-1, 0);
fail("Negative slot size should be rejected");
} catch (IllegalArgumentException expected) {
}
try {
mOpenHelper.setLookasideConfig(0, -10);
fail("Negative slot count should be rejected");
} catch (IllegalArgumentException expected) {
}
try {
mOpenHelper.setLookasideConfig(1, 0);
fail("Illegal config should be rejected");
} catch (IllegalArgumentException expected) {
}
try {
mOpenHelper.setLookasideConfig(0, 1);
fail("Illegal config should be rejected");
} catch (IllegalArgumentException expected) {
}
}
private static void verifyLookasideStats(String dbName, boolean expectDisabled) {
boolean dbStatFound = false;
SQLiteDebug.PagerStats info = SQLiteDebug.getDatabaseInfo();
for (SQLiteDebug.DbStats dbStat : info.dbStats) {
if (dbStat.dbName.endsWith(dbName)) {
dbStatFound = true;
Log.i(TAG, "Lookaside for " + dbStat.dbName + " " + dbStat.lookaside);
if (expectDisabled) {
assertTrue("lookaside slots count should be zero", dbStat.lookaside == 0);
} else {
assertTrue("lookaside slots count should be greater than zero",
dbStat.lookaside > 0);
}
}
}
assertTrue("No dbstat found for " + dbName, dbStatFound);
}
public void testCloseIdleConnection() throws Exception {
mOpenHelper.setIdleConnectionTimeout(1000);
mOpenHelper.getReadableDatabase();
// Wait a bit and check that connection is still open
Thread.sleep(600);
String output = getDbInfoOutput();
assertTrue("Connection #0 should be open. Output: " + output,
output.contains("Connection #0:"));
// Now cause idle timeout and check that connection is closed
// We wait up to 5 seconds, which is longer than required 1 s to accommodate for delays in
// message processing when system is busy
boolean connectionWasClosed = waitForConnectionToClose(10, 500);
assertTrue("Connection #0 should be closed", connectionWasClosed);
}
public void testSetIdleConnectionTimeoutValidation() throws Exception {
try {
mOpenHelper.setIdleConnectionTimeout(-1);
fail("Negative timeout should be rejected");
} catch (IllegalArgumentException expected) {
}
}
public void testCloseIdleConnectionDefaultDisabled() throws Exception {
// Make sure system default timeout is not changed
assertEquals(30000, SQLiteGlobal.getIdleConnectionTimeout());
mOpenHelper.getReadableDatabase();
// Wait past the timeout and verify that connection is still open
Log.w(TAG, "Waiting for 35 seconds...");
Thread.sleep(35000);
String output = getDbInfoOutput();
assertTrue("Connection #0 should be open. Output: " + output,
output.contains("Connection #0:"));
}
public void testOpenParamsConstructor() {
SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
.build();
MockOpenHelper helper = new MockOpenHelper(mContext, null, 1, params);
SQLiteDatabase database = helper.getWritableDatabase();
assertNotNull(database);
helper.close();
}
/**
* Test for {@link SQLiteOpenHelper#setOpenParams(SQLiteDatabase.OpenParams)}.
* <p>Opens the database using the helper and verifies that params have been applied</p>
*/
public void testSetOpenParams() {
mOpenHelper.close();
SQLiteDatabase.OpenParams.Builder paramsBuilder = new SQLiteDatabase.OpenParams.Builder();
paramsBuilder.addOpenFlags(SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING);
MockOpenHelper helper = new MockOpenHelper(mContext, TEST_DATABASE_NAME, null, 1);
helper.setOpenParams(paramsBuilder.build());
assertTrue("database must be opened with ENABLE_WRITE_AHEAD_LOGGING flag",
helper.getWritableDatabase().isWriteAheadLoggingEnabled());
}
/**
* Verifies that {@link SQLiteOpenHelper#setOpenParams(SQLiteDatabase.OpenParams)} cannot be
* called after opening the database.
*/
public void testSetOpenParamsFailsIfDbIsOpen() {
mOpenHelper.getWritableDatabase();
try {
mOpenHelper.setOpenParams(new SQLiteDatabase.OpenParams.Builder().build());
fail("setOpenParams should fail if the database is open");
} catch (IllegalStateException e) {
// Expected
}
}
/**
* Tests a scenario in WAL mode with multiple connections, when a connection should see schema
* changes made from another connection.
*/
public void testWalSchemaChangeVisibilityOnUpgrade() {
File dbPath = mContext.getDatabasePath(TEST_DATABASE_NAME);
SQLiteDatabase.deleteDatabase(dbPath);
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
db.execSQL("CREATE TABLE test_table (_id INTEGER PRIMARY KEY AUTOINCREMENT)");
db.setVersion(1);
db.close();
mOpenHelper = new MockOpenHelper(mContext, TEST_DATABASE_NAME, null, 2) {
{
setWriteAheadLoggingEnabled(true);
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion == 1) {
db.execSQL("ALTER TABLE test_table ADD column2 INT DEFAULT 1234");
db.execSQL("CREATE TABLE test_table2 (_id INTEGER PRIMARY KEY AUTOINCREMENT)");
}
}
};
// Check if can see the new column
try (Cursor cursor = mOpenHelper.getReadableDatabase()
.rawQuery("select * from test_table", null)) {
assertEquals("Newly added column should be visible. Returned columns: " + Arrays
.toString(cursor.getColumnNames()), 2, cursor.getColumnCount());
}
// Check if can see the new table
try (Cursor cursor = mOpenHelper.getReadableDatabase()
.rawQuery("select * from test_table2", null)) {
assertEquals(1, cursor.getColumnCount());
}
}
public void testSetWriteAheadLoggingDisablesCompatibilityWal() {
// Verify that compatibility WAL is not enabled, if an application explicitly disables WAL
mOpenHelper.setWriteAheadLoggingEnabled(false);
String journalMode = DatabaseUtils
.stringForQuery(mOpenHelper.getWritableDatabase(), "PRAGMA journal_mode", null);
assertFalse("Default journal mode should not be WAL", "WAL".equalsIgnoreCase(journalMode));
}
private MockOpenHelper getOpenHelper() {
return new MockOpenHelper(mContext, TEST_DATABASE_NAME, mFactory, TEST_VERSION);
}
private static class MockOpenHelper extends SQLiteOpenHelper {
private boolean mHasCalledOnOpen = false;
MockOpenHelper(Context context, String name, CursorFactory factory, int version) {
super(context, name, factory, version);
}
MockOpenHelper(Context context, String name, int version,
SQLiteDatabase.OpenParams openParams) {
super(context, name, version, openParams);
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
@Override
public void onOpen(SQLiteDatabase db) {
mHasCalledOnOpen = true;
}
public boolean hasCalledOnOpen() {
return mHasCalledOnOpen;
}
public void resetStatus() {
mHasCalledOnOpen = false;
}
}
private static class MockCursor extends SQLiteCursor {
MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable,
SQLiteQuery query) {
super(db, driver, editTable, query);
}
}
}