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  }