Add foundation for automated performance tests.

fork-5.53.8
Greyson Parrelli 2021-03-05 13:54:57 -05:00 zatwierdzone przez GitHub
rodzic d8cc3c86b4
commit f92891895e
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
9 zmienionych plików z 226 dodań i 2 usunięć

Wyświetl plik

@ -202,6 +202,12 @@ android {
debuggable false
matchingFallbacks = ['debug']
}
mock {
initWith debug
isDefault false
minifyEnabled false
matchingFallbacks = ['debug']
}
}
productFlavors {
@ -227,6 +233,15 @@ android {
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
}
study {
dimension 'distribution'
applicationIdSuffix ".study"
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
}
prod {
dimension 'environment'
@ -266,6 +281,18 @@ android {
}
}
android.variantFilter { variant ->
def distribution = variant.getFlavors().get(0).name
def environment = variant.getFlavors().get(1).name
def buildType = variant.buildType.name
if (distribution == 'study' && buildType != 'perf' && buildType != 'mock') {
variant.setIgnore(true)
} else if (distribution != 'study' && buildType == 'mock') {
variant.setIgnore(true)
}
}
lintOptions {
abortOnError true
baseline file("lint-baseline.xml")

Wyświetl plik

@ -6,6 +6,7 @@ import android.content.Context;
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import org.signal.core.util.concurrent.SignalExecutors;
@ -126,6 +127,15 @@ public final class KeyValueStore implements KeyValueReader {
}
}
/**
* Forces the store to re-fetch all of it's data from the database.
* Should only be used for testing!
*/
@VisibleForTesting
synchronized void resetCache() {
dataSet = null;
initializeIfNecessary();
}
private synchronized void write(@NonNull KeyValueDataSet newDataSet, @NonNull Collection<String> removes) {
initializeIfNecessary();

Wyświetl plik

@ -1,9 +1,9 @@
package org.thoughtcrime.securesms.keyvalue;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceDataStore;
import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
@ -70,6 +70,15 @@ public final class SignalStore {
proxy().onFirstEverAppLaunch();
}
/**
* Forces the store to re-fetch all of it's data from the database.
* Should only be used for testing!
*/
@VisibleForTesting
public static void resetCache() {
INSTANCE.store.resetCache();
}
public static @NonNull KbsValues kbsValues() {
return INSTANCE.kbsValues;
}

Wyświetl plik

@ -5,6 +5,8 @@ import android.database.Cursor;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.recipients.RecipientId;
@ -13,13 +15,14 @@ import org.whispersystems.libsignal.util.guava.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class SqlUtil {
private SqlUtil() {}
private SqlUtil() {}
public static boolean tableExists(@NonNull SQLiteDatabase db, @NonNull String table) {
try (Cursor cursor = db.rawQuery("SELECT name FROM sqlite_master WHERE type=? AND name=?", new String[] { "table", table })) {
@ -27,6 +30,28 @@ public final class SqlUtil {
}
}
public static @NonNull List<String> getAllTables(@NonNull SQLiteDatabase db) {
List<String> tables = new LinkedList<>();
try (Cursor cursor = db.rawQuery("SELECT name FROM sqlite_master WHERE type=?", new String[] { "table" })) {
while (cursor.moveToNext()) {
tables.add(cursor.getString(0));
}
}
return tables;
}
/**
* Splits a multi-statement SQL block into independent statements. It is assumed that there is
* only one statement per line, and that each statement is terminated by a semi-colon.
*/
public static @NonNull List<String> splitStatements(@NonNull String sql) {
return Stream.of(Arrays.asList(sql.split(";\n")))
.map(String::trim)
.toList();
}
public static boolean isEmpty(@NonNull SQLiteDatabase db, @NonNull String table) {
try (Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM " + table, null)) {
if (cursor.moveToFirst()) {

Wyświetl plik

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms">
<application
android:name=".MockApplicationContext"
tools:replace="android:name" />
</manifest>

Wyświetl plik

@ -0,0 +1,101 @@
package org.thoughtcrime.securesms;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
import net.sqlcipher.database.SQLiteDatabase;
import org.signal.core.util.StreamUtil;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.KeyValueDatabase;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.SetUtil;
import org.thoughtcrime.securesms.util.SqlUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* Helper to initialize app state with the right database contents, shared prefs, etc.
*/
final class MockAppDataInitializer {
private static final Set<String> IGNORED_TABLES = SetUtil.newHashSet(
"sqlite_sequence",
"sms_fts",
"sms_fts_data",
"sms_fts_idx",
"sms_fts_docsize",
"sms_fts_config",
"mms_fts",
"mms_fts_data",
"mms_fts_idx",
"mms_fts_docsize",
"mms_fts_config"
);
public static void initialize(@NonNull Application application, @NonNull File sqlDirectory) throws IOException {
String localE164 = StreamUtil.readFullyAsString(new FileInputStream(new File(sqlDirectory, "e164.txt"))).trim();
String mainSql = StreamUtil.readFullyAsString(new FileInputStream(new File(sqlDirectory, "signal.sql")));
String keyValueSql = StreamUtil.readFullyAsString(new FileInputStream(new File(sqlDirectory, "signal-key-value.sql")));
initializeDatabase(DatabaseFactory.getInstance(application).getRawDatabase(), mainSql);
initializeDatabase(KeyValueDatabase.getInstance(application).getSqlCipherDatabase(), keyValueSql);
initializePreferences(application, localE164);
SignalStore.resetCache();
}
private static void initializeDatabase(@NonNull SQLiteDatabase db, @NonNull String sql) {
db.beginTransaction();
try {
clearAllTables(db);
execStatements(db, sql);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
private static void clearAllTables(@NonNull SQLiteDatabase db) {
List<String> tables = SqlUtil.getAllTables(db);
for (String table : tables) {
if (!IGNORED_TABLES.contains(table)) {
db.execSQL("DELETE FROM " + table);
}
}
}
private static void execStatements(@NonNull SQLiteDatabase db, @NonNull String sql) {
List<String> statements = SqlUtil.splitStatements(sql);
for (String statement : statements) {
db.execSQL(statement);
}
}
private static void initializePreferences(@NonNull Context context, @NonNull String localE164) {
MasterSecret masterSecret = MasterSecretUtil.generateMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
MasterSecretUtil.generateAsymmetricMasterSecret(context, masterSecret);
IdentityKeyUtil.generateIdentityKeys(context);
TextSecurePreferences.setPromptedPushRegistration(context, true);
TextSecurePreferences.setLocalNumber(context, localE164);
TextSecurePreferences.setLocalUuid(context, Recipient.external(context, localE164).requireUuid());
TextSecurePreferences.setPushRegistered(context, true);
}
}

Wyświetl plik

@ -0,0 +1,18 @@
package org.thoughtcrime.securesms;
import java.io.File;
import java.io.IOException;
public class MockApplicationContext extends ApplicationContext {
@Override
public void onCreate() {
super.onCreate();
try {
MockAppDataInitializer.initialize(this, new File(getExternalFilesDir(null), "mock-data"));
} catch (IOException e) {
throw new IllegalStateException("Failed to initialize mock data!", e);
}
}
}

Wyświetl plik

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/core_green"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Wyświetl plik

@ -10,6 +10,7 @@ import org.robolectric.annotation.Config;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@ -114,4 +115,22 @@ public final class SqlUtilTest {
public void buildCollectionQuery_none() {
SqlUtil.buildCollectionQuery("a", Collections.emptyList());
}
@Test
public void splitStatements_singleStatement() {
List<String> result = SqlUtil.splitStatements("SELECT * FROM foo;\n");
assertEquals(Arrays.asList("SELECT * FROM foo"), result);
}
@Test
public void splitStatements_twoStatements() {
List<String> result = SqlUtil.splitStatements("SELECT * FROM foo;\nSELECT * FROM bar;\n");
assertEquals(Arrays.asList("SELECT * FROM foo", "SELECT * FROM bar"), result);
}
@Test
public void splitStatements_twoStatementsSeparatedByNewLines() {
List<String> result = SqlUtil.splitStatements("SELECT * FROM foo;\n\nSELECT * FROM bar;\n");
assertEquals(Arrays.asList("SELECT * FROM foo", "SELECT * FROM bar"), result);
}
}