1 /*
2  * Copyright (C) 2021 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 package com.android.systemui.flags
17 
18 import android.content.BroadcastReceiver
19 import android.content.Context
20 import android.content.Intent
21 import android.content.pm.PackageManager.NameNotFoundException
22 import android.content.res.Resources
23 import android.content.res.Resources.NotFoundException
24 import android.platform.test.annotations.DisableFlags
25 import android.platform.test.annotations.EnableFlags
26 import androidx.test.ext.junit.runners.AndroidJUnit4
27 import androidx.test.filters.SmallTest
28 import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD
29 import com.android.systemui.SysuiTestCase
30 import com.android.systemui.settings.FakeUserTracker
31 import com.android.systemui.util.concurrency.FakeExecutor
32 import com.android.systemui.util.mockito.any
33 import com.android.systemui.util.mockito.eq
34 import com.android.systemui.util.mockito.nullable
35 import com.android.systemui.util.mockito.withArgCaptor
36 import com.android.systemui.util.settings.GlobalSettings
37 import com.android.systemui.util.time.FakeSystemClock
38 import com.google.common.truth.Truth.assertThat
39 import java.io.PrintWriter
40 import java.io.Serializable
41 import java.io.StringWriter
42 import java.util.function.Consumer
43 import org.junit.Assert
44 import org.junit.Before
45 import org.junit.Test
46 import org.junit.runner.RunWith
47 import org.mockito.Mock
48 import org.mockito.Mockito.anyBoolean
49 import org.mockito.Mockito.anyString
50 import org.mockito.Mockito.inOrder
51 import org.mockito.Mockito.never
52 import org.mockito.Mockito.times
53 import org.mockito.Mockito.verify
54 import org.mockito.Mockito.verifyNoMoreInteractions
55 import org.mockito.Mockito.`when` as whenever
56 import org.mockito.MockitoAnnotations
57 
58 /**
59  * NOTE: This test is for the version of FeatureFlagManager in src-debug, which allows overriding
60  * the default.
61  */
62 @SmallTest
63 @RunWith(AndroidJUnit4::class)
64 class FeatureFlagsClassicDebugTest : SysuiTestCase() {
65     private lateinit var mFeatureFlagsClassicDebug: FeatureFlagsClassicDebug
66 
67     @Mock private lateinit var flagManager: FlagManager
68     @Mock private lateinit var mockContext: Context
69     @Mock private lateinit var globalSettings: GlobalSettings
70     @Mock private lateinit var systemProperties: SystemPropertiesHelper
71     @Mock private lateinit var resources: Resources
72     @Mock private lateinit var restarter: Restarter
73     private lateinit var fakeExecutor: FakeExecutor
74     private lateinit var userTracker: FakeUserTracker
75     private val flagMap = mutableMapOf<String, Flag<*>>()
76     private lateinit var broadcastReceiver: BroadcastReceiver
77     private lateinit var clearCacheAction: Consumer<String>
78     private val serverFlagReader = ServerFlagReaderFake()
79 
80     private val teamfoodableFlagA = UnreleasedFlag(name = "a", namespace = "test", teamfood = true)
81     private val releasedFlagB = ReleasedFlag(name = "b", namespace = "test")
82 
83     @Before
setupnull84     fun setup() {
85         MockitoAnnotations.initMocks(this)
86         flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
87         flagMap.put(releasedFlagB.name, releasedFlagB)
88 
89         fakeExecutor = FakeExecutor(FakeSystemClock())
90         userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockContext })
91 
92         mFeatureFlagsClassicDebug =
93             FeatureFlagsClassicDebug(
94                 flagManager,
95                 mockContext,
96                 globalSettings,
97                 systemProperties,
98                 resources,
99                 serverFlagReader,
100                 flagMap,
101                 restarter,
102                 userTracker,
103                 fakeExecutor,
104             )
105         mFeatureFlagsClassicDebug.init()
106         verify(flagManager).onSettingsChangedAction = any()
107         broadcastReceiver = withArgCaptor {
108             verify(mockContext).registerReceiver(capture(), any(), nullable(), nullable(), any())
109         }
110         clearCacheAction = withArgCaptor { verify(flagManager).clearCacheAction = capture() }
111         whenever(flagManager.nameToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" }
112     }
113 
114     @Test
readBooleanFlagnull115     fun readBooleanFlag() {
116         whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true)
117         whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false)
118 
119         assertThat(
120                 mFeatureFlagsClassicDebug.isEnabled(ReleasedFlag(name = "2", namespace = "test"))
121             )
122             .isTrue()
123         assertThat(
124                 mFeatureFlagsClassicDebug.isEnabled(UnreleasedFlag(name = "3", namespace = "test"))
125             )
126             .isTrue()
127         assertThat(
128                 mFeatureFlagsClassicDebug.isEnabled(ReleasedFlag(name = "4", namespace = "test"))
129             )
130             .isFalse()
131         assertThat(
132                 mFeatureFlagsClassicDebug.isEnabled(UnreleasedFlag(name = "5", namespace = "test"))
133             )
134             .isFalse()
135     }
136 
137     @Test
138     @DisableFlags(FLAG_SYSUI_TEAMFOOD)
teamFoodFlag_Falsenull139     fun teamFoodFlag_False() {
140         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isFalse()
141         assertThat(mFeatureFlagsClassicDebug.isEnabled(releasedFlagB)).isTrue()
142 
143         // Regular boolean flags should still test the same.
144         // Only our teamfoodableFlag should change.
145         readBooleanFlag()
146     }
147 
148     @Test
149     @EnableFlags(FLAG_SYSUI_TEAMFOOD)
teamFoodFlag_Truenull150     fun teamFoodFlag_True() {
151         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
152         assertThat(mFeatureFlagsClassicDebug.isEnabled(releasedFlagB)).isTrue()
153 
154         // Regular boolean flags should still test the same.
155         // Only our teamfoodableFlag should change.
156         readBooleanFlag()
157     }
158 
159     @Test
160     @EnableFlags(FLAG_SYSUI_TEAMFOOD)
teamFoodFlag_Overriddennull161     fun teamFoodFlag_Overridden() {
162         whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.name), any()))
163             .thenReturn(true)
164         whenever(flagManager.readFlagValue<Boolean>(eq(releasedFlagB.name), any()))
165             .thenReturn(false)
166         assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
167         assertThat(mFeatureFlagsClassicDebug.isEnabled(releasedFlagB)).isFalse()
168 
169         // Regular boolean flags should still test the same.
170         // Only our teamfoodableFlag should change.
171         readBooleanFlag()
172     }
173 
174     @Test
readResourceBooleanFlagnull175     fun readResourceBooleanFlag() {
176         whenever(resources.getBoolean(1001)).thenReturn(false)
177         whenever(resources.getBoolean(1002)).thenReturn(true)
178         whenever(resources.getBoolean(1003)).thenReturn(false)
179         whenever(resources.getBoolean(1004)).thenAnswer { throw NameNotFoundException() }
180         whenever(resources.getBoolean(1005)).thenAnswer { throw NameNotFoundException() }
181 
182         whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true)
183         whenever(flagManager.readFlagValue<Boolean>(eq("5"), any())).thenReturn(false)
184 
185         assertThat(mFeatureFlagsClassicDebug.isEnabled(ResourceBooleanFlag("1", "test", 1001)))
186             .isFalse()
187         assertThat(mFeatureFlagsClassicDebug.isEnabled(ResourceBooleanFlag("2", "test", 1002)))
188             .isTrue()
189         assertThat(mFeatureFlagsClassicDebug.isEnabled(ResourceBooleanFlag("3", "test", 1003)))
190             .isTrue()
191 
192         Assert.assertThrows(NameNotFoundException::class.java) {
193             mFeatureFlagsClassicDebug.isEnabled(ResourceBooleanFlag("4", "test", 1004))
194         }
195         // Test that resource is loaded (and validated) even when the setting is set.
196         //  This prevents developers from not noticing when they reference an invalid resource.
197         Assert.assertThrows(NameNotFoundException::class.java) {
198             mFeatureFlagsClassicDebug.isEnabled(ResourceBooleanFlag("5", "test", 1005))
199         }
200     }
201 
202     @Test
readSysPropBooleanFlagnull203     fun readSysPropBooleanFlag() {
204         whenever(systemProperties.getBoolean(anyString(), anyBoolean())).thenAnswer {
205             if ("b".equals(it.getArgument<String?>(0))) {
206                 return@thenAnswer true
207             }
208             return@thenAnswer it.getArgument(1)
209         }
210 
211         assertThat(mFeatureFlagsClassicDebug.isEnabled(SysPropBooleanFlag("a", "test"))).isFalse()
212         assertThat(mFeatureFlagsClassicDebug.isEnabled(SysPropBooleanFlag("b", "test"))).isTrue()
213         assertThat(mFeatureFlagsClassicDebug.isEnabled(SysPropBooleanFlag("c", "test", true)))
214             .isTrue()
215         assertThat(mFeatureFlagsClassicDebug.isEnabled(SysPropBooleanFlag("d", "test", false)))
216             .isFalse()
217         assertThat(mFeatureFlagsClassicDebug.isEnabled(SysPropBooleanFlag("e", "test"))).isFalse()
218     }
219 
220     @Test
readStringFlagnull221     fun readStringFlag() {
222         whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo")
223         whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar")
224         assertThat(mFeatureFlagsClassicDebug.getString(StringFlag("1", "test", "biz")))
225             .isEqualTo("biz")
226         assertThat(mFeatureFlagsClassicDebug.getString(StringFlag("2", "test", "baz")))
227             .isEqualTo("baz")
228         assertThat(mFeatureFlagsClassicDebug.getString(StringFlag("3", "test", "buz")))
229             .isEqualTo("foo")
230         assertThat(mFeatureFlagsClassicDebug.getString(StringFlag("4", "test", "buz")))
231             .isEqualTo("bar")
232     }
233 
234     @Test
readResourceStringFlagnull235     fun readResourceStringFlag() {
236         whenever(resources.getString(1001)).thenReturn("")
237         whenever(resources.getString(1002)).thenReturn("resource2")
238         whenever(resources.getString(1003)).thenReturn("resource3")
239         whenever(resources.getString(1004)).thenReturn(null)
240         whenever(resources.getString(1005)).thenAnswer { throw NameNotFoundException() }
241         whenever(resources.getString(1006)).thenAnswer { throw NameNotFoundException() }
242 
243         whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("override3")
244         whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("override4")
245         whenever(flagManager.readFlagValue<String>(eq("6"), any())).thenReturn("override6")
246 
247         assertThat(mFeatureFlagsClassicDebug.getString(ResourceStringFlag("1", "test", 1001)))
248             .isEqualTo("")
249         assertThat(mFeatureFlagsClassicDebug.getString(ResourceStringFlag("2", "test", 1002)))
250             .isEqualTo("resource2")
251         assertThat(mFeatureFlagsClassicDebug.getString(ResourceStringFlag("3", "test", 1003)))
252             .isEqualTo("override3")
253 
254         Assert.assertThrows(NullPointerException::class.java) {
255             mFeatureFlagsClassicDebug.getString(ResourceStringFlag("4", "test", 1004))
256         }
257         Assert.assertThrows(NameNotFoundException::class.java) {
258             mFeatureFlagsClassicDebug.getString(ResourceStringFlag("5", "test", 1005))
259         }
260         // Test that resource is loaded (and validated) even when the setting is set.
261         //  This prevents developers from not noticing when they reference an invalid resource.
262         Assert.assertThrows(NameNotFoundException::class.java) {
263             mFeatureFlagsClassicDebug.getString(ResourceStringFlag("6", "test", 1005))
264         }
265     }
266 
267     @Test
readIntFlagnull268     fun readIntFlag() {
269         whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22)
270         whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48)
271         assertThat(mFeatureFlagsClassicDebug.getInt(IntFlag("1", "test", 12))).isEqualTo(12)
272         assertThat(mFeatureFlagsClassicDebug.getInt(IntFlag("2", "test", 93))).isEqualTo(93)
273         assertThat(mFeatureFlagsClassicDebug.getInt(IntFlag("3", "test", 8))).isEqualTo(22)
274         assertThat(mFeatureFlagsClassicDebug.getInt(IntFlag("4", "test", 234))).isEqualTo(48)
275     }
276 
277     @Test
readResourceIntFlagnull278     fun readResourceIntFlag() {
279         whenever(resources.getInteger(1001)).thenReturn(88)
280         whenever(resources.getInteger(1002)).thenReturn(61)
281         whenever(resources.getInteger(1003)).thenReturn(9342)
282         whenever(resources.getInteger(1004)).thenThrow(NotFoundException("unknown resource"))
283         whenever(resources.getInteger(1005)).thenThrow(NotFoundException("unknown resource"))
284         whenever(resources.getInteger(1006)).thenThrow(NotFoundException("unknown resource"))
285 
286         whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(20)
287         whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(500)
288         whenever(flagManager.readFlagValue<Int>(eq("5"), any())).thenReturn(9519)
289 
290         assertThat(mFeatureFlagsClassicDebug.getInt(ResourceIntFlag("1", "test", 1001)))
291             .isEqualTo(88)
292         assertThat(mFeatureFlagsClassicDebug.getInt(ResourceIntFlag("2", "test", 1002)))
293             .isEqualTo(61)
294         assertThat(mFeatureFlagsClassicDebug.getInt(ResourceIntFlag("3", "test", 1003)))
295             .isEqualTo(20)
296 
297         Assert.assertThrows(NotFoundException::class.java) {
298             mFeatureFlagsClassicDebug.getInt(ResourceIntFlag("4", "test", 1004))
299         }
300         // Test that resource is loaded (and validated) even when the setting is set.
301         //  This prevents developers from not noticing when they reference an invalid resource.
302         Assert.assertThrows(NotFoundException::class.java) {
303             mFeatureFlagsClassicDebug.getInt(ResourceIntFlag("5", "test", 1005))
304         }
305     }
306 
307     @Test
broadcastReceiver_IgnoresInvalidDatanull308     fun broadcastReceiver_IgnoresInvalidData() {
309         addFlag(UnreleasedFlag("1", "test"))
310         addFlag(ResourceBooleanFlag("2", "test", 1002))
311         addFlag(StringFlag("3", "test", "flag3"))
312         addFlag(ResourceStringFlag("4", "test", 1004))
313 
314         broadcastReceiver.onReceive(mockContext, null)
315         broadcastReceiver.onReceive(mockContext, Intent())
316         broadcastReceiver.onReceive(mockContext, Intent("invalid action"))
317         broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG))
318         setByBroadcast("0", false) // unknown id does nothing
319         setByBroadcast("1", "string") // wrong type does nothing
320         setByBroadcast("2", 123) // wrong type does nothing
321         setByBroadcast("3", false) // wrong type does nothing
322         setByBroadcast("4", 123) // wrong type does nothing
323         verifyNoMoreInteractions(flagManager, globalSettings)
324     }
325 
326     @Test
intentWithId_NoValueKeyClearsnull327     fun intentWithId_NoValueKeyClears() {
328         addFlag(UnreleasedFlag(name = "1", namespace = "test"))
329 
330         // trying to erase an id not in the map does nothing
331         broadcastReceiver.onReceive(
332             mockContext,
333             Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, ""),
334         )
335         verifyNoMoreInteractions(flagManager, globalSettings)
336 
337         // valid id with no value puts empty string in the setting
338         broadcastReceiver.onReceive(
339             mockContext,
340             Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1"),
341         )
342         verifyPutData("1", "", numReads = 0)
343     }
344 
345     @Test
setBooleanFlagnull346     fun setBooleanFlag() {
347         addFlag(UnreleasedFlag("1", "test"))
348         addFlag(UnreleasedFlag("2", "test"))
349         addFlag(ResourceBooleanFlag("3", "test", 1003))
350         addFlag(ResourceBooleanFlag("4", "test", 1004))
351 
352         setByBroadcast("1", false)
353         verifyPutData("1", "{\"type\":\"boolean\",\"value\":false}")
354 
355         setByBroadcast("2", true)
356         verifyPutData("2", "{\"type\":\"boolean\",\"value\":true}")
357 
358         setByBroadcast("3", false)
359         verifyPutData("3", "{\"type\":\"boolean\",\"value\":false}")
360 
361         setByBroadcast("4", true)
362         verifyPutData("4", "{\"type\":\"boolean\",\"value\":true}")
363     }
364 
365     @Test
setStringFlagnull366     fun setStringFlag() {
367         addFlag(StringFlag("1", "1", "test"))
368         addFlag(ResourceStringFlag("2", "test", 1002))
369 
370         setByBroadcast("1", "override1")
371         verifyPutData("1", "{\"type\":\"string\",\"value\":\"override1\"}")
372 
373         setByBroadcast("2", "override2")
374         verifyPutData("2", "{\"type\":\"string\",\"value\":\"override2\"}")
375     }
376 
377     @Test
setFlag_ClearsCachenull378     fun setFlag_ClearsCache() {
379         val flag1 = addFlag(StringFlag("1", "test", "flag1"))
380         whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original")
381 
382         // gets the flag & cache it
383         assertThat(mFeatureFlagsClassicDebug.getString(flag1)).isEqualTo("original")
384         verify(flagManager, times(1)).readFlagValue(eq("1"), eq(StringFlagSerializer))
385 
386         // hit the cache
387         assertThat(mFeatureFlagsClassicDebug.getString(flag1)).isEqualTo("original")
388         verifyNoMoreInteractions(flagManager)
389 
390         // set the flag
391         setByBroadcast("1", "new")
392         verifyPutData("1", "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
393         whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("new")
394 
395         assertThat(mFeatureFlagsClassicDebug.getString(flag1)).isEqualTo("new")
396         verify(flagManager, times(3)).readFlagValue(eq("1"), eq(StringFlagSerializer))
397     }
398 
399     @Test
serverSide_Overrides_MakesFalsenull400     fun serverSide_Overrides_MakesFalse() {
401         val flag = ReleasedFlag("100", "test")
402 
403         serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
404 
405         assertThat(mFeatureFlagsClassicDebug.isEnabled(flag)).isFalse()
406     }
407 
408     @Test
serverSide_Overrides_MakesTruenull409     fun serverSide_Overrides_MakesTrue() {
410         val flag = UnreleasedFlag(name = "100", namespace = "test")
411 
412         serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
413         assertThat(mFeatureFlagsClassicDebug.isEnabled(flag)).isTrue()
414     }
415 
416     @Test
417     @DisableFlags(FLAG_SYSUI_TEAMFOOD)
serverSide_OverrideUncached_NoRestartnull418     fun serverSide_OverrideUncached_NoRestart() {
419         // No one has read the flag, so it's not in the cache.
420         serverFlagReader.setFlagValue(
421             teamfoodableFlagA.namespace,
422             teamfoodableFlagA.name,
423             !teamfoodableFlagA.default,
424         )
425         verify(restarter, never()).restartSystemUI(anyString())
426     }
427 
428     @Test
429     @DisableFlags(FLAG_SYSUI_TEAMFOOD)
serverSide_Override_Restartsnull430     fun serverSide_Override_Restarts() {
431         // Read it to put it in the cache.
432         mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)
433         serverFlagReader.setFlagValue(
434             teamfoodableFlagA.namespace,
435             teamfoodableFlagA.name,
436             !teamfoodableFlagA.default,
437         )
438         verify(restarter).restartSystemUI(anyString())
439     }
440 
441     @Test
442     @DisableFlags(FLAG_SYSUI_TEAMFOOD)
serverSide_RedundantOverride_NoRestartnull443     fun serverSide_RedundantOverride_NoRestart() {
444         // Read it to put it in the cache.
445         mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)
446         serverFlagReader.setFlagValue(
447             teamfoodableFlagA.namespace,
448             teamfoodableFlagA.name,
449             teamfoodableFlagA.default,
450         )
451         verify(restarter, never()).restartSystemUI(anyString())
452     }
453 
454     @Test
dumpFormatnull455     fun dumpFormat() {
456         val flag1 = ReleasedFlag("1", "test")
457         val flag2 = ResourceBooleanFlag("2", "test", 1002)
458         val flag3 = UnreleasedFlag("3", "test")
459         val flag4 = StringFlag("4", "test", "")
460         val flag5 = StringFlag("5", "test", "flag5default")
461         val flag6 = ResourceStringFlag("6", "test", 1006)
462         val flag7 = ResourceStringFlag("7", "test", 1007)
463 
464         whenever(resources.getBoolean(1002)).thenReturn(true)
465         whenever(resources.getString(1006)).thenReturn("resource1006")
466         whenever(resources.getString(1007)).thenReturn("resource1007")
467         whenever(flagManager.readFlagValue(eq("7"), eq(StringFlagSerializer)))
468             .thenReturn("override7")
469 
470         // WHEN the flags have been accessed
471         assertThat(mFeatureFlagsClassicDebug.isEnabled(flag1)).isTrue()
472         assertThat(mFeatureFlagsClassicDebug.isEnabled(flag2)).isTrue()
473         assertThat(mFeatureFlagsClassicDebug.isEnabled(flag3)).isFalse()
474         assertThat(mFeatureFlagsClassicDebug.getString(flag4)).isEmpty()
475         assertThat(mFeatureFlagsClassicDebug.getString(flag5)).isEqualTo("flag5default")
476         assertThat(mFeatureFlagsClassicDebug.getString(flag6)).isEqualTo("resource1006")
477         assertThat(mFeatureFlagsClassicDebug.getString(flag7)).isEqualTo("override7")
478 
479         // THEN the dump contains the flags and the default values
480         val dump = dumpToString()
481         assertThat(dump).contains(" sysui_flag_1: true\n")
482         assertThat(dump).contains(" sysui_flag_2: true\n")
483         assertThat(dump).contains(" sysui_flag_3: false\n")
484         assertThat(dump).contains(" sysui_flag_4: [length=0] \"\"\n")
485         assertThat(dump).contains(" sysui_flag_5: [length=12] \"flag5default\"\n")
486         assertThat(dump).contains(" sysui_flag_6: [length=12] \"resource1006\"\n")
487         assertThat(dump).contains(" sysui_flag_7: [length=9] \"override7\"\n")
488     }
489 
verifyPutDatanull490     private fun verifyPutData(name: String, data: String, numReads: Int = 1) {
491         inOrder(flagManager, globalSettings)
492             .apply {
493                 verify(flagManager, times(numReads))
494                     .readFlagValue(eq(name), any<FlagSerializer<*>>())
495                 verify(flagManager).nameToSettingsKey(eq(name))
496                 verify(globalSettings).putString(eq("key-$name"), eq(data))
497                 verify(flagManager).dispatchListenersAndMaybeRestart(eq(name), any())
498             }
499             .verifyNoMoreInteractions()
500         verifyNoMoreInteractions(flagManager, globalSettings)
501     }
502 
setByBroadcastnull503     private fun setByBroadcast(name: String, value: Serializable?) {
504         val intent = Intent(FlagManager.ACTION_SET_FLAG)
505         intent.putExtra(FlagManager.EXTRA_NAME, name)
506         intent.putExtra(FlagManager.EXTRA_VALUE, value)
507         broadcastReceiver.onReceive(mockContext, intent)
508     }
509 
addFlagnull510     private fun <F : Flag<*>> addFlag(flag: F): F {
511         val old = flagMap.put(flag.name, flag)
512         check(old == null) { "Flag ${flag.name} already registered" }
513         return flag
514     }
515 
dumpToStringnull516     private fun dumpToString(): String {
517         val sw = StringWriter()
518         val pw = PrintWriter(sw)
519         mFeatureFlagsClassicDebug.dump(pw, emptyArray<String>())
520         pw.flush()
521         return sw.toString()
522     }
523 }
524