Adds FakeFeatureFlags for testing
No default values are used or provided from this class.
Reading a flag value which has not been previously `set`
will result in an IllegalStateException.
This is designed to prevent tests which unknowingly rely
on default flag values to succeed. By specifying flag
values up front, a test documents dependencies in code.
Test: atest FakeFeatureFlagsTest
Change-Id: Ie367d784a4005be4686875ce5e992ad92155379a
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt
new file mode 100644
index 0000000..b2a4e33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.systemui.flags
+
+import android.util.SparseArray
+import android.util.SparseBooleanArray
+import androidx.core.util.containsKey
+import java.lang.IllegalStateException
+
+class FakeFeatureFlags : FeatureFlags {
+ private val booleanFlags = SparseBooleanArray()
+ private val stringFlags = SparseArray<String>()
+ private val knownFlagNames = mutableMapOf<Int, String>()
+
+ init {
+ Flags.getFlagFields().forEach { field ->
+ val flag: Flag<*> = field.get(null) as Flag<*>
+ knownFlagNames[flag.id] = field.name
+ }
+ }
+
+ fun set(flag: BooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: DeviceConfigBooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: ResourceBooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: SysPropBooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: StringFlag, value: String) {
+ stringFlags.put(flag.id, value)
+ }
+
+ fun set(flag: ResourceStringFlag, value: String) {
+ stringFlags.put(flag.id, value)
+ }
+
+ override fun isEnabled(flag: BooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun isEnabled(flag: DeviceConfigBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun getString(flag: StringFlag): String = requireStringValue(flag.id)
+
+ override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id)
+
+ override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {}
+
+ override fun removeListener(listener: FlagListenable.Listener) {}
+
+ private fun flagName(flagId: Int): String {
+ return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
+ }
+
+ private fun requireBooleanValue(flagId: Int): Boolean {
+ if (!booleanFlags.containsKey(flagId)) {
+ throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
+ }
+ return booleanFlags[flagId]
+ }
+
+ private fun requireStringValue(flagId: Int): String {
+ if (!stringFlags.containsKey(flagId)) {
+ throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
+ }
+ return stringFlags[flagId]
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
new file mode 100644
index 0000000..7d4b4f5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.systemui.flags
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.lang.IllegalStateException
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FakeFeatureFlagsTest : SysuiTestCase() {
+
+ private val booleanFlag = BooleanFlag(-1000)
+ private val stringFlag = StringFlag(-1001)
+ private val resourceBooleanFlag = ResourceBooleanFlag(-1002, resourceId = -1)
+ private val resourceStringFlag = ResourceStringFlag(-1003, resourceId = -1)
+ private val sysPropBooleanFlag = SysPropBooleanFlag(-1004, name = "test")
+
+ /**
+ * FakeFeatureFlags does not honor any default values. All flags which are accessed must be
+ * specified. If not, an exception is thrown.
+ */
+ @Test
+ fun throwsIfUnspecifiedFlagIsAccessed() {
+ val flags: FeatureFlags = FakeFeatureFlags()
+ try {
+ assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("TEAMFOOD")
+ }
+ try {
+ assertThat(flags.isEnabled(booleanFlag)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1000)")
+ }
+ try {
+ assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1002)")
+ }
+ try {
+ assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1004)")
+ }
+ try {
+ assertThat(flags.getString(stringFlag)).isEmpty()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1001)")
+ }
+ try {
+ assertThat(flags.getString(resourceStringFlag)).isEmpty()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1003)")
+ }
+ }
+
+ @Test
+ fun specifiedFlagsReturnCorrectValues() {
+ val flags = FakeFeatureFlags()
+ flags.set(booleanFlag, false)
+ flags.set(resourceBooleanFlag, false)
+ flags.set(sysPropBooleanFlag, false)
+ flags.set(resourceStringFlag, "")
+
+ assertThat(flags.isEnabled(booleanFlag)).isFalse()
+ assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
+ assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
+ assertThat(flags.getString(resourceStringFlag)).isEmpty()
+
+ flags.set(booleanFlag, true)
+ flags.set(resourceBooleanFlag, true)
+ flags.set(sysPropBooleanFlag, true)
+ flags.set(resourceStringFlag, "Android")
+
+ assertThat(flags.isEnabled(booleanFlag)).isTrue()
+ assertThat(flags.isEnabled(resourceBooleanFlag)).isTrue()
+ assertThat(flags.isEnabled(sysPropBooleanFlag)).isTrue()
+ assertThat(flags.getString(resourceStringFlag)).isEqualTo("Android")
+ }
+}