Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 093245af authored by Mark Renouf's avatar Mark Renouf
Browse files

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
parent 2ff2a64c
Loading
Loading
Loading
Loading
+93 −0
Original line number Diff line number Diff line
/*
 * 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]
    }
}
+106 −0
Original line number Diff line number Diff line
/*
 * 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")
    }
}