diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0123f46..0a89861 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3,7 +3,7 @@
package="net.zetetic" android:versionCode="1" android:versionName="1.0-SNAPSHOT">
-
+
diff --git a/assets/corrupt.db b/assets/corrupt.db
new file mode 100644
index 0000000..878444c
Binary files /dev/null and b/assets/corrupt.db differ
diff --git a/src/main/java/net/zetetic/tests/AttachDatabaseTest.java b/src/main/java/net/zetetic/tests/AttachDatabaseTest.java
index 1d259a3..a7fbfd0 100644
--- a/src/main/java/net/zetetic/tests/AttachDatabaseTest.java
+++ b/src/main/java/net/zetetic/tests/AttachDatabaseTest.java
@@ -1,11 +1,10 @@
package net.zetetic.tests;
-
import net.sqlcipher.database.SQLiteDatabase;
import net.zetetic.QueryHelper;
+
import net.zetetic.ZeteticApplication;
-import javax.management.Query;
import java.io.File;
public class AttachDatabaseTest extends SQLCipherTest {
diff --git a/src/main/java/net/zetetic/tests/ClosedDatabaseTest.java b/src/main/java/net/zetetic/tests/ClosedDatabaseTest.java
new file mode 100644
index 0000000..c955fb3
--- /dev/null
+++ b/src/main/java/net/zetetic/tests/ClosedDatabaseTest.java
@@ -0,0 +1,335 @@
+package net.zetetic.tests;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.util.Locale;
+
+import net.sqlcipher.database.SQLiteDatabase;
+import net.sqlcipher.database.SQLiteException;
+import net.zetetic.ZeteticApplication;
+
+public class ClosedDatabaseTest extends SQLCipherTest {
+
+ @Override
+ public TestResult run() {
+
+ TestResult result = new TestResult(getName(), false);
+ try {
+ result.setResult(execute(null));
+ SQLiteDatabase.releaseMemory();
+ } catch (Exception e) {
+ Log.v(ZeteticApplication.TAG, e.toString());
+ }
+ return result;
+ }
+
+ @Override
+ public boolean execute(SQLiteDatabase null_database_ignored) {
+
+ File testDatabasePath = ZeteticApplication.getInstance().getDatabasePath("closed-db-test.db");
+
+ boolean status = false;
+
+ try {
+ SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(testDatabasePath, "", null);
+
+ database.close();
+
+ status = execute_closed_database_tests(database);
+ } catch (Exception e) {
+ // Uncaught [unexpected] exception:
+ Log.e(ZeteticApplication.TAG, "Unexpected exception", e);
+ return false;
+ }
+ finally {
+ testDatabasePath.delete();
+ }
+
+ return status;
+ }
+
+ boolean execute_closed_database_tests(SQLiteDatabase database) {
+ try {
+ /* operations that check if db is closed (and throw IllegalStateException): */
+ try {
+ // should throw IllegalStateException:
+ database.beginTransaction();
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.beginTransaction() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.beginTransaction() did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.endTransaction();
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.endTransaction() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.endTransaction() did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.setTransactionSuccessful();
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setTransactionSuccessful() did NOT throw throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setTransactionSuccessful() did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.getVersion();
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.getVersion() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.getVersion() did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.setVersion(111);
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setVersion() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setVersion() did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.getMaximumSize();
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.getMaximumSize() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.getMaximumSize() did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.setMaximumSize(111);
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setMaximumSize() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setMaximumSize() did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.getPageSize();
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.getPageSize() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.getPageSize() did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.setPageSize(111);
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setPageSize() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setPageSize() did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.compileStatement("SELECT 1;");
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.compileStatement() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.compileStatement() did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.query("t1", new String[]{"a", "b"}, null, null, null, null, null);
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.query() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.query() did throw exception on closed database OK", e);
+ }
+
+ // TODO: cover more query functions
+
+ try {
+ // should throw IllegalStateException:
+ database.execSQL("SELECT 1;");
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.execSQL(String) did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.execSQL(String) did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.execSQL("SELECT 1;", new Object[1]);
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.execSQL(String, Object[]) did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.execSQL(String, Object[]) did throw exception on closed database OK", e);
+ }
+
+ try {
+ // should throw IllegalStateException:
+ database.rawExecSQL("SELECT 1;");
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.rawExecSQL() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.rawExecSQL() did throw exception on closed database OK", e);
+ }
+
+ /* operations that do not explicitly check if db is closed
+ * ([should] throw SQLiteException on a closed database): */
+
+ try {
+ // should throw IllegalStateException:
+ database.setLocale(Locale.getDefault());
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setLocale() did NOT throw exception on closed database");
+ return false;
+ } catch (SQLiteException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setLocale() did throw exception on closed database OK", e);
+ }
+
+ try {
+ // [should] throw an exception on a closed database:
+ database.changePassword("new-password");
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.changePassword(String) did NOT throw exception on closed database");
+ return false;
+ } catch (SQLiteException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.changePassword(String) did throw exception on closed database OK", e);
+ }
+
+ try {
+ // [should] throw an exception on a closed database:
+ database.changePassword("new-password".toCharArray());
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.changePassword(char []) did NOT throw exception on closed database");
+ return false;
+ } catch (SQLiteException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.changePassword(char []) did throw exception on closed database OK", e);
+ }
+
+ try {
+ // [should] throw an exception on a closed database:
+ database.markTableSyncable("aa", "bb");
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.markTableSyncable(String, String) did NOT throw exception on closed database");
+ return false;
+ } catch (SQLiteException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.markTableSyncable(String, String) did throw exception on closed database OK", e);
+ }
+
+ // TBD SQLiteDatabase.markTableSyncable(String, String, String) does NOT throw exception on closed database:
+ try {
+ // NOTE: does not (yet) throw an exception on a closed database:
+ database.markTableSyncable("aa", "bb", "cc");
+
+ // ...
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.markTableSyncable(String, String, String) did NOT throw exception on closed database");
+ return false;
+ } catch (SQLiteException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.markTableSyncable(String, String, String) did throw exception on closed database OK", e);
+
+ // SIGNAL that this test must be updated:
+ //Log.e(ZeteticApplication.TAG, "BEHAVIOR CHANGED - please update the test");
+ //return false;
+ }
+
+ try {
+ // should throw IllegalStateException [since it calls getVersion()]:
+ database.needUpgrade(111);
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.needUpgrade() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.needUpgrade() did throw exception on closed database OK", e);
+ }
+
+ /* operations that are NOT expected to throw an exception if the database is closed ([should] not crash) */
+
+
+ /* XXX TODO: these functions should check the db state,
+ * TBD either throw or simply return false if the db is closed */
+ database.yieldIfContended();
+ database.yieldIfContendedSafely();
+ database.yieldIfContendedSafely(100);
+
+ database.setLockingEnabled(false);
+ database.setLockingEnabled(true);
+
+ database.close();
+
+ database.isReadOnly();
+ database.isOpen();
+
+ database.isInCompiledSqlCache("SELECT 1;");
+ database.purgeFromCompiledSqlCache("SELECT 1;");
+ database.resetCompiledSqlCache();
+
+ database.getMaxSqlCacheSize();
+
+ try {
+ // should throw IllegalStateException:
+ database.setMaxSqlCacheSize(111);
+
+ // should not get here:
+ Log.e(ZeteticApplication.TAG, "SQLiteDatabase.setMaxSqlCacheSize() did NOT throw exception on closed database");
+ return false;
+ } catch (IllegalStateException e) {
+ Log.v(ZeteticApplication.TAG, "SQLiteDatabase.setMaxSqlCacheSize() did throw exception on closed database OK", e);
+ }
+
+ } catch (Exception e) {
+ // Uncaught [unexpected] exception:
+ Log.e(ZeteticApplication.TAG, "Unexpected exception", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "Closed Database Test";
+ }
+}
diff --git a/src/main/java/net/zetetic/tests/CorruptDatabaseTest.java b/src/main/java/net/zetetic/tests/CorruptDatabaseTest.java
new file mode 100644
index 0000000..4d70d90
--- /dev/null
+++ b/src/main/java/net/zetetic/tests/CorruptDatabaseTest.java
@@ -0,0 +1,62 @@
+package net.zetetic.tests;
+
+import android.util.Log;
+
+import android.database.Cursor;
+
+import net.sqlcipher.database.SQLiteDatabase;
+
+import net.zetetic.ZeteticApplication;
+
+import java.io.File;
+import java.io.IOException;
+
+public class CorruptDatabaseTest extends SQLCipherTest {
+
+ @Override
+ public TestResult run() {
+
+ TestResult result = new TestResult(getName(), false);
+ try {
+ result.setResult(execute(null));
+ SQLiteDatabase.releaseMemory();
+ } catch (Exception e) {
+ Log.v(ZeteticApplication.TAG, e.toString());
+ }
+ return result;
+ }
+
+ @Override
+ public boolean execute(SQLiteDatabase null_database_ignored) {
+
+ File unencryptedDatabase = ZeteticApplication.getInstance().getDatabasePath("corrupt.db");
+
+ try {
+ ZeteticApplication.getInstance().extractAssetToDatabaseDirectory("corrupt.db");
+
+ SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(unencryptedDatabase, "", null);
+
+ // NOTE: database not expected to be null, but check:
+ if (database == null) {
+ Log.e(TAG, "ERROR: got null database object");
+ return false;
+ }
+
+ database.close();
+
+ return true;
+ } catch (Exception ex) {
+ // Uncaught exception (not expected):
+ Log.e(TAG, "UNEXPECTED EXCEPTION", ex);
+ return false;
+ }
+ finally {
+ unencryptedDatabase.delete();
+ }
+ }
+
+ @Override
+ public String getName() {
+ return "Corrupt Database Test";
+ }
+}
diff --git a/src/main/java/net/zetetic/tests/MultiThreadReadWriteTest.java b/src/main/java/net/zetetic/tests/MultiThreadReadWriteTest.java
index 23f154b..a7e060d 100644
--- a/src/main/java/net/zetetic/tests/MultiThreadReadWriteTest.java
+++ b/src/main/java/net/zetetic/tests/MultiThreadReadWriteTest.java
@@ -1,8 +1,12 @@
package net.zetetic.tests;
import android.util.Log;
-import net.sqlcipher.Cursor;
+
+import android.database.Cursor;
+
import net.sqlcipher.database.SQLiteDatabase;
+import net.sqlcipher.database.SQLiteException;
+
import net.zetetic.ZeteticApplication;
import java.io.File;
@@ -59,13 +63,20 @@ public Writer(int id, int size, DatabaseAccessType accessType) {
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
Log.i(TAG, String.format("writer thread %d beginning", id));
+ // not expected to throw:
SQLiteDatabase writer = getDatabase(accessType);
- writer.execSQL("create table if not exists t1(a,b)");
- for (int index = 0; index < size; index++) {
- Log.i(TAG, String.format("writer thread %d - insert data for row:%d", id, index));
- writer.execSQL("insert into t1(a,b) values(?, ?)",
+
+ try {
+ writer.execSQL("create table if not exists t1(a,b)");
+
+ for (int index = 0; index < size; index++) {
+ Log.i(TAG, String.format("writer thread %d - insert data for row:%d", id, index));
+ writer.execSQL("insert into t1(a,b) values(?, ?)",
new Object[]{"one for the money", "two for the show"});
- }
+ }
+ } catch (SQLiteException ex) { Log.e(TAG, "caught exception, bailing", ex); }
+ catch (IllegalStateException ex) { Log.e(TAG, "caught exception, bailing", ex); }
+
closeDatabase(writer, accessType);
Log.i(TAG, String.format("writer thread %d terminating", id));
}
@@ -99,8 +110,13 @@ public void run() {
synchronized void logRecordsBetween(SQLiteDatabase reader, int start, int end) {
if(!reader.isOpen()) return;
- Cursor results = reader.rawQuery("select rowid, * from t1 where rowid between ? and ?",
+ Cursor results = null;
+ try {
+ results = reader.rawQuery("select rowid, * from t1 where rowid between ? and ?",
new String[]{String.valueOf(start), String.valueOf(end)});
+ } catch (IllegalStateException ex) { Log.e(TAG, "caught exception, bailing", ex); }
+ catch (SQLiteException ex) { Log.e(TAG, "caught exception, bailing", ex); }
+
if (results != null) {
Log.i(TAG, String.format("reader thread %d - writing results %d to %d", id, start, end));
while (results.moveToNext()) {
@@ -113,16 +129,26 @@ synchronized void logRecordsBetween(SQLiteDatabase reader, int start, int end) {
synchronized int getCurrentTableCount(SQLiteDatabase database) {
int count = 0;
+
+ if (!database.isOpen()) return -1;
+ Cursor cursor = null;
+
+ // I. Attempt database.rawQuery()-bail (explicitly return 0) if it throws:
try {
- if (!database.isOpen()) return -1;
- Cursor cursor = database.rawQuery("select count(*) from t1;", new String[]{});
- if (cursor != null) {
+ cursor = database.rawQuery("select count(*) from t1;", new String[]{});
+ } catch (IllegalStateException ex) { Log.e(TAG, "caught exception, bailing with count = " + count, ex); return 0; }
+ catch (SQLiteException ex) { Log.e(TAG, "caught exception, bailing", ex); return 0; }
+
+ // II. Attempt to get the count from the cursor:
+ if (cursor != null) {
+ try {
if (cursor.moveToFirst()) {
count = cursor.getInt(0);
}
cursor.close();
- }
- } catch (IllegalStateException ex){}
+ } catch (IllegalStateException ex) { Log.e(TAG, "caught exception, bailing with count = " + count, ex); }
+ }
+
return count;
}
}
@@ -186,4 +212,4 @@ private enum DatabaseAccessType {
public String getName() {
return "Multi-threaded read/write test";
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/net/zetetic/tests/TestSuiteRunner.java b/src/main/java/net/zetetic/tests/TestSuiteRunner.java
index f93b6a1..0bbb8d3 100644
--- a/src/main/java/net/zetetic/tests/TestSuiteRunner.java
+++ b/src/main/java/net/zetetic/tests/TestSuiteRunner.java
@@ -44,6 +44,7 @@ private void runSuite(){
private List getTestsToRun(){
List tests = new ArrayList();
+
tests.add(new AttachDatabaseTest());
tests.add(new CipherMigrateTest());
tests.add(new GetTypeFromCrossProcessCursorWrapperTest());
@@ -83,6 +84,9 @@ private List getTestsToRun(){
tests.add(new RawQueryTest());
tests.add(new OpenReadOnlyDatabaseTest());
tests.add(new RawRekeyTest());
+ tests.add(new ClosedDatabaseTest());
+ tests.add(new CorruptDatabaseTest());
+
return tests;
}
}