1 /*
<lambda>null2  * Copyright 2024 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 androidx.compose.runtime.snapshots
18 
19 import androidx.compose.runtime.mutableStateSetOf
20 import kotlin.test.Test
21 import kotlin.test.assertEquals
22 import kotlin.test.assertFailsWith
23 import kotlin.test.assertFalse
24 import kotlin.test.assertTrue
25 import kotlin.time.Duration.Companion.seconds
26 import kotlinx.coroutines.Dispatchers
27 import kotlinx.coroutines.ExperimentalCoroutinesApi
28 import kotlinx.coroutines.channels.Channel
29 import kotlinx.coroutines.channels.consumeEach
30 import kotlinx.coroutines.coroutineScope
31 import kotlinx.coroutines.launch
32 import kotlinx.coroutines.test.runTest
33 import kotlinx.test.IgnoreJsTarget
34 
35 class SnapshotStateSetTests {
36     @Test
37     fun canCreateAStateSet() {
38         mutableStateSetOf<Any>()
39     }
40 
41     @Test
42     fun canCreateAStateSetOfInts() {
43         val set = mutableStateSetOf(0, 1, 2, 3, 4)
44         set.forEachIndexed { index, item -> assertEquals(index, item) }
45     }
46 
47     @Test
48     fun validateSize() {
49         val set = mutableStateSetOf(0, 1, 2, 3)
50         assertEquals(4, set.size)
51     }
52 
53     @Test
54     fun validateContains() {
55         val set = mutableStateSetOf(0, 1, 2, 3, 4)
56         (0..4).forEach { assertTrue(set.contains(it)) }
57         assertFalse(set.contains(5))
58     }
59 
60     @Test
61     fun validateContainsAll() {
62         val set = mutableStateSetOf(0, 1, 2, 3, 4)
63         assertTrue(set.containsAll(setOf(1, 2, 3)))
64         assertFalse(set.containsAll(setOf(0, 2, 3, 5)))
65     }
66 
67     @Test
68     fun validateIsEmpty() {
69         val set = mutableStateSetOf(0, 1, 2)
70         assertFalse(set.isEmpty())
71         val emptySet = mutableStateSetOf<Any>()
72         assertTrue(emptySet.isEmpty())
73     }
74 
75     @Test
76     fun validateIterator() {
77         val set = mutableStateSetOf(0, 1, 2, 3, 4)
78         var expected = 0
79         for (item in set) {
80             assertEquals(expected++, item)
81         }
82         assertEquals(5, expected)
83     }
84 
85     @Test
86     fun validateIterator_remove() {
87         assertFailsWith(IllegalStateException::class) {
88             validate(mutableStateSetOf(0, 1, 2, 3, 4)) { normalSet ->
89                 val iterator = normalSet.iterator()
90                 iterator.next()
91                 iterator.next()
92                 iterator.remove()
93                 iterator.remove()
94                 iterator.next()
95                 iterator.remove()
96             }
97         }
98     }
99 
100     @Test
101     fun canRemoveFromAStateSet() {
102         val set = mutableStateSetOf(0, 1, 2)
103         set.remove(1)
104         assertEquals(2, set.size)
105         assertTrue(set.contains(0))
106         assertTrue(set.contains(2))
107     }
108 
109     @Test
110     fun canRemoveAllFromAStateSet() {
111         val set = mutableStateSetOf(0, 1, 2, 3, 4, 5)
112         val normalSet = mutableSetOf(0, 1, 2, 3, 4, 5)
113         set.removeAll(setOf(2, 4))
114         normalSet.removeAll(setOf(2, 4))
115         expected(normalSet, set)
116     }
117 
118     @Test
119     fun canRetainAllOfAStateSet() {
120         val set = mutableStateSetOf(0, 1, 2, 3, 4, 5, 6)
121         val normalSet = mutableSetOf(0, 1, 2, 3, 4, 5, 6)
122         set.retainAll(setOf(2, 4, 6, 8))
123         normalSet.retainAll(setOf(2, 4, 6, 8))
124         expected(normalSet, set)
125     }
126 
127     @Test
128     fun stateSetsCanBeSnapshot() {
129         val original = setOf(0, 1, 2, 3, 4, 5, 6)
130         val mutableSet = original.toMutableSet()
131         val set = mutableStateSetOf(0, 1, 2, 3, 4, 5, 6)
132         val snapshot = Snapshot.takeSnapshot()
133         try {
134             set.remove(0)
135             mutableSet.remove(0)
136             expected(mutableSet, set)
137             snapshot.enter { expected(original, set) }
138         } finally {
139             snapshot.dispose()
140         }
141         expected(mutableSet, set)
142     }
143 
144     @Test
145     @ExperimentalCoroutinesApi
146     fun concurrentGlobalModification_add() = runTest {
147         repeat(100) {
148             val set = mutableStateSetOf<Int>()
149             coroutineScope {
150                 repeat(100) { index -> launch(Dispatchers.Default) { set.add(index) } }
151             }
152 
153             repeat(100) { assertTrue(set.contains(it)) }
154         }
155     }
156 
157     @Test
158     @ExperimentalCoroutinesApi
159     fun concurrentGlobalModification_remove() = runTest {
160         repeat(100) {
161             val set = mutableStateSetOf<Int>()
162             repeat(100) { index -> set.add(index) }
163 
164             coroutineScope {
165                 repeat(100) { index -> launch(Dispatchers.Default) { set.remove(index) } }
166             }
167 
168             repeat(100) { assertFalse(set.contains(it)) }
169         }
170     }
171 
172     @Test
173     @IgnoreJsTarget // Not relevant in a single threaded environment
174     fun concurrentMixingWriteApply_add() =
175         runTest(timeout = 30.seconds) {
176             repeat(10) {
177                 val sets = Array(100) { mutableStateSetOf<Int>() }.toList()
178                 val channel = Channel<Unit>(Channel.CONFLATED)
179                 coroutineScope {
180                     // Launch mutator
181                     launch(Dispatchers.Default) {
182                         repeat(100) { index ->
183                             sets.fastForEach { set -> set.add(index) }
184 
185                             // Simulate the write observer
186                             channel.trySend(Unit)
187                         }
188                         channel.close()
189                     }
190 
191                     // Simulate the global snapshot manager
192                     launch(Dispatchers.Default) {
193                         channel.consumeEach { Snapshot.notifyObjectsInitialized() }
194                     }
195                 }
196             }
197             // Should only get here if the above doesn't deadlock.
198         }
199 
200     @Test
201     fun testWritingANewValueDoesObserveChange() {
202         val state = mutableStateSetOf(0, 1, 2)
203         val modified = observeGlobalChanges { repeat(4) { state.add(it) } }
204         assertTrue(modified.size == 1)
205         assertEquals(modified.first(), state)
206     }
207 
208     @Test
209     fun testWritingTheSameValueDoesNotChangeTheSet() {
210         val state = mutableStateSetOf(0, 1, 2, 3)
211         val modified = observeGlobalChanges { repeat(4) { state.add(it) } }
212         assertTrue(modified.isEmpty())
213     }
214 
215     @Test
216     fun toStringOfSnapshotStateSetDoesNotTriggerReadObserver() {
217         val state = mutableStateSetOf(0)
218         val normalReads = readsOf { state.readable }
219         assertEquals(1, normalReads)
220         val toStringReads = readsOf { state.toString() }
221         assertEquals(0, toStringReads)
222     }
223 
224     @Test
225     fun testValueOfStateSetToString() {
226         val state = mutableStateSetOf(0, 1, 2)
227         assertEquals("SnapshotStateSet(value=[0, 1, 2])@${state.hashCode()}", state.toString())
228     }
229 
230     private fun <T> validate(set: MutableSet<T>, block: (set: MutableSet<T>) -> Unit) {
231         val normalSet = set.toMutableSet()
232         block(normalSet)
233         block(set)
234         expected(normalSet, set)
235     }
236 
237     private fun <T> expected(expected: Set<T>, actual: Set<T>) {
238         assertEquals(expected.size, actual.size)
239         assertEquals(expected.subtract(actual), emptySet())
240     }
241 
242     private fun observeGlobalChanges(block: () -> Unit): Set<Any> {
243         val result = mutableSetOf<Any>()
244         val handle = Snapshot.registerApplyObserver { set, _ -> result.addAll(set) }
245         try {
246             block()
247         } finally {
248             Snapshot.sendApplyNotifications()
249             handle.dispose()
250         }
251         return result
252     }
253 }
254