Loading packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt +123 −2 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() { * specified. If not, an exception is thrown. */ @Test fun throwsIfUnspecifiedFlagIsAccessed() { fun accessingUnspecifiedFlags_throwsException() { val flags: FeatureFlags = FakeFeatureFlags() try { assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse() Loading Loading @@ -88,7 +88,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() { } @Test fun specifiedFlagsReturnCorrectValues() { fun specifiedFlags_returnCorrectValues() { val flags = FakeFeatureFlags() flags.set(unreleasedFlag, false) flags.set(releasedFlag, false) Loading @@ -114,4 +114,125 @@ class FakeFeatureFlagsTest : SysuiTestCase() { assertThat(flags.isEnabled(sysPropBooleanFlag)).isTrue() assertThat(flags.getString(resourceStringFlag)).isEqualTo("Android") } @Test fun listenerForBooleanFlag_calledOnlyWhenFlagChanged() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(unreleasedFlag, listener) flags.set(unreleasedFlag, true) flags.set(unreleasedFlag, true) flags.set(unreleasedFlag, false) flags.set(unreleasedFlag, false) listener.verifyInOrder(unreleasedFlag.id, unreleasedFlag.id) } @Test fun listenerForStringFlag_calledOnlyWhenFlagChanged() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(stringFlag, listener) flags.set(stringFlag, "Test") flags.set(stringFlag, "Test") listener.verifyInOrder(stringFlag.id) } @Test fun listenerForBooleanFlag_notCalledAfterRemoved() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(unreleasedFlag, listener) flags.set(unreleasedFlag, true) flags.removeListener(listener) flags.set(unreleasedFlag, false) listener.verifyInOrder(unreleasedFlag.id) } @Test fun listenerForStringFlag_notCalledAfterRemoved() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(stringFlag, listener) flags.set(stringFlag, "Test") flags.removeListener(listener) flags.set(stringFlag, "Other") listener.verifyInOrder(stringFlag.id) } @Test fun listenerForMultipleFlags_calledWhenFlagsChange() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(unreleasedFlag, listener) flags.addListener(releasedFlag, listener) flags.set(releasedFlag, true) flags.set(unreleasedFlag, true) listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id) } @Test fun listenerForMultipleFlags_notCalledAfterRemoved() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(unreleasedFlag, listener) flags.addListener(releasedFlag, listener) flags.set(releasedFlag, true) flags.set(unreleasedFlag, true) flags.removeListener(listener) flags.set(releasedFlag, false) flags.set(unreleasedFlag, false) listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id) } @Test fun multipleListenersForSingleFlag_allAreCalledWhenChanged() { val flags = FakeFeatureFlags() val listener1 = VerifyingListener() val listener2 = VerifyingListener() flags.addListener(releasedFlag, listener1) flags.addListener(releasedFlag, listener2) flags.set(releasedFlag, true) listener1.verifyInOrder(releasedFlag.id) listener2.verifyInOrder(releasedFlag.id) } @Test fun multipleListenersForSingleFlag_removedListenerNotCalledAfterRemoval() { val flags = FakeFeatureFlags() val listener1 = VerifyingListener() val listener2 = VerifyingListener() flags.addListener(releasedFlag, listener1) flags.addListener(releasedFlag, listener2) flags.set(releasedFlag, true) flags.removeListener(listener2) flags.set(releasedFlag, false) listener1.verifyInOrder(releasedFlag.id, releasedFlag.id) listener2.verifyInOrder(releasedFlag.id) } class VerifyingListener : FlagListenable.Listener { var flagEventIds = mutableListOf<Int>() override fun onFlagChanged(event: FlagListenable.FlagEvent) { flagEventIds.add(event.flagId) } fun verifyInOrder(vararg eventIds: Int) { assertThat(flagEventIds).containsExactlyElementsIn(eventIds.asList()) } } } packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt +48 −20 Original line number Diff line number Diff line Loading @@ -16,14 +16,12 @@ package com.android.systemui.flags import android.util.SparseArray import android.util.SparseBooleanArray import androidx.core.util.containsKey class FakeFeatureFlags : FeatureFlags { private val booleanFlags = SparseBooleanArray() private val stringFlags = SparseArray<String>() private val booleanFlags = mutableMapOf<Int, Boolean>() private val stringFlags = mutableMapOf<Int, String>() private val knownFlagNames = mutableMapOf<Int, String>() private val flagListeners = mutableMapOf<Int, MutableSet<FlagListenable.Listener>>() private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>() init { Flags.getFlagFields().forEach { field -> Loading @@ -33,27 +31,52 @@ class FakeFeatureFlags : FeatureFlags { } fun set(flag: BooleanFlag, value: Boolean) { booleanFlags.put(flag.id, value) if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { notifyFlagChanged(flag) } } fun set(flag: DeviceConfigBooleanFlag, value: Boolean) { booleanFlags.put(flag.id, value) if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { notifyFlagChanged(flag) } } fun set(flag: ResourceBooleanFlag, value: Boolean) { booleanFlags.put(flag.id, value) if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { notifyFlagChanged(flag) } } fun set(flag: SysPropBooleanFlag, value: Boolean) { booleanFlags.put(flag.id, value) if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { notifyFlagChanged(flag) } } fun set(flag: StringFlag, value: String) { stringFlags.put(flag.id, value) if (stringFlags.put(flag.id, value)?.let { value != it } == null) { notifyFlagChanged(flag) } } fun set(flag: ResourceStringFlag, value: String) { stringFlags.put(flag.id, value) if (stringFlags.put(flag.id, value)?.let { value != it } == null) { notifyFlagChanged(flag) } } private fun notifyFlagChanged(flag: Flag<*>) { flagListeners[flag.id]?.let { listeners -> listeners.forEach { listener -> listener.onFlagChanged( object : FlagListenable.FlagEvent { override val flagId = flag.id override fun requestNoRestart() {} } ) } } } override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.id) Loading @@ -70,25 +93,30 @@ class FakeFeatureFlags : FeatureFlags { override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id) override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {} override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) { flagListeners.getOrPut(flag.id) { mutableSetOf() }.add(listener) listenerFlagIds.getOrPut(listener) { mutableSetOf() }.add(flag.id) } override fun removeListener(listener: FlagListenable.Listener) {} override fun removeListener(listener: FlagListenable.Listener) { listenerFlagIds.remove(listener)?.let { flagIds -> flagIds.forEach { id -> flagListeners[id]?.remove(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] ?: error("Flag ${flagName(flagId)} was accessed but not specified.") } private fun requireStringValue(flagId: Int): String { if (!stringFlags.containsKey(flagId)) { throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.") } return stringFlags[flagId] ?: error("Flag ${flagName(flagId)} was accessed but not specified.") } } Loading
packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt +123 −2 Original line number Diff line number Diff line Loading @@ -41,7 +41,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() { * specified. If not, an exception is thrown. */ @Test fun throwsIfUnspecifiedFlagIsAccessed() { fun accessingUnspecifiedFlags_throwsException() { val flags: FeatureFlags = FakeFeatureFlags() try { assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse() Loading Loading @@ -88,7 +88,7 @@ class FakeFeatureFlagsTest : SysuiTestCase() { } @Test fun specifiedFlagsReturnCorrectValues() { fun specifiedFlags_returnCorrectValues() { val flags = FakeFeatureFlags() flags.set(unreleasedFlag, false) flags.set(releasedFlag, false) Loading @@ -114,4 +114,125 @@ class FakeFeatureFlagsTest : SysuiTestCase() { assertThat(flags.isEnabled(sysPropBooleanFlag)).isTrue() assertThat(flags.getString(resourceStringFlag)).isEqualTo("Android") } @Test fun listenerForBooleanFlag_calledOnlyWhenFlagChanged() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(unreleasedFlag, listener) flags.set(unreleasedFlag, true) flags.set(unreleasedFlag, true) flags.set(unreleasedFlag, false) flags.set(unreleasedFlag, false) listener.verifyInOrder(unreleasedFlag.id, unreleasedFlag.id) } @Test fun listenerForStringFlag_calledOnlyWhenFlagChanged() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(stringFlag, listener) flags.set(stringFlag, "Test") flags.set(stringFlag, "Test") listener.verifyInOrder(stringFlag.id) } @Test fun listenerForBooleanFlag_notCalledAfterRemoved() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(unreleasedFlag, listener) flags.set(unreleasedFlag, true) flags.removeListener(listener) flags.set(unreleasedFlag, false) listener.verifyInOrder(unreleasedFlag.id) } @Test fun listenerForStringFlag_notCalledAfterRemoved() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(stringFlag, listener) flags.set(stringFlag, "Test") flags.removeListener(listener) flags.set(stringFlag, "Other") listener.verifyInOrder(stringFlag.id) } @Test fun listenerForMultipleFlags_calledWhenFlagsChange() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(unreleasedFlag, listener) flags.addListener(releasedFlag, listener) flags.set(releasedFlag, true) flags.set(unreleasedFlag, true) listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id) } @Test fun listenerForMultipleFlags_notCalledAfterRemoved() { val flags = FakeFeatureFlags() val listener = VerifyingListener() flags.addListener(unreleasedFlag, listener) flags.addListener(releasedFlag, listener) flags.set(releasedFlag, true) flags.set(unreleasedFlag, true) flags.removeListener(listener) flags.set(releasedFlag, false) flags.set(unreleasedFlag, false) listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id) } @Test fun multipleListenersForSingleFlag_allAreCalledWhenChanged() { val flags = FakeFeatureFlags() val listener1 = VerifyingListener() val listener2 = VerifyingListener() flags.addListener(releasedFlag, listener1) flags.addListener(releasedFlag, listener2) flags.set(releasedFlag, true) listener1.verifyInOrder(releasedFlag.id) listener2.verifyInOrder(releasedFlag.id) } @Test fun multipleListenersForSingleFlag_removedListenerNotCalledAfterRemoval() { val flags = FakeFeatureFlags() val listener1 = VerifyingListener() val listener2 = VerifyingListener() flags.addListener(releasedFlag, listener1) flags.addListener(releasedFlag, listener2) flags.set(releasedFlag, true) flags.removeListener(listener2) flags.set(releasedFlag, false) listener1.verifyInOrder(releasedFlag.id, releasedFlag.id) listener2.verifyInOrder(releasedFlag.id) } class VerifyingListener : FlagListenable.Listener { var flagEventIds = mutableListOf<Int>() override fun onFlagChanged(event: FlagListenable.FlagEvent) { flagEventIds.add(event.flagId) } fun verifyInOrder(vararg eventIds: Int) { assertThat(flagEventIds).containsExactlyElementsIn(eventIds.asList()) } } }
packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt +48 −20 Original line number Diff line number Diff line Loading @@ -16,14 +16,12 @@ package com.android.systemui.flags import android.util.SparseArray import android.util.SparseBooleanArray import androidx.core.util.containsKey class FakeFeatureFlags : FeatureFlags { private val booleanFlags = SparseBooleanArray() private val stringFlags = SparseArray<String>() private val booleanFlags = mutableMapOf<Int, Boolean>() private val stringFlags = mutableMapOf<Int, String>() private val knownFlagNames = mutableMapOf<Int, String>() private val flagListeners = mutableMapOf<Int, MutableSet<FlagListenable.Listener>>() private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>() init { Flags.getFlagFields().forEach { field -> Loading @@ -33,27 +31,52 @@ class FakeFeatureFlags : FeatureFlags { } fun set(flag: BooleanFlag, value: Boolean) { booleanFlags.put(flag.id, value) if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { notifyFlagChanged(flag) } } fun set(flag: DeviceConfigBooleanFlag, value: Boolean) { booleanFlags.put(flag.id, value) if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { notifyFlagChanged(flag) } } fun set(flag: ResourceBooleanFlag, value: Boolean) { booleanFlags.put(flag.id, value) if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { notifyFlagChanged(flag) } } fun set(flag: SysPropBooleanFlag, value: Boolean) { booleanFlags.put(flag.id, value) if (booleanFlags.put(flag.id, value)?.let { value != it } != false) { notifyFlagChanged(flag) } } fun set(flag: StringFlag, value: String) { stringFlags.put(flag.id, value) if (stringFlags.put(flag.id, value)?.let { value != it } == null) { notifyFlagChanged(flag) } } fun set(flag: ResourceStringFlag, value: String) { stringFlags.put(flag.id, value) if (stringFlags.put(flag.id, value)?.let { value != it } == null) { notifyFlagChanged(flag) } } private fun notifyFlagChanged(flag: Flag<*>) { flagListeners[flag.id]?.let { listeners -> listeners.forEach { listener -> listener.onFlagChanged( object : FlagListenable.FlagEvent { override val flagId = flag.id override fun requestNoRestart() {} } ) } } } override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.id) Loading @@ -70,25 +93,30 @@ class FakeFeatureFlags : FeatureFlags { override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id) override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {} override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) { flagListeners.getOrPut(flag.id) { mutableSetOf() }.add(listener) listenerFlagIds.getOrPut(listener) { mutableSetOf() }.add(flag.id) } override fun removeListener(listener: FlagListenable.Listener) {} override fun removeListener(listener: FlagListenable.Listener) { listenerFlagIds.remove(listener)?.let { flagIds -> flagIds.forEach { id -> flagListeners[id]?.remove(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] ?: error("Flag ${flagName(flagId)} was accessed but not specified.") } private fun requireStringValue(flagId: Int): String { if (!stringFlags.containsKey(flagId)) { throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.") } return stringFlags[flagId] ?: error("Flag ${flagName(flagId)} was accessed but not specified.") } }