diff options
Diffstat (limited to 'common/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt')
-rw-r--r-- | common/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt | 176 |
1 files changed, 0 insertions, 176 deletions
diff --git a/common/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt b/common/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt deleted file mode 100644 index 3d98cc38..00000000 --- a/common/testutils/devicetests/com/android/testutils/DeviceConfigRule.kt +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * 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 com.android.testutils - -import android.Manifest.permission.READ_DEVICE_CONFIG -import android.Manifest.permission.WRITE_DEVICE_CONFIG -import android.provider.DeviceConfig -import android.util.Log -import com.android.modules.utils.build.SdkLevel -import com.android.testutils.FunctionalUtils.ThrowingRunnable -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import java.util.concurrent.CompletableFuture -import java.util.concurrent.Executor -import java.util.concurrent.TimeUnit - -private val TAG = DeviceConfigRule::class.simpleName - -private const val TIMEOUT_MS = 20_000L - -/** - * A [TestRule] that helps set [DeviceConfig] for tests and clean up the test configuration - * automatically on teardown. - * - * The rule can also optionally retry tests when they fail following an external change of - * DeviceConfig before S; this typically happens because device config flags are synced while the - * test is running, and DisableConfigSyncTargetPreparer is only usable starting from S. - * - * @param retryCountBeforeSIfConfigChanged if > 0, when the test fails before S, check if - * the configs that were set through this rule were changed, and retry the test - * up to the specified number of times if yes. - */ -class DeviceConfigRule @JvmOverloads constructor( - val retryCountBeforeSIfConfigChanged: Int = 0 -) : TestRule { - // Maps (namespace, key) -> value - private val originalConfig = mutableMapOf<Pair<String, String>, String?>() - private val usedConfig = mutableMapOf<Pair<String, String>, String?>() - - /** - * Actions to be run after cleanup of the config, for the current test only. - */ - private val currentTestCleanupActions = mutableListOf<ThrowingRunnable>() - - override fun apply(base: Statement, description: Description): Statement { - return TestValidationUrlStatement(base, description) - } - - private inner class TestValidationUrlStatement( - private val base: Statement, - private val description: Description - ) : Statement() { - override fun evaluate() { - var retryCount = if (SdkLevel.isAtLeastS()) 1 else retryCountBeforeSIfConfigChanged + 1 - while (retryCount > 0) { - retryCount-- - tryTest { - base.evaluate() - // Can't use break/return out of a loop here because this is a tryTest lambda, - // so set retryCount to exit instead - retryCount = 0 - }.catch<Throwable> { e -> // junit AssertionFailedError does not extend Exception - if (retryCount == 0) throw e - usedConfig.forEach { (key, value) -> - val currentValue = runAsShell(READ_DEVICE_CONFIG) { - DeviceConfig.getProperty(key.first, key.second) - } - if (currentValue != value) { - Log.w(TAG, "Test failed with unexpected device config change, retrying") - return@catch - } - } - throw e - } cleanupStep { - runAsShell(WRITE_DEVICE_CONFIG) { - originalConfig.forEach { (key, value) -> - DeviceConfig.setProperty( - key.first, key.second, value, false /* makeDefault */) - } - } - } cleanupStep { - originalConfig.clear() - usedConfig.clear() - } cleanup { - // Fold all cleanup actions into cleanup steps of an empty tryTest, so they are - // all run even if exceptions are thrown, and exceptions are reported properly. - currentTestCleanupActions.fold(tryTest { }) { - tryBlock, action -> tryBlock.cleanupStep { action.run() } - }.cleanup { - currentTestCleanupActions.clear() - } - } - } - } - } - - /** - * Set a configuration key/value. After the test case ends, it will be restored to the value it - * had when this method was first called. - */ - fun setConfig(namespace: String, key: String, value: String?): String? { - Log.i(TAG, "Setting config \"$key\" to \"$value\"") - val readWritePermissions = arrayOf(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) - - val keyPair = Pair(namespace, key) - val existingValue = runAsShell(*readWritePermissions) { - DeviceConfig.getProperty(namespace, key) - } - if (!originalConfig.containsKey(keyPair)) { - originalConfig[keyPair] = existingValue - } - usedConfig[keyPair] = value - if (existingValue == value) { - // Already the correct value. There may be a race if a change is already in flight, - // but if multiple threads update the config there is no way to fix that anyway. - Log.i(TAG, "\"$key\" already had value \"$value\"") - return value - } - - val future = CompletableFuture<String>() - val listener = DeviceConfig.OnPropertiesChangedListener { - // The listener receives updates for any change to any key, so don't react to - // changes that do not affect the relevant key - if (!it.keyset.contains(key)) return@OnPropertiesChangedListener - // "null" means absent in DeviceConfig : there is no such thing as a present but - // null value, so the following works even if |value| is null. - if (it.getString(key, null) == value) { - future.complete(value) - } - } - - return tryTest { - runAsShell(*readWritePermissions) { - DeviceConfig.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_CONNECTIVITY, - inlineExecutor, - listener) - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_CONNECTIVITY, - key, - value, - false /* makeDefault */) - // Don't drop the permission until the config is applied, just in case - future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) - }.also { - Log.i(TAG, "Config \"$key\" successfully set to \"$value\"") - } - } cleanup { - DeviceConfig.removeOnPropertiesChangedListener(listener) - } - } - - private val inlineExecutor get() = Executor { r -> r.run() } - - /** - * Add an action to be run after config cleanup when the current test case ends. - */ - fun runAfterNextCleanup(action: ThrowingRunnable) { - currentTestCleanupActions.add(action) - } -} |