github.com/songzhibin97/go-baseutils@v0.0.2-0.20240302024150-487d8ce9c082/app/bcache/bcache_test.go (about)

     1  package bcache
     2  
     3  import (
     4  	"runtime"
     5  	"strconv"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/songzhibin97/go-baseutils/base/bcomparator"
    11  )
    12  
    13  type TestStruct struct {
    14  	Num      int
    15  	Children []*TestStruct
    16  }
    17  
    18  func TestCache(t *testing.T) {
    19  	tc := New[string, any](bcomparator.StringComparator())
    20  
    21  	a, found := tc.Get("a")
    22  	if found || a != nil {
    23  		t.Error("a exist:", a)
    24  	}
    25  
    26  	b, found := tc.Get("b")
    27  	if found || b != nil {
    28  		t.Error("b exist:", b)
    29  	}
    30  
    31  	c, found := tc.Get("c")
    32  	if found || c != nil {
    33  		t.Error("c exist::", c)
    34  	}
    35  
    36  	tc.Set("a", 1, DefaultExpire)
    37  	tc.Set("b", "b", DefaultExpire)
    38  	tc.Set("c", 3.5, DefaultExpire)
    39  
    40  	v, found := tc.Get("a")
    41  	if !found {
    42  		t.Error("a not exist")
    43  	}
    44  	if v == nil {
    45  		t.Error("a == nil")
    46  	} else if vv := v.(int); vv+2 != 3 {
    47  		t.Error("vv != 3", vv)
    48  	}
    49  
    50  	v, found = tc.Get("b")
    51  	if !found {
    52  		t.Error("b not exist")
    53  	}
    54  	if v == nil {
    55  		t.Error("b == nil")
    56  	} else if vv := v.(string); vv+"B" != "bB" {
    57  		t.Error("bb != bB:", vv)
    58  	}
    59  
    60  	v, found = tc.Get("c")
    61  	if !found {
    62  		t.Error("c not exist")
    63  	}
    64  	if v == nil {
    65  		t.Error("x for c is nil")
    66  	} else if vv := v.(float64); vv+1.2 != 4.7 {
    67  		t.Error("vv != 4,7:", vv)
    68  	}
    69  }
    70  
    71  func TestCacheTimes(t *testing.T) {
    72  	var found bool
    73  	tc := New[string, int](bcomparator.StringComparator(), SetDefaultExpire[string, int](50*time.Millisecond), SetInternal[string, int](time.Millisecond))
    74  	tc.Set("a", 1, DefaultExpire)
    75  	tc.Set("b", 2, NoExpire)
    76  	tc.Set("c", 3, 20*time.Millisecond)
    77  	tc.Set("d", 4, 70*time.Millisecond)
    78  
    79  	<-time.After(25 * time.Millisecond)
    80  	_, found = tc.Get("c")
    81  	if found {
    82  		t.Error("Found c when it should have been automatically deleted")
    83  	}
    84  
    85  	<-time.After(30 * time.Millisecond)
    86  	_, found = tc.Get("a")
    87  	if found {
    88  		t.Error("Found a when it should have been automatically deleted")
    89  	}
    90  
    91  	_, found = tc.Get("b")
    92  	if !found {
    93  		t.Error("Did not find b even though it was set to never expire")
    94  	}
    95  
    96  	_, found = tc.Get("d")
    97  	if !found {
    98  		t.Error("Did not find d even though it was set to expire later than the default")
    99  	}
   100  
   101  	<-time.After(20 * time.Millisecond)
   102  	_, found = tc.Get("d")
   103  	if found {
   104  		t.Error("Found d when it should have been automatically deleted (later than the default)")
   105  	}
   106  }
   107  
   108  func TestStorePointerToStruct(t *testing.T) {
   109  	tc := New[string, any](bcomparator.StringComparator())
   110  	tc.Set("foo", &TestStruct{Num: 1}, DefaultExpire)
   111  	x, found := tc.Get("foo")
   112  	if !found {
   113  		t.Fatal("*TestStruct was not found for foo")
   114  	}
   115  	foo := x.(*TestStruct)
   116  	foo.Num++
   117  
   118  	y, found := tc.Get("foo")
   119  	if !found {
   120  		t.Fatal("*TestStruct was not found for foo (second time)")
   121  	}
   122  	bar := y.(*TestStruct)
   123  	if bar.Num != 2 {
   124  		t.Fatal("TestStruct.Num is not 2")
   125  	}
   126  }
   127  
   128  func TestAdd(t *testing.T) {
   129  	tc := New[string, string](bcomparator.StringComparator())
   130  	ok := tc.SetIfAbsent("foo", "bar", DefaultExpire)
   131  	if !ok {
   132  		t.Error("Couldn't add foo even though it shouldn't exist")
   133  	}
   134  	ok = tc.SetIfAbsent("foo", "baz", DefaultExpire)
   135  	if ok {
   136  		t.Error("Successfully added another foo when it should have returned an error")
   137  	}
   138  }
   139  
   140  func TestReplace(t *testing.T) {
   141  	tc := New[string, string](bcomparator.StringComparator())
   142  	ok := tc.Replace("foo", "bar", DefaultExpire)
   143  	if ok {
   144  		t.Error("Replaced foo when it shouldn't exist")
   145  	}
   146  	tc.Set("foo", "bar", DefaultExpire)
   147  	ok = tc.Replace("foo", "bar", DefaultExpire)
   148  	if !ok {
   149  		t.Error("Couldn't replace existing key foo")
   150  	}
   151  }
   152  
   153  func TestDelete(t *testing.T) {
   154  	tc := New[string, string](bcomparator.StringComparator())
   155  	tc.Set("foo", "bar", DefaultExpire)
   156  	tc.Delete("foo")
   157  	x, found := tc.Get("foo")
   158  	if found {
   159  		t.Error("foo was found, but it should have been deleted")
   160  	}
   161  	if x != "" {
   162  		t.Error("x is not nil:", x)
   163  	}
   164  }
   165  
   166  func TestItemCount(t *testing.T) {
   167  	tc := New[string, string](bcomparator.StringComparator())
   168  	tc.Set("foo", "1", DefaultExpire)
   169  	tc.Set("bar", "2", DefaultExpire)
   170  	tc.Set("baz", "3", DefaultExpire)
   171  	if n := tc.Count(); n != 3 {
   172  		t.Errorf("Item count is not 3: %d", n)
   173  	}
   174  }
   175  
   176  func TestFlush(t *testing.T) {
   177  	tc := New[string, string](bcomparator.StringComparator())
   178  	tc.Set("foo", "bar", DefaultExpire)
   179  	tc.Set("baz", "yes", DefaultExpire)
   180  	tc.Clear()
   181  	x, found := tc.Get("foo")
   182  	if found {
   183  		t.Error("foo was found, but it should have been deleted")
   184  	}
   185  	if x != "" {
   186  		t.Error("x is not nil:", x)
   187  	}
   188  	x, found = tc.Get("baz")
   189  	if found {
   190  		t.Error("baz was found, but it should have been deleted")
   191  	}
   192  	if x != "" {
   193  		t.Error("x is not nil:", x)
   194  	}
   195  }
   196  
   197  func TestCacheSerialization(t *testing.T) {
   198  	tc := New[string, any](bcomparator.StringComparator())
   199  	testFillAndSerialize(t, tc)
   200  
   201  	// Check if gob.Register behaves properly even after multiple gob.Register
   202  	// on c.Items (many of which will be the same type)
   203  	testFillAndSerialize(t, tc)
   204  }
   205  
   206  func testFillAndSerialize(t *testing.T, tc *BCache[string, any]) {
   207  	tc.Set("a", "a", DefaultExpire)
   208  	tc.Set("b", "b", DefaultExpire)
   209  	tc.Set("c", "c", DefaultExpire)
   210  	tc.Set("expired", "foo", 1*time.Millisecond)
   211  	tc.Set("*struct", &TestStruct{Num: 1}, DefaultExpire)
   212  	tc.Set("[]struct", []TestStruct{
   213  		{Num: 2},
   214  		{Num: 3},
   215  	}, DefaultExpire)
   216  	tc.Set("[]*struct", []*TestStruct{
   217  		{Num: 4},
   218  		{Num: 5},
   219  	}, DefaultExpire)
   220  	tc.Set("structuration", &TestStruct{
   221  		Num: 42,
   222  		Children: []*TestStruct{
   223  			{Num: 6174},
   224  			{Num: 4716},
   225  		},
   226  	}, DefaultExpire)
   227  
   228  	data, err := tc.Marshal()
   229  	if err != nil {
   230  		t.Fatal("Couldn't save cache to fp:", err)
   231  	}
   232  
   233  	oc := New[string, any](bcomparator.StringComparator())
   234  	err = oc.Load(data)
   235  	if err != nil {
   236  		t.Fatal("Couldn't load cache from fp:", err)
   237  	}
   238  
   239  	a, found := oc.Get("a")
   240  	if !found {
   241  		t.Error("a was not found")
   242  	}
   243  	if a.(string) != "a" {
   244  		t.Error("a is not a")
   245  	}
   246  
   247  	b, found := oc.Get("b")
   248  	if !found {
   249  		t.Error("b was not found")
   250  	}
   251  	if b.(string) != "b" {
   252  		t.Error("b is not b")
   253  	}
   254  
   255  	c, found := oc.Get("c")
   256  	if !found {
   257  		t.Error("c was not found")
   258  	}
   259  	if c.(string) != "c" {
   260  		t.Error("c is not c")
   261  	}
   262  
   263  	<-time.After(5 * time.Millisecond)
   264  	_, found = oc.Get("expired")
   265  	if found {
   266  		t.Error("expired was found")
   267  	}
   268  
   269  	s1, found := oc.Get("*struct")
   270  	if !found {
   271  		t.Error("*struct was not found")
   272  	}
   273  	if s1.(map[string]interface{})["Num"].(float64) != 1 {
   274  		t.Error("*struct.Num is not 1")
   275  	}
   276  
   277  	s2, found := oc.Get("[]struct")
   278  	if !found {
   279  		t.Error("[]struct was not found")
   280  	}
   281  	s2r := s2.([]interface{})
   282  	if len(s2r) != 2 {
   283  		t.Error("Length of s2r is not 2")
   284  	}
   285  	if s2r[0].(map[string]interface{})["Num"].(float64) != 2 {
   286  		t.Error("s2r[0].Num is not 2")
   287  	}
   288  	if s2r[1].(map[string]interface{})["Num"].(float64) != 3 {
   289  		t.Error("s2r[1].Num is not 3")
   290  	}
   291  
   292  	s3, found := oc.Get("[]*struct")
   293  	if !found {
   294  		t.Error("[]*struct was not found")
   295  	}
   296  	s3r := s3.([]interface{})
   297  	if len(s3r) != 2 {
   298  		t.Error("Length of s3r is not 2")
   299  	}
   300  	if s3r[0].(map[string]interface{})["Num"].(float64) != 4 {
   301  		t.Error("s3r[0].Num is not 4")
   302  	}
   303  	if s3r[1].(map[string]interface{})["Num"].(float64) != 5 {
   304  		t.Error("s3r[1].Num is not 5")
   305  	}
   306  
   307  	s4, found := oc.Get("structuration")
   308  	if !found {
   309  		t.Error("structuration was not found")
   310  	}
   311  	s4r := s4.(map[string]interface{})
   312  	if len(s4r["Children"].([]interface{})) != 2 {
   313  		t.Error("Length of s4r.Children is not 2")
   314  	}
   315  
   316  	if s4r["Children"].([]interface{})[0].(map[string]interface{})["Num"].(float64) != 6174 {
   317  		t.Error("s4r.Children[0].Num is not 6174")
   318  	}
   319  	if s4r["Children"].([]interface{})[1].(map[string]interface{})["Num"].(float64) != 4716 {
   320  		t.Error("s4r.Children[1].Num is not 4716")
   321  	}
   322  }
   323  
   324  func BenchmarkCacheGetExpiring(b *testing.B) {
   325  	benchmarkCacheGet(b)
   326  }
   327  
   328  func BenchmarkCacheGetNotExpiring(b *testing.B) {
   329  	benchmarkCacheGet(b)
   330  }
   331  
   332  func benchmarkCacheGet(b *testing.B) {
   333  	b.StopTimer()
   334  	tc := New[string, string](bcomparator.StringComparator())
   335  	tc.Set("foo", "bar", DefaultExpire)
   336  	b.StartTimer()
   337  	for i := 0; i < b.N; i++ {
   338  		tc.Get("foo")
   339  	}
   340  }
   341  
   342  func BenchmarkRWMutexMapGet(b *testing.B) {
   343  	b.StopTimer()
   344  	m := map[string]string{
   345  		"foo": "bar",
   346  	}
   347  	mu := sync.RWMutex{}
   348  	b.StartTimer()
   349  	for i := 0; i < b.N; i++ {
   350  		mu.RLock()
   351  		_, _ = m["foo"]
   352  		mu.RUnlock()
   353  	}
   354  }
   355  
   356  func BenchmarkRWMutexInterfaceMapGetStruct(b *testing.B) {
   357  	b.StopTimer()
   358  	s := struct{ name string }{name: "foo"}
   359  	m := map[interface{}]string{
   360  		s: "bar",
   361  	}
   362  	mu := sync.RWMutex{}
   363  	b.StartTimer()
   364  	for i := 0; i < b.N; i++ {
   365  		mu.RLock()
   366  		_, _ = m[s]
   367  		mu.RUnlock()
   368  	}
   369  }
   370  
   371  func BenchmarkRWMutexInterfaceMapGetString(b *testing.B) {
   372  	b.StopTimer()
   373  	m := map[interface{}]string{
   374  		"foo": "bar",
   375  	}
   376  	mu := sync.RWMutex{}
   377  	b.StartTimer()
   378  	for i := 0; i < b.N; i++ {
   379  		mu.RLock()
   380  		_, _ = m["foo"]
   381  		mu.RUnlock()
   382  	}
   383  }
   384  
   385  func BenchmarkCacheGetConcurrentExpiring(b *testing.B) {
   386  	benchmarkCacheGetConcurrent(b)
   387  }
   388  
   389  func BenchmarkCacheGetConcurrentNotExpiring(b *testing.B) {
   390  	benchmarkCacheGetConcurrent(b)
   391  }
   392  
   393  func benchmarkCacheGetConcurrent(b *testing.B) {
   394  	b.StopTimer()
   395  	tc := New[string, string](bcomparator.StringComparator())
   396  	tc.Set("foo", "bar", DefaultExpire)
   397  	wg := new(sync.WaitGroup)
   398  	workers := runtime.NumCPU()
   399  	each := b.N / workers
   400  	wg.Add(workers)
   401  	b.StartTimer()
   402  	for i := 0; i < workers; i++ {
   403  		go func() {
   404  			for j := 0; j < each; j++ {
   405  				tc.Get("foo")
   406  			}
   407  			wg.Done()
   408  		}()
   409  	}
   410  	wg.Wait()
   411  }
   412  
   413  func BenchmarkRWMutexMapGetConcurrent(b *testing.B) {
   414  	b.StopTimer()
   415  	m := map[string]string{
   416  		"foo": "bar",
   417  	}
   418  	mu := sync.RWMutex{}
   419  	wg := new(sync.WaitGroup)
   420  	workers := runtime.NumCPU()
   421  	each := b.N / workers
   422  	wg.Add(workers)
   423  	b.StartTimer()
   424  	for i := 0; i < workers; i++ {
   425  		go func() {
   426  			for j := 0; j < each; j++ {
   427  				mu.RLock()
   428  				_, _ = m["foo"]
   429  				mu.RUnlock()
   430  			}
   431  			wg.Done()
   432  		}()
   433  	}
   434  	wg.Wait()
   435  }
   436  
   437  func BenchmarkCacheGetManyConcurrentExpiring(b *testing.B) {
   438  	benchmarkCacheGetManyConcurrent(b)
   439  }
   440  
   441  func BenchmarkCacheGetManyConcurrentNotExpiring(b *testing.B) {
   442  	benchmarkCacheGetManyConcurrent(b)
   443  }
   444  
   445  func benchmarkCacheGetManyConcurrent(b *testing.B) {
   446  	// This is the same as BenchmarkCacheGetConcurrent, but its result
   447  	// can be compared against BenchmarkShardedCacheGetManyConcurrent
   448  	// in sharded_test.go.
   449  	b.StopTimer()
   450  	n := 10000
   451  	tc := New[string, string](bcomparator.StringComparator())
   452  	keys := make([]string, n)
   453  	for i := 0; i < n; i++ {
   454  		k := "foo" + strconv.Itoa(i)
   455  		keys[i] = k
   456  		tc.Set(k, "bar", DefaultExpire)
   457  	}
   458  	each := b.N / n
   459  	wg := new(sync.WaitGroup)
   460  	wg.Add(n)
   461  	for _, v := range keys {
   462  		go func(k string) {
   463  			for j := 0; j < each; j++ {
   464  				tc.Get(k)
   465  			}
   466  			wg.Done()
   467  		}(v)
   468  	}
   469  	b.StartTimer()
   470  	wg.Wait()
   471  }
   472  
   473  func BenchmarkCacheSetExpiring(b *testing.B) {
   474  	benchmarkCacheSet(b)
   475  }
   476  
   477  func BenchmarkCacheSetNotExpiring(b *testing.B) {
   478  	benchmarkCacheSet(b)
   479  }
   480  
   481  func benchmarkCacheSet(b *testing.B) {
   482  	b.StopTimer()
   483  	tc := New[string, string](bcomparator.StringComparator())
   484  	b.StartTimer()
   485  	for i := 0; i < b.N; i++ {
   486  		tc.Set("foo", "bar", DefaultExpire)
   487  	}
   488  }
   489  
   490  func BenchmarkRWMutexMapSet(b *testing.B) {
   491  	b.StopTimer()
   492  	m := map[string]string{}
   493  	mu := sync.RWMutex{}
   494  	b.StartTimer()
   495  	for i := 0; i < b.N; i++ {
   496  		mu.Lock()
   497  		m["foo"] = "bar"
   498  		mu.Unlock()
   499  	}
   500  }
   501  
   502  func BenchmarkCacheSetDelete(b *testing.B) {
   503  	b.StopTimer()
   504  	tc := New[string, string](bcomparator.StringComparator(), SetCapture[string, string](nil))
   505  	b.StartTimer()
   506  	for i := 0; i < b.N; i++ {
   507  		tc.Set("foo", "bar", DefaultExpire)
   508  		tc.Delete("foo")
   509  	}
   510  }
   511  
   512  func BenchmarkRWMutexMapSetDelete(b *testing.B) {
   513  	b.StopTimer()
   514  	m := map[string]string{}
   515  	mu := sync.RWMutex{}
   516  	b.StartTimer()
   517  	for i := 0; i < b.N; i++ {
   518  		mu.Lock()
   519  		m["foo"] = "bar"
   520  		mu.Unlock()
   521  		mu.Lock()
   522  		delete(m, "foo")
   523  		mu.Unlock()
   524  	}
   525  }
   526  
   527  func BenchmarkCacheSetDeleteSingleLock(b *testing.B) {
   528  	b.StopTimer()
   529  	tc := New[string, string](bcomparator.StringComparator())
   530  	b.StartTimer()
   531  	for i := 0; i < b.N; i++ {
   532  		tc.Lock()
   533  		tc.set("foo", "bar", DefaultExpire)
   534  		tc.delete("foo")
   535  		tc.Unlock()
   536  	}
   537  }
   538  
   539  func BenchmarkRWMutexMapSetDeleteSingleLock(b *testing.B) {
   540  	b.StopTimer()
   541  	m := map[string]string{}
   542  	mu := sync.RWMutex{}
   543  	b.StartTimer()
   544  	for i := 0; i < b.N; i++ {
   545  		mu.Lock()
   546  		m["foo"] = "bar"
   547  		delete(m, "foo")
   548  		mu.Unlock()
   549  	}
   550  }
   551  
   552  func BenchmarkDeleteExpiredLoop(b *testing.B) {
   553  	b.StopTimer()
   554  	tc := New[string, string](bcomparator.StringComparator(), SetDefaultExpire[string, string](5*time.Minute), SetCapture[string, string](nil))
   555  	for i := 0; i < 100000; i++ {
   556  		tc.set(strconv.Itoa(i), "bar", DefaultExpire)
   557  	}
   558  	b.StartTimer()
   559  	for i := 0; i < b.N; i++ {
   560  		tc.deleteExpire()
   561  	}
   562  }
   563  
   564  func TestGetWithExpiration(t *testing.T) {
   565  	tc := New[string, any](bcomparator.StringComparator())
   566  
   567  	a, expiration, found := tc.GetWithExpire("a")
   568  	if found || a != nil || !expiration.IsZero() {
   569  		t.Error("Getting A found value that shouldn't exist:", a)
   570  	}
   571  
   572  	b, expiration, found := tc.GetWithExpire("b")
   573  	if found || b != nil || !expiration.IsZero() {
   574  		t.Error("Getting B found value that shouldn't exist:", b)
   575  	}
   576  
   577  	c, expiration, found := tc.GetWithExpire("c")
   578  	if found || c != nil || !expiration.IsZero() {
   579  		t.Error("Getting C found value that shouldn't exist:", c)
   580  	}
   581  
   582  	tc.Set("a", 1, DefaultExpire)
   583  	tc.Set("b", "b", DefaultExpire)
   584  	tc.Set("c", 3.5, DefaultExpire)
   585  	tc.Set("d", 1, NoExpire)
   586  	tc.Set("e", 1, 50*time.Millisecond)
   587  
   588  	x, expiration, found := tc.GetWithExpire("a")
   589  	if !found {
   590  		t.Error("a was not found while getting a2")
   591  	}
   592  	if x == nil {
   593  		t.Error("x for a is nil")
   594  	} else if a2 := x.(int); a2+2 != 3 {
   595  		t.Error("a2 (which should be 1) plus 2 does not equal 3; value:", a2)
   596  	}
   597  	if !expiration.IsZero() {
   598  		t.Error("expiration for a is not a zeroed time")
   599  	}
   600  
   601  	x, expiration, found = tc.GetWithExpire("b")
   602  	if !found {
   603  		t.Error("b was not found while getting b2")
   604  	}
   605  	if x == nil {
   606  		t.Error("x for b is nil")
   607  	} else if b2 := x.(string); b2+"B" != "bB" {
   608  		t.Error("b2 (which should be b) plus B does not equal bB; value:", b2)
   609  	}
   610  	if !expiration.IsZero() {
   611  		t.Error("expiration for b is not a zeroed time")
   612  	}
   613  
   614  	x, expiration, found = tc.GetWithExpire("c")
   615  	if !found {
   616  		t.Error("c was not found while getting c2")
   617  	}
   618  	if x == nil {
   619  		t.Error("x for c is nil")
   620  	} else if c2 := x.(float64); c2+1.2 != 4.7 {
   621  		t.Error("c2 (which should be 3.5) plus 1.2 does not equal 4.7; value:", c2)
   622  	}
   623  	if !expiration.IsZero() {
   624  		t.Error("expiration for c is not a zeroed time")
   625  	}
   626  
   627  	x, expiration, found = tc.GetWithExpire("d")
   628  	if !found {
   629  		t.Error("d was not found while getting d2")
   630  	}
   631  	if x == nil {
   632  		t.Error("x for d is nil")
   633  	} else if d2 := x.(int); d2+2 != 3 {
   634  		t.Error("d (which should be 1) plus 2 does not equal 3; value:", d2)
   635  	}
   636  	if !expiration.IsZero() {
   637  		t.Error("expiration for d is not a zeroed time")
   638  	}
   639  
   640  	x, expiration, found = tc.GetWithExpire("e")
   641  	if !found {
   642  		t.Error("e was not found while getting e2")
   643  	}
   644  	if x == nil {
   645  		t.Error("x for e is nil")
   646  	} else if e2 := x.(int); e2+2 != 3 {
   647  		t.Error("e (which should be 1) plus 2 does not equal 3; value:", e2)
   648  	}
   649  	v, _ := tc.member.Get("e")
   650  	if expiration.UnixNano() != v.Expire {
   651  		t.Error("expiration for e is not the correct time")
   652  	}
   653  	if expiration.UnixNano() < time.Now().UnixNano() {
   654  		t.Error("expiration for e is in the past")
   655  	}
   656  }