| package org.robolectric.shadows; |
| |
| import static android.os.Build.VERSION_CODES.LOLLIPOP; |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| import static com.google.common.truth.TruthJUnit.assume; |
| import static org.junit.Assert.fail; |
| import static org.robolectric.annotation.SQLiteMode.Mode.LEGACY; |
| import static org.robolectric.shadows.ShadowLegacySQLiteConnection.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 androidx.test.core.app.ApplicationProvider; |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| import com.almworks.sqlite4java.SQLiteConnection; |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.robolectric.annotation.Config; |
| import org.robolectric.annotation.SQLiteMode; |
| import org.robolectric.shadows.util.SQLiteLibraryLoader; |
| import org.robolectric.util.ReflectionHelpers; |
| |
| @RunWith(AndroidJUnit4.class) |
| @Config(minSdk = LOLLIPOP) |
| @SQLiteMode(LEGACY) // This test relies on legacy SQLite behavior in Robolectric. |
| public class ShadowSQLiteConnectionTest { |
| private SQLiteDatabase database; |
| private File databasePath; |
| private long ptr; |
| private SQLiteConnection conn; |
| private ShadowLegacySQLiteConnection.Connections connections; |
| |
| @Before |
| public void setUp() throws Exception { |
| if (!SQLiteLibraryLoader.isOsSupported()) { |
| return; |
| } |
| 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(); |
| } |
| |
| @After |
| public void tearDown() { |
| if (!SQLiteLibraryLoader.isOsSupported()) { |
| return; |
| } |
| database.close(); |
| } |
| |
| @Test |
| public void testSqlConversion() { |
| assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue(); |
| 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() { |
| assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue(); |
| 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() { |
| assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue(); |
| assertThat(conn).isNotNull(); |
| assertWithMessage("open").that(conn.isOpen()).isTrue(); |
| } |
| |
| @Test |
| public void nativeClose_closesConnection() { |
| assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue(); |
| ShadowLegacySQLiteConnection.nativeClose(ptr); |
| assertWithMessage("open").that(conn.isOpen()).isFalse(); |
| } |
| |
| @Test |
| public void reset_closesConnection() { |
| assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue(); |
| ShadowLegacySQLiteConnection.reset(); |
| assertWithMessage("open").that(conn.isOpen()).isFalse(); |
| } |
| |
| @Test |
| public void reset_clearsConnectionCache() { |
| assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue(); |
| final Map<Long, SQLiteConnection> connectionsMap = |
| ReflectionHelpers.getField(connections, "connectionsMap"); |
| |
| assertWithMessage("connections before").that(connectionsMap).isNotEmpty(); |
| ShadowLegacySQLiteConnection.reset(); |
| |
| assertWithMessage("connections after").that(connectionsMap).isEmpty(); |
| } |
| |
| @Test |
| public void reset_clearsStatementCache() { |
| assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue(); |
| final Map<Long, SQLiteStatement> statementsMap = |
| ReflectionHelpers.getField(connections, "statementsMap"); |
| |
| assertWithMessage("statements before").that(statementsMap).isNotEmpty(); |
| ShadowLegacySQLiteConnection.reset(); |
| |
| assertWithMessage("statements after").that(statementsMap).isEmpty(); |
| } |
| |
| @Test |
| public void error_resultsInSpecificExceptionWithCause() { |
| assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue(); |
| 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) |
| .hasCauseThat() |
| .hasCauseThat() |
| .isInstanceOf(com.almworks.sqlite4java.SQLiteException.class); |
| } |
| } |
| |
| @Test |
| public void interruption_doesNotConcurrentlyModifyDatabase() { |
| assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue(); |
| Thread.currentThread().interrupt(); |
| try { |
| database.execSQL("insert into routine(name) values ('الصحافة اليدوية')"); |
| } finally { |
| Thread.interrupted(); |
| } |
| ShadowLegacySQLiteConnection.reset(); |
| } |
| |
| @Test |
| public void test_setUseInMemoryDatabase() { |
| assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue(); |
| assertThat(conn.isMemoryDatabase()).isFalse(); |
| ShadowSQLiteConnection.setUseInMemoryDatabase(true); |
| SQLiteDatabase inMemoryDb = createDatabase("in_memory.db"); |
| SQLiteConnection inMemoryConn = getSQLiteConnection(); |
| assertThat(inMemoryConn.isMemoryDatabase()).isTrue(); |
| inMemoryDb.close(); |
| } |
| |
| @Test |
| public void cancel_shouldCancelAllStatements() { |
| assume().that(SQLiteLibraryLoader.isOsSupported()).isTrue(); |
| SQLiteStatement statement1 = |
| database.compileStatement("insert into routine(name) values ('Hand press 1')"); |
| SQLiteStatement statement2 = |
| database.compileStatement("insert into routine(name) values ('Hand press 2')"); |
| ShadowLegacySQLiteConnection.nativeCancel(ptr); |
| // An attempt to execute a statement after a cancellation should be a no-op, unless the |
| // statement hasn't been cancelled, in which case it will throw a SQLiteInterruptedException. |
| statement1.execute(); |
| statement2.execute(); |
| } |
| |
| private SQLiteDatabase createDatabase(String filename) { |
| databasePath = ApplicationProvider.getApplicationContext().getDatabasePath(filename); |
| databasePath.getParentFile().mkdirs(); |
| return SQLiteDatabase.openOrCreateDatabase(databasePath.getPath(), null); |
| } |
| |
| private SQLiteConnection getSQLiteConnection() { |
| ptr = |
| ShadowLegacySQLiteConnection.nativeOpen( |
| databasePath.getPath(), 0, "test connection", false, false) |
| .longValue(); |
| connections = |
| ReflectionHelpers.getStaticField(ShadowLegacySQLiteConnection.class, "CONNECTIONS"); |
| return connections.getConnection(ptr); |
| } |
| } |