• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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.util.kotlin
18 
19 import androidx.test.ext.junit.runners.AndroidJUnit4
20 import androidx.test.filters.SmallTest
21 import com.android.systemui.SysuiTestCase
22 import com.android.systemui.util.time.FakeSystemClock
23 import com.google.common.truth.Truth.assertThat
24 import kotlin.time.Duration.Companion.milliseconds
25 import kotlinx.coroutines.Dispatchers
26 import kotlinx.coroutines.Job
27 import kotlinx.coroutines.delay
28 import kotlinx.coroutines.flow.Flow
29 import kotlinx.coroutines.flow.MutableSharedFlow
30 import kotlinx.coroutines.flow.MutableStateFlow
31 import kotlinx.coroutines.flow.asFlow
32 import kotlinx.coroutines.flow.emptyFlow
33 import kotlinx.coroutines.flow.filterIsInstance
34 import kotlinx.coroutines.flow.flow
35 import kotlinx.coroutines.flow.flowOf
36 import kotlinx.coroutines.flow.merge
37 import kotlinx.coroutines.flow.onEach
38 import kotlinx.coroutines.flow.takeWhile
39 import kotlinx.coroutines.flow.toList
40 import kotlinx.coroutines.launch
41 import kotlinx.coroutines.runBlocking
42 import kotlinx.coroutines.test.TestScope
43 import kotlinx.coroutines.test.advanceTimeBy
44 import kotlinx.coroutines.test.runCurrent
45 import kotlinx.coroutines.test.runTest
46 import kotlinx.coroutines.yield
47 import org.junit.Test
48 import org.junit.runner.RunWith
49 
50 @SmallTest
51 @RunWith(AndroidJUnit4::class)
52 class PairwiseFlowTest : SysuiTestCase() {
53     @Test
54     fun simple() = runBlocking {
55         assertThatFlow((1..3).asFlow().pairwise()).emitsExactly(WithPrev(1, 2), WithPrev(2, 3))
56     }
57 
58     @Test fun notEnough() = runBlocking { assertThatFlow(flowOf(1).pairwise()).emitsNothing() }
59 
60     @Test
61     fun withInit() = runBlocking {
62         assertThatFlow(flowOf(2).pairwise(initialValue = 1)).emitsExactly(WithPrev(1, 2))
63     }
64 
65     @Test
66     fun notEnoughWithInit() = runBlocking {
67         assertThatFlow(emptyFlow<Int>().pairwise(initialValue = 1)).emitsNothing()
68     }
69 
70     @Test
71     fun withTransform() = runBlocking {
72         assertThatFlow(
73                 flowOf("val1", "val2", "val3").pairwiseBy { prev: String, next: String ->
74                     "$prev|$next"
75                 }
76             )
77             .emitsExactly("val1|val2", "val2|val3")
78     }
79 
80     @Test
81     fun withGetInit() = runBlocking {
82         var initRun = false
83         assertThatFlow(
84                 flowOf("val1", "val2").pairwiseBy(
85                     getInitialValue = {
86                         initRun = true
87                         "initial"
88                     }
89                 ) { prev: String, next: String ->
90                     "$prev|$next"
91                 }
92             )
93             .emitsExactly("initial|val1", "val1|val2")
94         assertThat(initRun).isTrue()
95     }
96 
97     @Test
98     fun notEnoughWithGetInit() = runBlocking {
99         var initRun = false
100         assertThatFlow(
101                 emptyFlow<String>().pairwiseBy(
102                     getInitialValue = {
103                         initRun = true
104                         "initial"
105                     }
106                 ) { prev: String, next: String ->
107                     "$prev|$next"
108                 }
109             )
110             .emitsNothing()
111         // Even though the flow will not emit anything, the initial value function should still get
112         // run.
113         assertThat(initRun).isTrue()
114     }
115 
116     @Test
117     fun getInitNotRunWhenFlowNotCollected() = runBlocking {
118         var initRun = false
119         flowOf("val1", "val2").pairwiseBy(
120             getInitialValue = {
121                 initRun = true
122                 "initial"
123             }
124         ) { prev: String, next: String ->
125             "$prev|$next"
126         }
127 
128         // Since the flow isn't collected, ensure [initialValueFun] isn't run.
129         assertThat(initRun).isFalse()
130     }
131 
132     @Test
133     fun withStateFlow() =
134         runBlocking(Dispatchers.Main.immediate) {
135             val state = MutableStateFlow(1)
136             val stop = MutableSharedFlow<Unit>()
137 
138             val stoppable = merge(state, stop).takeWhile { it is Int }.filterIsInstance<Int>()
139 
140             val job1 = launch { assertThatFlow(stoppable.pairwise()).emitsExactly(WithPrev(1, 2)) }
141             state.value = 2
142             val job2 = launch { assertThatFlow(stoppable.pairwise()).emitsNothing() }
143 
144             stop.emit(Unit)
145 
146             assertThatJob(job1).isCompleted()
147             assertThatJob(job2).isCompleted()
148         }
149 }
150 
151 @SmallTest
152 @RunWith(AndroidJUnit4::class)
153 class SetChangesFlowTest : SysuiTestCase() {
154     @Test
<lambda>null155     fun simple() = runBlocking {
156         assertThatFlow(flowOf(setOf(1, 2, 3), setOf(2, 3, 4)).setChanges())
157             .emitsExactly(
158                 SetChanges(added = setOf(1, 2, 3), removed = emptySet()),
159                 SetChanges(added = setOf(4), removed = setOf(1)),
160             )
161     }
162 
163     @Test
<lambda>null164     fun onlyOneEmission() = runBlocking {
165         assertThatFlow(flowOf(setOf(1)).setChanges())
166             .emitsExactly(SetChanges(added = setOf(1), removed = emptySet()))
167     }
168 
169     @Test
<lambda>null170     fun fromEmptySet() = runBlocking {
171         assertThatFlow(flowOf(emptySet(), setOf(1, 2)).setChanges())
172             .emitsExactly(SetChanges(removed = emptySet(), added = setOf(1, 2)))
173     }
174 
175     @Test
<lambda>null176     fun dontEmitFirstEvent() = runBlocking {
177         assertThatFlow(flowOf(setOf(1, 2), setOf(2, 3)).setChanges(emitFirstEvent = false))
178             .emitsExactly(SetChanges(removed = setOf(1), added = setOf(3)))
179     }
180 }
181 
182 @SmallTest
183 @RunWith(AndroidJUnit4::class)
184 class SampleFlowTest : SysuiTestCase() {
185     @Test
<lambda>null186     fun simple() = runBlocking {
187         assertThatFlow(
188                 flow {
189                         yield()
190                         emit(1)
191                     }
192                     .sample(flowOf(2)) { a, b -> a to b }
193             )
194             .emitsExactly(1 to 2)
195     }
196 
197     @Test
<lambda>null198     fun otherFlowNoValueYet() = runBlocking {
199         assertThatFlow(flowOf(1).sample(emptyFlow<Unit>())).emitsNothing()
200     }
201 
202     @Test
<lambda>null203     fun multipleSamples() = runBlocking {
204         val samplee = MutableSharedFlow<Int>()
205         val sampler = flow {
206             emit(1)
207             samplee.emit(1)
208             emit(2)
209             samplee.emit(2)
210             samplee.emit(3)
211             emit(3)
212             emit(4)
213         }
214         assertThatFlow(sampler.sample(samplee) { a, b -> a to b })
215             .emitsExactly(2 to 1, 3 to 3, 4 to 3)
216     }
217 }
218 
219 @SmallTest
220 @RunWith(AndroidJUnit4::class)
221 class ThrottleFlowTest : SysuiTestCase() {
222 
223     @Test
<lambda>null224     fun doesNotAffectEmissions_whenDelayAtLeastEqualToPeriod() = runTest {
225         // Arrange
226         val choreographer = createChoreographer(this)
227         val output = mutableListOf<Int>()
228         val collectJob =
229             backgroundScope.launch {
230                 flow {
231                         emit(1)
232                         delay(1000)
233                         emit(2)
234                     }
235                     .throttle(1000, choreographer.fakeClock)
236                     .toList(output)
237             }
238 
239         // Act
240         choreographer.advanceAndRun(0)
241 
242         // Assert
243         assertThat(output).containsExactly(1)
244 
245         // Act
246         choreographer.advanceAndRun(999)
247 
248         // Assert
249         assertThat(output).containsExactly(1)
250 
251         // Act
252         choreographer.advanceAndRun(1)
253 
254         // Assert
255         assertThat(output).containsExactly(1, 2)
256 
257         // Cleanup
258         collectJob.cancel()
259     }
260 
261     @Test
<lambda>null262     fun delaysEmissions_withShorterThanPeriodDelay_untilPeriodElapses() = runTest {
263         // Arrange
264         val choreographer = createChoreographer(this)
265         val output = mutableListOf<Int>()
266         val collectJob =
267             backgroundScope.launch {
268                 flow {
269                         emit(1)
270                         delay(500)
271                         emit(2)
272                     }
273                     .throttle(1000, choreographer.fakeClock)
274                     .toList(output)
275             }
276 
277         // Act
278         choreographer.advanceAndRun(0)
279 
280         // Assert
281         assertThat(output).containsExactly(1)
282 
283         // Act
284         choreographer.advanceAndRun(500)
285         choreographer.advanceAndRun(499)
286 
287         // Assert
288         assertThat(output).containsExactly(1)
289 
290         // Act
291         choreographer.advanceAndRun(1)
292 
293         // Assert
294         assertThat(output).containsExactly(1, 2)
295 
296         // Cleanup
297         collectJob.cancel()
298     }
299 
300     @Test
<lambda>null301     fun filtersAllButLastEmission_whenMultipleEmissionsInPeriod() = runTest {
302         // Arrange
303         val choreographer = createChoreographer(this)
304         val output = mutableListOf<Int>()
305         val collectJob =
306             backgroundScope.launch {
307                 flow {
308                         emit(1)
309                         delay(500)
310                         emit(2)
311                         delay(500)
312                         emit(3)
313                     }
314                     .throttle(1000, choreographer.fakeClock)
315                     .toList(output)
316             }
317 
318         // Act
319         choreographer.advanceAndRun(0)
320 
321         // Assert
322         assertThat(output).containsExactly(1)
323 
324         // Act
325         choreographer.advanceAndRun(500)
326         choreographer.advanceAndRun(499)
327 
328         // Assert
329         assertThat(output).containsExactly(1)
330 
331         // Act
332         choreographer.advanceAndRun(1)
333 
334         // Assert
335         assertThat(output).containsExactly(1, 3)
336 
337         // Cleanup
338         collectJob.cancel()
339     }
340 
341     @Test
<lambda>null342     fun filtersAllButLastEmission_andDelaysIt_whenMultipleEmissionsInShorterThanPeriod() = runTest {
343         // Arrange
344         val choreographer = createChoreographer(this)
345         val output = mutableListOf<Int>()
346         val collectJob =
347             backgroundScope.launch {
348                 flow {
349                         emit(1)
350                         delay(500)
351                         emit(2)
352                         delay(250)
353                         emit(3)
354                     }
355                     .throttle(1000, choreographer.fakeClock)
356                     .toList(output)
357             }
358 
359         // Act
360         choreographer.advanceAndRun(0)
361 
362         // Assert
363         assertThat(output).containsExactly(1)
364 
365         // Act
366         choreographer.advanceAndRun(500)
367         choreographer.advanceAndRun(250)
368         choreographer.advanceAndRun(249)
369 
370         // Assert
371         assertThat(output).containsExactly(1)
372 
373         // Act
374         choreographer.advanceAndRun(1)
375 
376         // Assert
377         assertThat(output).containsExactly(1, 3)
378 
379         // Cleanup
380         collectJob.cancel()
381     }
382 
createChoreographernull383     private fun createChoreographer(testScope: TestScope) =
384         object {
385             val fakeClock = FakeSystemClock()
386 
387             fun advanceAndRun(millis: Long) {
388                 fakeClock.advanceTime(millis)
389                 testScope.advanceTimeBy(millis)
390                 testScope.runCurrent()
391             }
392         }
393 }
394 
395 @SmallTest
396 @RunWith(AndroidJUnit4::class)
397 class SlidingWindowFlowTest : SysuiTestCase() {
398 
399     @Test
<lambda>null400     fun basicWindowing() = runTest {
401         val choreographer = createChoreographer(this)
402         val output = mutableListOf<List<Int>>()
403         val collectJob =
404             backgroundScope.launch {
405                 (1..5)
406                     .asFlow()
407                     .onEach { delay(100) }
408                     .slidingWindow(300.milliseconds, choreographer.fakeClock)
409                     .toList(output)
410             }
411 
412         choreographer.advanceAndRun(0)
413         assertThat(output).isEmpty()
414 
415         choreographer.advanceAndRun(100)
416         assertThat(output).containsExactly(listOf(1))
417 
418         choreographer.advanceAndRun(1)
419         assertThat(output).containsExactly(listOf(1))
420 
421         choreographer.advanceAndRun(99)
422         assertThat(output).containsExactly(listOf(1), listOf(1, 2))
423 
424         choreographer.advanceAndRun(100)
425         assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3))
426 
427         choreographer.advanceAndRun(100)
428         assertThat(output)
429             .containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3), listOf(2, 3, 4))
430 
431         choreographer.advanceAndRun(100)
432         assertThat(output)
433             .containsExactly(
434                 listOf(1),
435                 listOf(1, 2),
436                 listOf(1, 2, 3),
437                 listOf(2, 3, 4),
438                 listOf(3, 4, 5),
439             )
440 
441         choreographer.advanceAndRun(100)
442         assertThat(output)
443             .containsExactly(
444                 listOf(1),
445                 listOf(1, 2),
446                 listOf(1, 2, 3),
447                 listOf(2, 3, 4),
448                 listOf(3, 4, 5),
449                 listOf(4, 5),
450             )
451 
452         choreographer.advanceAndRun(100)
453         assertThat(output)
454             .containsExactly(
455                 listOf(1),
456                 listOf(1, 2),
457                 listOf(1, 2, 3),
458                 listOf(2, 3, 4),
459                 listOf(3, 4, 5),
460                 listOf(4, 5),
461                 listOf(5),
462             )
463 
464         choreographer.advanceAndRun(100)
465         assertThat(output)
466             .containsExactly(
467                 listOf(1),
468                 listOf(1, 2),
469                 listOf(1, 2, 3),
470                 listOf(2, 3, 4),
471                 listOf(3, 4, 5),
472                 listOf(4, 5),
473                 listOf(5),
474                 emptyList<Int>(),
475             )
476 
477         // Verify no more emissions
478         choreographer.advanceAndRun(9999999999)
479         assertThat(output)
480             .containsExactly(
481                 listOf(1),
482                 listOf(1, 2),
483                 listOf(1, 2, 3),
484                 listOf(2, 3, 4),
485                 listOf(3, 4, 5),
486                 listOf(4, 5),
487                 listOf(5),
488                 emptyList<Int>(),
489             )
490 
491         assertThat(collectJob.isCompleted).isTrue()
492     }
493 
494     @Test
<lambda>null495     fun initialEmptyFlow() = runTest {
496         val choreographer = createChoreographer(this)
497         val output = mutableListOf<List<Int>>()
498         val collectJob =
499             backgroundScope.launch {
500                 flow {
501                         delay(200)
502                         emit(1)
503                     }
504                     .slidingWindow(100.milliseconds, choreographer.fakeClock)
505                     .toList(output)
506             }
507 
508         choreographer.advanceAndRun(0)
509         assertThat(output).isEmpty()
510 
511         choreographer.advanceAndRun(200)
512         assertThat(output).containsExactly(listOf(1))
513 
514         choreographer.advanceAndRun(100)
515         assertThat(output).containsExactly(listOf(1), emptyList<Int>())
516 
517         assertThat(collectJob.isCompleted).isTrue()
518     }
519 
520     @Test
<lambda>null521     fun windowLargerThanData() = runTest {
522         val choreographer = createChoreographer(this)
523         val output = mutableListOf<List<Int>>()
524         val collectJob =
525             backgroundScope.launch {
526                 (1..3)
527                     .asFlow()
528                     .onEach { delay(50) }
529                     .slidingWindow(500.milliseconds, choreographer.fakeClock)
530                     .toList(output)
531             }
532 
533         choreographer.advanceAndRun(0)
534         assertThat(output).isEmpty()
535 
536         choreographer.advanceAndRun(50)
537         assertThat(output).containsExactly(listOf(1))
538 
539         choreographer.advanceAndRun(50)
540         assertThat(output).containsExactly(listOf(1), listOf(1, 2))
541 
542         choreographer.advanceAndRun(50)
543         assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3))
544 
545         // It has been 100ms since the first emission, which means we have 400ms left until the
546         // first item is evicted from the window. Ensure that we have no evictions until that time.
547         choreographer.advanceAndRun(399)
548         assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3))
549 
550         choreographer.advanceAndRun(1)
551         assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3), listOf(2, 3))
552 
553         choreographer.advanceAndRun(50)
554         assertThat(output)
555             .containsExactly(listOf(1), listOf(1, 2), listOf(1, 2, 3), listOf(2, 3), listOf(3))
556 
557         choreographer.advanceAndRun(50)
558         assertThat(output)
559             .containsExactly(
560                 listOf(1),
561                 listOf(1, 2),
562                 listOf(1, 2, 3),
563                 listOf(2, 3),
564                 listOf(3),
565                 emptyList<Int>(),
566             )
567 
568         assertThat(collectJob.isCompleted).isTrue()
569     }
570 
571     @Test
<lambda>null572     fun dataGapLargerThanWindow() = runTest {
573         val choreographer = createChoreographer(this)
574         val output = mutableListOf<List<Int>>()
575         val collectJob =
576             backgroundScope.launch {
577                 flow {
578                         emit(1)
579                         delay(200)
580                         emit(2)
581                         delay(500) // Gap larger than window
582                         emit(3)
583                     }
584                     .slidingWindow(300.milliseconds, choreographer.fakeClock)
585                     .toList(output)
586             }
587 
588         choreographer.advanceAndRun(0)
589         assertThat(output).containsExactly(listOf(1))
590 
591         choreographer.advanceAndRun(200)
592         assertThat(output).containsExactly(listOf(1), listOf(1, 2))
593 
594         choreographer.advanceAndRun(100)
595         assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(2))
596 
597         choreographer.advanceAndRun(200)
598         assertThat(output).containsExactly(listOf(1), listOf(1, 2), listOf(2), emptyList<Int>())
599 
600         choreographer.advanceAndRun(200)
601         assertThat(output)
602             .containsExactly(listOf(1), listOf(1, 2), listOf(2), emptyList<Int>(), listOf(3))
603 
604         choreographer.advanceAndRun(300)
605         assertThat(output)
606             .containsExactly(
607                 listOf(1),
608                 listOf(1, 2),
609                 listOf(2),
610                 emptyList<Int>(),
611                 listOf(3),
612                 emptyList<Int>(),
613             )
614 
615         assertThat(collectJob.isCompleted).isTrue()
616     }
617 
618     @Test
<lambda>null619     fun emptyFlow() = runTest {
620         val choreographer = createChoreographer(this)
621         val output = mutableListOf<List<Int>>()
622 
623         val collectJob =
624             backgroundScope.launch {
625                 emptyFlow<Int>().slidingWindow(100.milliseconds).toList(output)
626             }
627 
628         choreographer.advanceAndRun(0)
629         assertThat(output).isEmpty()
630 
631         assertThat(collectJob.isCompleted).isTrue()
632     }
633 
createChoreographernull634     private fun createChoreographer(testScope: TestScope) =
635         object {
636             val fakeClock = FakeSystemClock()
637 
638             fun advanceAndRun(millis: Long) {
639                 fakeClock.advanceTime(millis)
640                 testScope.advanceTimeBy(millis)
641                 testScope.runCurrent()
642             }
643         }
644 }
645 
assertThatFlownull646 private fun <T> assertThatFlow(flow: Flow<T>) =
647     object {
648         suspend fun emitsExactly(vararg emissions: T) =
649             assertThat(flow.toList()).containsExactly(*emissions).inOrder()
650 
651         suspend fun emitsNothing() = assertThat(flow.toList()).isEmpty()
652     }
653 
assertThatJobnull654 private fun assertThatJob(job: Job) =
655     object {
656         fun isCompleted() = assertThat(job.isCompleted).isTrue()
657     }
658