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  }