From 5864ce43488c928e5ec8905c3eb4c6a2b048bb7d Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 5 Aug 2022 13:08:21 +0100 Subject: [PATCH 1/3] adding rule to force a new session to be started for instrumentation tests --- .../java/im/vector/app/CantVerifyTest.kt | 5 ++- .../im/vector/app/ClearCurrentSessionRule.kt | 40 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt diff --git a/vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt b/vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt index ba844e56b7..e6b17c1e9e 100644 --- a/vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt +++ b/vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt @@ -27,6 +27,7 @@ import im.vector.app.features.MainActivity import im.vector.app.ui.robot.ElementRobot import org.junit.Rule import org.junit.Test +import org.junit.rules.RuleChain import org.junit.runner.RunWith import java.util.UUID @@ -35,7 +36,9 @@ import java.util.UUID class CantVerifyTest : VerificationTestBase() { @get:Rule - val activityRule = ActivityScenarioRule(MainActivity::class.java) + val testRule = RuleChain + .outerRule(ActivityScenarioRule(MainActivity::class.java)) + .around(ClearCurrentSessionRule()) private val elementRobot = ElementRobot() var userName: String = "loginTest_${UUID.randomUUID()}" diff --git a/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt b/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt new file mode 100644 index 0000000000..7ba0b63799 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app + +import androidx.datastore.preferences.preferencesDataStoreFile +import androidx.test.platform.app.InstrumentationRegistry +import kotlinx.coroutines.runBlocking +import org.junit.rules.TestWatcher +import org.junit.runner.Description +import org.junit.runners.model.Statement + +class ClearCurrentSessionRule : TestWatcher() { + override fun apply(base: Statement, description: Description): Statement { + val context = InstrumentationRegistry.getInstrumentation().targetContext + runBlocking { + runCatching { + val holder = (context.applicationContext as VectorApplication).activeSessionHolder + holder.getSafeActiveSession()?.signOutService()?.signOut(true) + context.preferencesDataStoreFile(name = "vector_analytics").delete() + (context.applicationContext as VectorApplication).vectorPreferences.clearPreferences() + holder.clearActiveSession() + } + } + return super.apply(base, description) + } +} From 2683e9209b3d3aaf737795ee4818b9e76443b2cc Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 5 Aug 2022 14:24:53 +0100 Subject: [PATCH 2/3] resetting the analytics datastore via reflection --- vector/build.gradle | 1 + .../im/vector/app/ClearCurrentSessionRule.kt | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index dc0a2da35d..0edaf5424e 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -598,4 +598,5 @@ dependencies { androidTestImplementation libs.mockk.mockkAndroid androidTestUtil libs.androidx.orchestrator debugImplementation libs.androidx.fragmentTesting + androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.10" } diff --git a/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt b/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt index 7ba0b63799..68db0a9509 100644 --- a/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt +++ b/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt @@ -16,21 +16,26 @@ package im.vector.app -import androidx.datastore.preferences.preferencesDataStoreFile +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit import androidx.test.platform.app.InstrumentationRegistry +import im.vector.app.features.analytics.store.AnalyticsStore import kotlinx.coroutines.runBlocking import org.junit.rules.TestWatcher import org.junit.runner.Description import org.junit.runners.model.Statement +import kotlin.reflect.KClass class ClearCurrentSessionRule : TestWatcher() { override fun apply(base: Statement, description: Description): Statement { val context = InstrumentationRegistry.getInstrumentation().targetContext runBlocking { + reflectAnalyticDatastore(context).edit { it.clear() } runCatching { val holder = (context.applicationContext as VectorApplication).activeSessionHolder holder.getSafeActiveSession()?.signOutService()?.signOut(true) - context.preferencesDataStoreFile(name = "vector_analytics").delete() (context.applicationContext as VectorApplication).vectorPreferences.clearPreferences() holder.clearActiveSession() } @@ -38,3 +43,12 @@ class ClearCurrentSessionRule : TestWatcher() { return super.apply(base, description) } } + +private fun KClass<*>.asTopLevel() = Class.forName("${qualifiedName}Kt") + +@Suppress("UNCHECKED_CAST") +private fun reflectAnalyticDatastore(context: Context): DataStore { + val klass = AnalyticsStore::class.asTopLevel() + val method = klass.getMethod("access\$getDataStore", Context::class.java) + return method.invoke(klass, context) as DataStore +} From 20b3dbc6e6e239104fd5bf5931f4ee663155f776 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 5 Aug 2022 17:53:36 +0100 Subject: [PATCH 3/3] documenting the rule and reflection helper --- .../java/im/vector/app/ClearCurrentSessionRule.kt | 9 +++++++++ .../app/features/analytics/store/AnalyticsStore.kt | 3 +++ 2 files changed, 12 insertions(+) diff --git a/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt b/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt index 68db0a9509..735e96c1e0 100644 --- a/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt +++ b/vector/src/androidTest/java/im/vector/app/ClearCurrentSessionRule.kt @@ -28,6 +28,11 @@ import org.junit.runner.Description import org.junit.runners.model.Statement import kotlin.reflect.KClass +/** + * A TestRule to reset and clear the current Session. + * If a Session is active it will be signed out and cleared from the ActiveSessionHolder. + * The VectorPreferences and AnalyticsDatastore are also cleared in an attempt to recreate a fresh base. + */ class ClearCurrentSessionRule : TestWatcher() { override fun apply(base: Statement, description: Description): Statement { val context = InstrumentationRegistry.getInstrumentation().targetContext @@ -46,6 +51,10 @@ class ClearCurrentSessionRule : TestWatcher() { private fun KClass<*>.asTopLevel() = Class.forName("${qualifiedName}Kt") +/** + * Fetches the top level, private [Context.dataStore] extension property from [im.vector.app.features.analytics.store.AnalyticsStore] + * via reflection to avoid exposing property to all callers. + */ @Suppress("UNCHECKED_CAST") private fun reflectAnalyticDatastore(context: Context): DataStore { val klass = AnalyticsStore::class.asTopLevel() diff --git a/vector/src/main/java/im/vector/app/features/analytics/store/AnalyticsStore.kt b/vector/src/main/java/im/vector/app/features/analytics/store/AnalyticsStore.kt index 823d6285ed..fe1e8ad9ce 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/store/AnalyticsStore.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/store/AnalyticsStore.kt @@ -29,6 +29,9 @@ import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.extensions.orFalse import javax.inject.Inject +/** + * Also accessed via reflection by the instrumentation tests @see [im.vector.app.ClearCurrentSessionRule]. + */ private val Context.dataStore: DataStore by preferencesDataStore(name = "vector_analytics") /**