package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.robolectric.shadows.ShadowSQLiteConnection.convertSQLWithLocalizedUnicodeCollator; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatatypeMismatchException; import android.database.sqlite.SQLiteStatement; import com.almworks.sqlite4java.SQLiteConnection; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; import org.robolectric.TestRunners; import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @RunWith(TestRunners.MultiApiSelfTest.class) @Config(minSdk = LOLLIPOP) public class ShadowSQLiteConnectionTest { private SQLiteDatabase database; private File databasePath; private long ptr; private SQLiteConnection conn; private ShadowSQLiteConnection.Connections CONNECTIONS; @Before public void setUp() throws Exception { database = createDatabase("database.db"); SQLiteStatement createStatement = database.compileStatement( "CREATE TABLE `routine` (`id` INTEGER PRIMARY KEY AUTOINCREMENT , `name` VARCHAR , `lastUsed` INTEGER DEFAULT 0 , UNIQUE (`name`)) ;"); createStatement.execute(); conn = getSQLiteConnection(database); } @After public void tearDown() throws Exception { database.close(); } @Test public void testSqlConversion() { assertThat(convertSQLWithLocalizedUnicodeCollator("select * from `routine`")) .isEqualTo("select * from `routine`"); assertThat(convertSQLWithLocalizedUnicodeCollator( "select * from `routine` order by name \n\r \f collate\f\n\tunicode" + "\n, id \n\n\t collate\n\t \n\flocalized")) .isEqualTo("select * from `routine` order by name COLLATE NOCASE\n" + ", id COLLATE NOCASE"); assertThat(convertSQLWithLocalizedUnicodeCollator("select * from `routine` order by name collate localized")) .isEqualTo("select * from `routine` order by name COLLATE NOCASE"); assertThat(convertSQLWithLocalizedUnicodeCollator("select * from `routine` order by name collate unicode")) .isEqualTo("select * from `routine` order by name COLLATE NOCASE"); } @Test public void testSQLWithLocalizedOrUnicodeCollatorShouldBeSortedAsNoCase() throws Exception { database.execSQL("insert into routine(name) values ('الصحافة اليدوية')"); database.execSQL("insert into routine(name) values ('Hand press 1')"); database.execSQL("insert into routine(name) values ('hand press 2')"); database.execSQL("insert into routine(name) values ('Hand press 3')"); List<String> expected = Arrays.asList("Hand press 1", "hand press 2", "Hand press 3", "الصحافة اليدوية" ); String sqlLocalized = "SELECT `name` FROM `routine` ORDER BY `name` collate localized"; String sqlUnicode = "SELECT `name` FROM `routine` ORDER BY `name` collate unicode"; assertThat(simpleQueryForList(database, sqlLocalized)).isEqualTo(expected); assertThat(simpleQueryForList(database, sqlUnicode)).isEqualTo(expected); } private List<String> simpleQueryForList(SQLiteDatabase db, String sql) { Cursor cursor = db.rawQuery(sql, new String[0]); List<String> result = new ArrayList<>(); while (cursor.moveToNext()) { result.add(cursor.getString(0)); } cursor.close(); return result; } @Test public void nativeOpen_addsConnectionToPool() { assertThat(conn).isNotNull(); assertThat(conn.isOpen()).as("open").isTrue(); } @Test public void nativeClose_closesConnection() { ShadowSQLiteConnection.nativeClose(ptr); assertThat(conn.isOpen()).as("open").isFalse(); } @Test public void reset_closesConnection() { ShadowSQLiteConnection.reset(); assertThat(conn.isOpen()).as("open").isFalse(); } @Test public void reset_clearsConnectionCache() { final Map<Long, SQLiteConnection> connectionsMap = ReflectionHelpers.getField(CONNECTIONS, "connectionsMap"); assertThat(connectionsMap).as("connections before").isNotEmpty(); ShadowSQLiteConnection.reset(); assertThat(connectionsMap).as("connections after").isEmpty(); } @Test public void reset_clearsStatementCache() { final Map<Long, SQLiteStatement> statementsMap = ReflectionHelpers.getField(CONNECTIONS, "statementsMap"); assertThat(statementsMap).as("statements before").isNotEmpty(); ShadowSQLiteConnection.reset(); assertThat(statementsMap).as("statements after").isEmpty(); } @Test public void error_resultsInSpecificExceptionWithCause() { try { database.execSQL("insert into routine(name) values ('Hand press 1')"); ContentValues values = new ContentValues(1); values.put("rowid", "foo"); database.update("routine", values, "name='Hand press 1'", null); fail(); } catch (SQLiteDatatypeMismatchException expected) { assertThat(expected).hasRootCauseInstanceOf(com.almworks.sqlite4java.SQLiteException.class); } } @Test public void interruption_doesNotConcurrentlyModifyDatabase() throws Exception { Thread.currentThread().interrupt(); try { database.execSQL("insert into routine(name) values ('الصحافة اليدوية')"); } finally { Thread.interrupted(); } ShadowSQLiteConnection.reset(); } @Test public void test_setUseInMemoryDatabase() throws Exception { assertThat(conn.isMemoryDatabase()).isFalse(); ShadowSQLiteConnection.setUseInMemoryDatabase(true); SQLiteDatabase inMemoryDb = createDatabase("in_memory.db"); SQLiteConnection inMemoryConn = getSQLiteConnection(inMemoryDb); assertThat(inMemoryConn.isMemoryDatabase()).isTrue(); inMemoryDb.close(); } private SQLiteDatabase createDatabase(String filename) { databasePath = RuntimeEnvironment.application.getDatabasePath(filename); databasePath.getParentFile().mkdirs(); return SQLiteDatabase.openOrCreateDatabase(databasePath.getPath(), null); } private SQLiteConnection getSQLiteConnection(SQLiteDatabase database) { ptr = ShadowSQLiteConnection.nativeOpen(databasePath.getPath(), 0, "test connection", false, false).longValue(); CONNECTIONS = ReflectionHelpers.getStaticField(ShadowSQLiteConnection.class, "CONNECTIONS"); return CONNECTIONS.getConnection(ptr); } }