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