Tests for lookaside configuration

Test: android.database.sqlite.cts.SQLiteDatabaseTest
Test: android.database.sqlite.cts.SQLiteOpenHelperTest
Bug: 63998707
Change-Id: I94614dc3b4c5ff27110e8abaa3f7c7ed87550d33
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
index f6458c1..28cea5a 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
@@ -22,6 +22,7 @@
 import java.util.Locale;
 import java.util.concurrent.Semaphore;
 
+import android.app.ActivityManager;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -31,16 +32,17 @@
 import android.database.sqlite.SQLiteCursorDriver;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
-import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteDebug;
 import android.database.sqlite.SQLiteQuery;
 import android.database.sqlite.SQLiteStatement;
 import android.database.sqlite.SQLiteTransactionListener;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
 
 public class SQLiteDatabaseTest extends AndroidTestCase {
+    private static final String TAG = "SQLiteDatabaseTest";
     private SQLiteDatabase mDatabase;
     private File mDatabaseFile;
     private String mDatabaseFilePath;
@@ -82,19 +84,17 @@
 
     @Override
     protected void tearDown() throws Exception {
-        mDatabase.close();
-        mDatabaseFile.delete();
+        closeAndDeleteDatabase();
         super.tearDown();
     }
 
-    public void testOpenDatabase() {
-        CursorFactory factory = new CursorFactory() {
-            public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
-                    String editTable, SQLiteQuery query) {
-                return new MockSQLiteCursor(db, masterQuery, editTable, query);
-            }
-        };
+    private void closeAndDeleteDatabase() {
+        mDatabase.close();
+        SQLiteDatabase.deleteDatabase(mDatabaseFile);
+    }
 
+    public void testOpenDatabase() {
+        CursorFactory factory = MockSQLiteCursor::new;
         SQLiteDatabase db = SQLiteDatabase.openDatabase(mDatabaseFilePath,
                 factory, SQLiteDatabase.CREATE_IF_NECESSARY);
         assertNotNull(db);
@@ -1276,8 +1276,7 @@
     @LargeTest
     public void testReaderGetsOldVersionOfDataWhenWriterIsInXact() throws InterruptedException {
         // redo setup to create WAL enabled database
-        mDatabase.close();
-        new File(mDatabase.getPath()).delete();
+        closeAndDeleteDatabase();
         mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, null);
         boolean rslt = mDatabase.enableWriteAheadLogging();
         assertTrue(rslt);
@@ -1350,8 +1349,7 @@
     public void testExceptionsFromEnableWriteAheadLogging() {
         // attach a database
         // redo setup to create WAL enabled database
-        mDatabase.close();
-        new File(mDatabase.getPath()).delete();
+        closeAndDeleteDatabase();
         mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, null);
 
         // attach a database and call enableWriteAheadLogging - should not be allowed
@@ -1388,7 +1386,7 @@
     }
 
     public void testEnableThenDisableWriteAheadLoggingUsingOpenFlag() {
-        new File(mDatabase.getPath()).delete();
+        closeAndDeleteDatabase();
         mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile.getPath(), null,
                 SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
                 null);
@@ -1409,14 +1407,13 @@
 
     public void testEnableWriteAheadLoggingFromContextUsingModeFlag() {
         // Without the MODE_ENABLE_WRITE_AHEAD_LOGGING flag, database opens without WAL.
-        getContext().deleteDatabase(DATABASE_FILE_NAME);
+        closeAndDeleteDatabase();
         mDatabase = getContext().openOrCreateDatabase(DATABASE_FILE_NAME,
                 Context.MODE_PRIVATE, null);
         assertFalse(mDatabase.isWriteAheadLoggingEnabled());
-        mDatabase.close();
 
         // With the MODE_ENABLE_WRITE_AHEAD_LOGGING flag, database opens with WAL.
-        getContext().deleteDatabase(DATABASE_FILE_NAME);
+        closeAndDeleteDatabase();
         mDatabase = getContext().openOrCreateDatabase(DATABASE_FILE_NAME,
                 Context.MODE_PRIVATE | Context.MODE_ENABLE_WRITE_AHEAD_LOGGING, null);
         assertTrue(mDatabase.isWriteAheadLoggingEnabled());
@@ -1493,4 +1490,53 @@
         mDatabase.setForeignKeyConstraintsEnabled(true);
         assertEquals(1, DatabaseUtils.longForQuery(mDatabase, "PRAGMA foreign_keys", null));
     }
+
+    public void testOpenDatabaseLookasideConfig() {
+        // First check that lookaside is enabled (except low-RAM devices)
+        boolean expectDisabled = mContext.getSystemService(ActivityManager.class).isLowRamDevice();
+        verifyLookasideStats(expectDisabled);
+        // Reopen test db with lookaside disabled
+        mDatabase.close();
+        SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
+                .setLookasideConfig(0, 0).build();
+        mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile.getPath(), params);
+        verifyLookasideStats(true);
+        // Reopen test db with custom lookaside config
+        mDatabase.close();
+        params = new SQLiteDatabase.OpenParams.Builder().setLookasideConfig(10000, 10).build();
+        mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile.getPath(), params);
+        // Lookaside is always disabled on low-RAM devices
+        verifyLookasideStats(expectDisabled);
+    }
+
+    public void testOpenParamsSetLookasideConfigValidation() {
+        try {
+            new SQLiteDatabase.OpenParams.Builder().setLookasideConfig(-1, 0).build();
+            fail("Negative slot size should be rejected");
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            new SQLiteDatabase.OpenParams.Builder().setLookasideConfig(0, -10).build();
+            fail("Negative slot count should be rejected");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    private void verifyLookasideStats(boolean expectDisabled) {
+        boolean dbStatFound = false;
+        SQLiteDebug.PagerStats info = SQLiteDebug.getDatabaseInfo();
+        for (SQLiteDebug.DbStats dbStat : info.dbStats) {
+            if (dbStat.dbName.endsWith(mDatabaseFile.getName())) {
+                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 " + mDatabaseFile.getName(), dbStatFound);
+    }
 }
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
index 8a0ba21..2d64cf3 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
@@ -16,37 +16,42 @@
 
 package android.database.sqlite.cts;
 
+import android.app.ActivityManager;
 import android.content.Context;
-import android.database.Cursor;
 import android.database.sqlite.SQLiteCursor;
 import android.database.sqlite.SQLiteCursorDriver;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDebug;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQuery;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 /**
  * 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 = new SQLiteDatabase.CursorFactory() {
-        public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
-                String editTable, SQLiteQuery query) {
-            return new MockCursor(db, masterQuery, editTable, query);
-        }
-    };
+    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();
+        super.tearDown();
+    }
+
     public void testConstructor() {
         new MockOpenHelper(mContext, TEST_DATABASE_NAME, mFactory, TEST_VERSION);
 
@@ -94,6 +99,68 @@
         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);
+    }
+
     private MockOpenHelper getOpenHelper() {
         return new MockOpenHelper(mContext, TEST_DATABASE_NAME, mFactory, TEST_VERSION);
     }