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