github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/collection/skipset/skipset_test.go (about) 1 // Copyright 2021 ByteDance Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package skipset 16 17 import ( 18 "fmt" 19 "math" 20 "sort" 21 "strconv" 22 "sync" 23 "sync/atomic" 24 "testing" 25 26 "github.com/bytedance/gopkg/lang/fastrand" 27 ) 28 29 func Example() { 30 l := NewInt() 31 32 for _, v := range []int{10, 12, 15} { 33 if l.Add(v) { 34 fmt.Println("skipset add", v) 35 } 36 } 37 38 if l.Contains(10) { 39 fmt.Println("skipset contains 10") 40 } 41 42 l.Range(func(value int) bool { 43 fmt.Println("skipset range found ", value) 44 return true 45 }) 46 47 l.Remove(15) 48 fmt.Printf("skipset contains %d items\r\n", l.Len()) 49 } 50 51 func TestIntSet(t *testing.T) { 52 // Correctness. 53 l := NewInt() 54 if l.length != 0 { 55 t.Fatal("invalid length") 56 } 57 if l.Contains(0) { 58 t.Fatal("invalid contains") 59 } 60 61 if !l.Add(0) || l.length != 1 { 62 t.Fatal("invalid add") 63 } 64 if !l.Contains(0) { 65 t.Fatal("invalid contains") 66 } 67 if !l.Remove(0) || l.length != 0 { 68 t.Fatal("invalid remove") 69 } 70 71 if !l.Add(20) || l.length != 1 { 72 t.Fatal("invalid add") 73 } 74 if !l.Add(22) || l.length != 2 { 75 t.Fatal("invalid add") 76 } 77 if !l.Add(21) || l.length != 3 { 78 t.Fatal("invalid add") 79 } 80 81 var i int 82 l.Range(func(score int) bool { 83 if i == 0 && score != 20 { 84 t.Fatal("invalid range") 85 } 86 if i == 1 && score != 21 { 87 t.Fatal("invalid range") 88 } 89 if i == 2 && score != 22 { 90 t.Fatal("invalid range") 91 } 92 i++ 93 return true 94 }) 95 96 if !l.Remove(21) || l.length != 2 { 97 t.Fatal("invalid remove") 98 } 99 100 i = 0 101 l.Range(func(score int) bool { 102 if i == 0 && score != 20 { 103 t.Fatal("invalid range") 104 } 105 if i == 1 && score != 22 { 106 t.Fatal("invalid range") 107 } 108 i++ 109 return true 110 }) 111 112 const num = math.MaxInt16 113 // Make rand shuffle array. 114 // The testArray contains [1,num] 115 testArray := make([]int, num) 116 testArray[0] = num + 1 117 for i := 1; i < num; i++ { 118 // We left 0, because it is the default score for head and tail. 119 // If we check the skipset contains 0, there must be something wrong. 120 testArray[i] = int(i) 121 } 122 for i := len(testArray) - 1; i > 0; i-- { // Fisher–Yates shuffle 123 j := fastrand.Uint32n(uint32(i + 1)) 124 testArray[i], testArray[j] = testArray[j], testArray[i] 125 } 126 127 // Concurrent add. 128 var wg sync.WaitGroup 129 for i := 0; i < num; i++ { 130 i := i 131 wg.Add(1) 132 go func() { 133 l.Add(testArray[i]) 134 wg.Done() 135 }() 136 } 137 wg.Wait() 138 if l.length != int64(num) { 139 t.Fatalf("invalid length expected %d, got %d", num, l.length) 140 } 141 142 // Don't contains 0 after concurrent addion. 143 if l.Contains(0) { 144 t.Fatal("contains 0 after concurrent addion") 145 } 146 147 // Concurrent contains. 148 for i := 0; i < num; i++ { 149 i := i 150 wg.Add(1) 151 go func() { 152 if !l.Contains(testArray[i]) { 153 wg.Done() 154 panic(fmt.Sprintf("add doesn't contains %d", i)) 155 } 156 wg.Done() 157 }() 158 } 159 wg.Wait() 160 161 // Concurrent remove. 162 for i := 0; i < num; i++ { 163 i := i 164 wg.Add(1) 165 go func() { 166 if !l.Remove(testArray[i]) { 167 wg.Done() 168 panic(fmt.Sprintf("can't remove %d", i)) 169 } 170 wg.Done() 171 }() 172 } 173 wg.Wait() 174 if l.length != 0 { 175 t.Fatalf("invalid length expected %d, got %d", 0, l.length) 176 } 177 178 // Test all methods. 179 const smallRndN = 1 << 8 180 for i := 0; i < 1<<16; i++ { 181 wg.Add(1) 182 go func() { 183 r := fastrand.Uint32n(num) 184 if r < 333 { 185 l.Add(int(fastrand.Uint32n(smallRndN)) + 1) 186 } else if r < 666 { 187 l.Contains(int(fastrand.Uint32n(smallRndN)) + 1) 188 } else if r != 999 { 189 l.Remove(int(fastrand.Uint32n(smallRndN)) + 1) 190 } else { 191 var pre int 192 l.Range(func(score int) bool { 193 if score <= pre { // 0 is the default value for header and tail score 194 panic("invalid content") 195 } 196 pre = score 197 return true 198 }) 199 } 200 wg.Done() 201 }() 202 } 203 wg.Wait() 204 205 // Correctness 2. 206 var ( 207 x = NewInt() 208 y = NewInt() 209 count = 10000 210 ) 211 212 for i := 0; i < count; i++ { 213 x.Add(i) 214 } 215 216 for i := 0; i < 16; i++ { 217 wg.Add(1) 218 go func() { 219 x.Range(func(score int) bool { 220 if x.Remove(score) { 221 if !y.Add(score) { 222 panic("invalid add") 223 } 224 } 225 return true 226 }) 227 wg.Done() 228 }() 229 } 230 wg.Wait() 231 if x.Len() != 0 || y.Len() != count { 232 t.Fatal("invalid length") 233 } 234 235 // Concurrent Add and Remove in small zone. 236 x = NewInt() 237 var ( 238 addcount uint64 = 0 239 removecount uint64 = 0 240 ) 241 242 for i := 0; i < 16; i++ { 243 wg.Add(1) 244 go func() { 245 for i := 0; i < 1000; i++ { 246 if fastrand.Uint32n(2) == 0 { 247 if x.Remove(int(fastrand.Uint32n(10))) { 248 atomic.AddUint64(&removecount, 1) 249 } 250 } else { 251 if x.Add(int(fastrand.Uint32n(10))) { 252 atomic.AddUint64(&addcount, 1) 253 } 254 } 255 } 256 wg.Done() 257 }() 258 } 259 wg.Wait() 260 if addcount < removecount { 261 panic("invalid count") 262 } 263 if addcount-removecount != uint64(x.Len()) { 264 panic("invalid count") 265 } 266 267 pre := -1 268 x.Range(func(score int) bool { 269 if score <= pre { 270 panic("invalid content") 271 } 272 pre = score 273 return true 274 }) 275 } 276 277 func TestIntSetDesc(t *testing.T) { 278 s := NewIntDesc() 279 nums := []int{-1, 0, 5, 12} 280 for _, v := range nums { 281 s.Add(v) 282 } 283 i := len(nums) - 1 284 s.Range(func(value int) bool { 285 if nums[i] != value { 286 t.Fatal("error") 287 } 288 i-- 289 return true 290 }) 291 } 292 293 func TestStringSet(t *testing.T) { 294 x := NewString() 295 if !x.Add("111") || x.Len() != 1 { 296 t.Fatal("invalid") 297 } 298 if !x.Add("222") || x.Len() != 2 { 299 t.Fatal("invalid") 300 } 301 if x.Add("111") || x.Len() != 2 { 302 t.Fatal("invalid") 303 } 304 if !x.Contains("111") || !x.Contains("222") { 305 t.Fatal("invalid") 306 } 307 if !x.Remove("111") || x.Len() != 1 { 308 t.Fatal("invalid") 309 } 310 if !x.Remove("222") || x.Len() != 0 { 311 t.Fatal("invalid") 312 } 313 314 var wg sync.WaitGroup 315 for i := 0; i < 100; i++ { 316 wg.Add(1) 317 i := i 318 go func() { 319 if !x.Add(strconv.Itoa(i)) { 320 panic("invalid") 321 } 322 wg.Done() 323 }() 324 } 325 wg.Wait() 326 327 tmp := make([]int, 0, 100) 328 x.Range(func(val string) bool { 329 res, _ := strconv.Atoi(val) 330 tmp = append(tmp, res) 331 return true 332 }) 333 sort.Ints(tmp) 334 for i := 0; i < 100; i++ { 335 if i != tmp[i] { 336 t.Fatal("invalid") 337 } 338 } 339 }