1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.flags
18 
19 import android.testing.AndroidTestingRunner
20 import androidx.test.filters.SmallTest
21 import com.android.systemui.SysuiTestCase
22 import com.google.common.truth.Truth.assertThat
23 import org.junit.Assert.fail
24 import org.junit.Test
25 import org.junit.runner.RunWith
26 
27 @SmallTest
28 @RunWith(AndroidTestingRunner::class)
29 class FakeFeatureFlagsTest : SysuiTestCase() {
30 
31     private val unreleasedFlag = UnreleasedFlag("-1000", "test")
32     private val releasedFlag = ReleasedFlag("-1001", "test")
33     private val stringFlag = StringFlag("-1002", "test")
34     private val resourceBooleanFlag = ResourceBooleanFlag("-1003", "test", resourceId = -1)
35     private val resourceStringFlag = ResourceStringFlag("-1004", "test", resourceId = -1)
36     private val sysPropBooleanFlag = SysPropBooleanFlag("test", "test")
37 
38     /**
39      * FakeFeatureFlags does not honor any default values. All flags which are accessed must be
40      * specified. If not, an exception is thrown.
41      */
42     @Test
43     fun accessingUnspecifiedFlags_throwsException() {
44         val flags: FeatureFlags = FakeFeatureFlags()
45         try {
46             assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
47             fail("Expected an exception when accessing an unspecified flag.")
48         } catch (ex: IllegalStateException) {
49             assertThat(ex.message).contains("UNKNOWN(teamfood)")
50         }
51         try {
52             assertThat(flags.isEnabled(unreleasedFlag)).isFalse()
53             fail("Expected an exception when accessing an unspecified flag.")
54         } catch (ex: IllegalStateException) {
55             assertThat(ex.message).contains("UNKNOWN(-1000)")
56         }
57         try {
58             assertThat(flags.isEnabled(releasedFlag)).isFalse()
59             fail("Expected an exception when accessing an unspecified flag.")
60         } catch (ex: IllegalStateException) {
61             assertThat(ex.message).contains("UNKNOWN(-1001)")
62         }
63         try {
64             assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
65             fail("Expected an exception when accessing an unspecified flag.")
66         } catch (ex: IllegalStateException) {
67             assertThat(ex.message).contains("UNKNOWN(-1003)")
68         }
69         try {
70             assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
71             fail("Expected an exception when accessing an unspecified flag.")
72         } catch (ex: IllegalStateException) {
73             assertThat(ex.message).contains("UNKNOWN(test)")
74         }
75         try {
76             assertThat(flags.getString(stringFlag)).isEmpty()
77             fail("Expected an exception when accessing an unspecified flag.")
78         } catch (ex: IllegalStateException) {
79             assertThat(ex.message).contains("UNKNOWN(-1002)")
80         }
81         try {
82             assertThat(flags.getString(resourceStringFlag)).isEmpty()
83             fail("Expected an exception when accessing an unspecified flag.")
84         } catch (ex: IllegalStateException) {
85             assertThat(ex.message).contains("UNKNOWN(-1004)")
86         }
87     }
88 
89     @Test
90     fun specifiedFlags_returnCorrectValues() {
91         val flags = FakeFeatureFlags()
92         flags.set(unreleasedFlag, false)
93         flags.set(releasedFlag, false)
94         flags.set(resourceBooleanFlag, false)
95         flags.set(sysPropBooleanFlag, false)
96         flags.set(resourceStringFlag, "")
97 
98         assertThat(flags.isEnabled(unreleasedFlag)).isFalse()
99         assertThat(flags.isEnabled(releasedFlag)).isFalse()
100         assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
101         assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
102         assertThat(flags.getString(resourceStringFlag)).isEmpty()
103 
104         flags.set(unreleasedFlag, true)
105         flags.set(releasedFlag, true)
106         flags.set(resourceBooleanFlag, true)
107         flags.set(sysPropBooleanFlag, true)
108         flags.set(resourceStringFlag, "Android")
109 
110         assertThat(flags.isEnabled(unreleasedFlag)).isTrue()
111         assertThat(flags.isEnabled(releasedFlag)).isTrue()
112         assertThat(flags.isEnabled(resourceBooleanFlag)).isTrue()
113         assertThat(flags.isEnabled(sysPropBooleanFlag)).isTrue()
114         assertThat(flags.getString(resourceStringFlag)).isEqualTo("Android")
115     }
116 
117     @Test
118     fun listenerForBooleanFlag_calledOnlyWhenFlagChanged() {
119         val flags = FakeFeatureFlags()
120         val listener = VerifyingListener()
121         flags.addListener(unreleasedFlag, listener)
122 
123         flags.set(unreleasedFlag, true)
124         flags.set(unreleasedFlag, true)
125         flags.set(unreleasedFlag, false)
126         flags.set(unreleasedFlag, false)
127 
128         listener.verifyInOrder(unreleasedFlag.name, unreleasedFlag.name)
129     }
130 
131     @Test
132     fun listenerForStringFlag_calledOnlyWhenFlagChanged() {
133         val flags = FakeFeatureFlags()
134         val listener = VerifyingListener()
135         flags.addListener(stringFlag, listener)
136 
137         flags.set(stringFlag, "Test")
138         flags.set(stringFlag, "Test")
139 
140         listener.verifyInOrder(stringFlag.name)
141     }
142 
143     @Test
144     fun listenerForBooleanFlag_notCalledAfterRemoved() {
145         val flags = FakeFeatureFlags()
146         val listener = VerifyingListener()
147         flags.addListener(unreleasedFlag, listener)
148         flags.set(unreleasedFlag, true)
149         flags.removeListener(listener)
150         flags.set(unreleasedFlag, false)
151 
152         listener.verifyInOrder(unreleasedFlag.name)
153     }
154 
155     @Test
156     fun listenerForStringFlag_notCalledAfterRemoved() {
157         val flags = FakeFeatureFlags()
158         val listener = VerifyingListener()
159 
160         flags.addListener(stringFlag, listener)
161         flags.set(stringFlag, "Test")
162         flags.removeListener(listener)
163         flags.set(stringFlag, "Other")
164 
165         listener.verifyInOrder(stringFlag.name)
166     }
167 
168     @Test
169     fun listenerForMultipleFlags_calledWhenFlagsChange() {
170         val flags = FakeFeatureFlags()
171         val listener = VerifyingListener()
172         flags.addListener(unreleasedFlag, listener)
173         flags.addListener(releasedFlag, listener)
174 
175         flags.set(releasedFlag, true)
176         flags.set(unreleasedFlag, true)
177 
178         listener.verifyInOrder(releasedFlag.name, unreleasedFlag.name)
179     }
180 
181     @Test
182     fun listenerForMultipleFlags_notCalledAfterRemoved() {
183         val flags = FakeFeatureFlags()
184         val listener = VerifyingListener()
185 
186         flags.addListener(unreleasedFlag, listener)
187         flags.addListener(releasedFlag, listener)
188         flags.set(releasedFlag, true)
189         flags.set(unreleasedFlag, true)
190         flags.removeListener(listener)
191         flags.set(releasedFlag, false)
192         flags.set(unreleasedFlag, false)
193 
194         listener.verifyInOrder(releasedFlag.name, unreleasedFlag.name)
195     }
196 
197     @Test
198     fun multipleListenersForSingleFlag_allAreCalledWhenChanged() {
199         val flags = FakeFeatureFlags()
200         val listener1 = VerifyingListener()
201         val listener2 = VerifyingListener()
202         flags.addListener(releasedFlag, listener1)
203         flags.addListener(releasedFlag, listener2)
204 
205         flags.set(releasedFlag, true)
206 
207         listener1.verifyInOrder(releasedFlag.name)
208         listener2.verifyInOrder(releasedFlag.name)
209     }
210 
211     @Test
212     fun multipleListenersForSingleFlag_removedListenerNotCalledAfterRemoval() {
213         val flags = FakeFeatureFlags()
214         val listener1 = VerifyingListener()
215         val listener2 = VerifyingListener()
216         flags.addListener(releasedFlag, listener1)
217         flags.addListener(releasedFlag, listener2)
218 
219         flags.set(releasedFlag, true)
220         flags.removeListener(listener2)
221         flags.set(releasedFlag, false)
222 
223         listener1.verifyInOrder(releasedFlag.name, releasedFlag.name)
224         listener2.verifyInOrder(releasedFlag.name)
225     }
226 
227     class VerifyingListener : FlagListenable.Listener {
228         var flagEventNames = mutableListOf<String>()
229         override fun onFlagChanged(event: FlagListenable.FlagEvent) {
230             flagEventNames.add(event.flagName)
231         }
232 
233         fun verifyInOrder(vararg eventNames: String) {
234             assertThat(flagEventNames).containsExactlyElementsIn(eventNames.asList())
235         }
236     }
237 }
238