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