github.com/maypok86/otter@v1.2.1/internal/hashtable/map_test.go (about)

     1  // Copyright (c) 2023 Alexey Mayshev. All rights reserved.
     2  // Copyright (c) 2021 Andrey Pechkurov
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  // Copyright notice. This code is a fork of tests for xsync.MapOf from this file with some changes:
    17  // https://github.com/puzpuzpuz/xsync/blob/main/mapof_test.go
    18  //
    19  // Use of this source code is governed by a MIT license that can be found
    20  // at https://github.com/puzpuzpuz/xsync/blob/main/LICENSE
    21  
    22  package hashtable
    23  
    24  import (
    25  	"math/rand"
    26  	"strconv"
    27  	"sync"
    28  	"sync/atomic"
    29  	"testing"
    30  	"time"
    31  	"unsafe"
    32  
    33  	"github.com/maypok86/otter/internal/generated/node"
    34  	"github.com/maypok86/otter/internal/xruntime"
    35  )
    36  
    37  func TestMap_PaddedBucketSize(t *testing.T) {
    38  	size := unsafe.Sizeof(paddedBucket{})
    39  	if size != xruntime.CacheLineSize {
    40  		t.Fatalf("size of 64B (one cache line) is expected, got: %d", size)
    41  	}
    42  }
    43  
    44  func TestMap_EmptyStringKey(t *testing.T) {
    45  	nm := node.NewManager[string, string](node.Config{})
    46  	m := New(nm)
    47  	m.Set(nm.Create("", "foobar", 0, 1))
    48  	n, ok := m.Get("")
    49  	if !ok {
    50  		t.Fatal("value was expected")
    51  	}
    52  	if n.Value() != "foobar" {
    53  		t.Fatalf("value does not match: %v", n.Value())
    54  	}
    55  }
    56  
    57  func TestMap_SetNilValue(t *testing.T) {
    58  	nm := node.NewManager[string, *struct{}](node.Config{})
    59  	m := New(nm)
    60  	m.Set(nm.Create("foo", nil, 0, 1))
    61  	n, ok := m.Get("foo")
    62  	if !ok {
    63  		t.Fatal("nil value was expected")
    64  	}
    65  	if n.Value() != nil {
    66  		t.Fatalf("value was not nil: %v", n.Value())
    67  	}
    68  }
    69  
    70  func TestMap_Set(t *testing.T) {
    71  	const numberOfNodes = 128
    72  	nm := node.NewManager[string, int](node.Config{})
    73  	m := New(nm)
    74  	for i := 0; i < numberOfNodes; i++ {
    75  		m.Set(nm.Create(strconv.Itoa(i), i, 0, 1))
    76  	}
    77  	for i := 0; i < numberOfNodes; i++ {
    78  		n, ok := m.Get(strconv.Itoa(i))
    79  		if !ok {
    80  			t.Fatalf("value not found for %d", i)
    81  		}
    82  		if n.Value() != i {
    83  			t.Fatalf("values do not match for %d: %v", i, n.Value())
    84  		}
    85  	}
    86  }
    87  
    88  func TestMap_SetIfAbsent(t *testing.T) {
    89  	const numberOfNodes = 128
    90  	nm := node.NewManager[string, int](node.Config{})
    91  	m := New(nm)
    92  	for i := 0; i < numberOfNodes; i++ {
    93  		res := m.SetIfAbsent(nm.Create(strconv.Itoa(i), i, 0, 1))
    94  		if res != nil {
    95  			t.Fatalf("set was dropped. got: %+v", res)
    96  		}
    97  	}
    98  	for i := 0; i < numberOfNodes; i++ {
    99  		n := nm.Create(strconv.Itoa(i), i, 0, 1)
   100  		res := m.SetIfAbsent(n)
   101  		if res == nil {
   102  			t.Fatalf("set was not dropped. node that was set: %+v", res)
   103  		}
   104  	}
   105  
   106  	for i := 0; i < numberOfNodes; i++ {
   107  		n, ok := m.Get(strconv.Itoa(i))
   108  		if !ok {
   109  			t.Fatalf("value not found for %d", i)
   110  		}
   111  		if n.Value() != i {
   112  			t.Fatalf("values do not match for %d: %v", i, n.Value())
   113  		}
   114  	}
   115  }
   116  
   117  // this code may break if the maphash.Hasher[k] structure changes.
   118  type hasher struct {
   119  	hash func(pointer unsafe.Pointer, seed uintptr) uintptr
   120  	seed uintptr
   121  }
   122  
   123  func TestMap_SetWithCollisions(t *testing.T) {
   124  	const numNodes = 1000
   125  	nm := node.NewManager[int, int](node.Config{})
   126  	m := NewWithSize(nm, numNodes)
   127  	table := (*table[int])(atomic.LoadPointer(&m.table))
   128  	hasher := (*hasher)((unsafe.Pointer)(&table.hasher))
   129  	hasher.hash = func(ptr unsafe.Pointer, seed uintptr) uintptr {
   130  		// We intentionally use an awful hash function here to make sure
   131  		// that the map copes with key collisions.
   132  		return 42
   133  	}
   134  	for i := 0; i < numNodes; i++ {
   135  		m.Set(nm.Create(i, i, 0, 1))
   136  	}
   137  	for i := 0; i < numNodes; i++ {
   138  		v, ok := m.Get(i)
   139  		if !ok {
   140  			t.Fatalf("value not found for %d", i)
   141  		}
   142  		if v.Value() != i {
   143  			t.Fatalf("values do not match for %d: %v", i, v)
   144  		}
   145  	}
   146  }
   147  
   148  func TestMap_SetThenDelete(t *testing.T) {
   149  	const numberOfNodes = 1000
   150  	nm := node.NewManager[string, int](node.Config{})
   151  	m := New(nm)
   152  	for i := 0; i < numberOfNodes; i++ {
   153  		m.Set(nm.Create(strconv.Itoa(i), i, 0, 1))
   154  	}
   155  	for i := 0; i < numberOfNodes; i++ {
   156  		m.Delete(strconv.Itoa(i))
   157  		if _, ok := m.Get(strconv.Itoa(i)); ok {
   158  			t.Fatalf("value was not expected for %d", i)
   159  		}
   160  	}
   161  }
   162  
   163  func TestMap_Range(t *testing.T) {
   164  	const numNodes = 1000
   165  	nm := node.NewManager[string, int](node.Config{})
   166  	m := New(nm)
   167  	for i := 0; i < numNodes; i++ {
   168  		m.Set(nm.Create(strconv.Itoa(i), i, 0, 1))
   169  	}
   170  	iters := 0
   171  	met := make(map[string]int)
   172  	m.Range(func(n node.Node[string, int]) bool {
   173  		if n.Key() != strconv.Itoa(n.Value()) {
   174  			t.Fatalf("got unexpected key/value for iteration %d: %v/%v", iters, n.Key(), n.Value())
   175  			return false
   176  		}
   177  		met[n.Key()] += 1
   178  		iters++
   179  		return true
   180  	})
   181  	if iters != numNodes {
   182  		t.Fatalf("got unexpected number of iterations: %d", iters)
   183  	}
   184  	for i := 0; i < numNodes; i++ {
   185  		if c := met[strconv.Itoa(i)]; c != 1 {
   186  			t.Fatalf("range did not iterate correctly over %d: %d", i, c)
   187  		}
   188  	}
   189  }
   190  
   191  func TestMap_RangeFalseReturned(t *testing.T) {
   192  	nm := node.NewManager[string, int](node.Config{})
   193  	m := New(nm)
   194  	for i := 0; i < 100; i++ {
   195  		m.Set(nm.Create(strconv.Itoa(i), i, 0, 1))
   196  	}
   197  	iters := 0
   198  	m.Range(func(n node.Node[string, int]) bool {
   199  		iters++
   200  		return iters != 13
   201  	})
   202  	if iters != 13 {
   203  		t.Fatalf("got unexpected number of iterations: %d", iters)
   204  	}
   205  }
   206  
   207  func TestMap_RangeNestedDelete(t *testing.T) {
   208  	const numNodes = 256
   209  	nm := node.NewManager[string, int](node.Config{})
   210  	m := New(nm)
   211  	for i := 0; i < numNodes; i++ {
   212  		m.Set(nm.Create(strconv.Itoa(i), i, 0, 1))
   213  	}
   214  	m.Range(func(n node.Node[string, int]) bool {
   215  		m.Delete(n.Key())
   216  		return true
   217  	})
   218  	for i := 0; i < numNodes; i++ {
   219  		if _, ok := m.Get(strconv.Itoa(i)); ok {
   220  			t.Fatalf("value found for %d", i)
   221  		}
   222  	}
   223  }
   224  
   225  func TestMap_Size(t *testing.T) {
   226  	const numberOfNodes = 1000
   227  	nm := node.NewManager[string, int](node.Config{})
   228  	m := New(nm)
   229  	size := m.Size()
   230  	if size != 0 {
   231  		t.Fatalf("zero size expected: %d", size)
   232  	}
   233  	expectedSize := 0
   234  	for i := 0; i < numberOfNodes; i++ {
   235  		m.Set(nm.Create(strconv.Itoa(i), i, 0, 1))
   236  		expectedSize++
   237  		size := m.Size()
   238  		if size != expectedSize {
   239  			t.Fatalf("size of %d was expected, got: %d", expectedSize, size)
   240  		}
   241  	}
   242  	for i := 0; i < numberOfNodes; i++ {
   243  		m.Delete(strconv.Itoa(i))
   244  		expectedSize--
   245  		size := m.Size()
   246  		if size != expectedSize {
   247  			t.Fatalf("size of %d was expected, got: %d", expectedSize, size)
   248  		}
   249  	}
   250  }
   251  
   252  func TestMap_Clear(t *testing.T) {
   253  	const numberOfNodes = 1000
   254  	nm := node.NewManager[string, int](node.Config{})
   255  	m := New(nm)
   256  	for i := 0; i < numberOfNodes; i++ {
   257  		m.Set(nm.Create(strconv.Itoa(i), i, 0, 1))
   258  	}
   259  	size := m.Size()
   260  	if size != numberOfNodes {
   261  		t.Fatalf("size of %d was expected, got: %d", numberOfNodes, size)
   262  	}
   263  	m.Clear()
   264  	size = m.Size()
   265  	if size != 0 {
   266  		t.Fatalf("zero size was expected, got: %d", size)
   267  	}
   268  }
   269  
   270  func parallelSeqSetter(t *testing.T, m *Map[string, int], storers, iterations, nodes int, wg *sync.WaitGroup) {
   271  	t.Helper()
   272  
   273  	for i := 0; i < iterations; i++ {
   274  		for j := 0; j < nodes; j++ {
   275  			if storers == 0 || j%storers == 0 {
   276  				m.Set(m.nodeManager.Create(strconv.Itoa(j), j, 0, 1))
   277  				n, ok := m.Get(strconv.Itoa(j))
   278  				if !ok {
   279  					t.Errorf("value was not found for %d", j)
   280  					break
   281  				}
   282  				if n.Value() != j {
   283  					t.Errorf("value was not expected for %d: %d", j, n.Value())
   284  					break
   285  				}
   286  			}
   287  		}
   288  	}
   289  	wg.Done()
   290  }
   291  
   292  func TestMap_ParallelSets(t *testing.T) {
   293  	const storers = 4
   294  	const iterations = 10_000
   295  	const nodes = 100
   296  	nm := node.NewManager[string, int](node.Config{})
   297  	m := New(nm)
   298  
   299  	wg := &sync.WaitGroup{}
   300  	wg.Add(storers)
   301  	for i := 0; i < storers; i++ {
   302  		go parallelSeqSetter(t, m, i, iterations, nodes, wg)
   303  	}
   304  	wg.Wait()
   305  
   306  	for i := 0; i < nodes; i++ {
   307  		n, ok := m.Get(strconv.Itoa(i))
   308  		if !ok {
   309  			t.Fatalf("value not found for %d", i)
   310  		}
   311  		if n.Value() != i {
   312  			t.Fatalf("values do not match for %d: %v", i, n.Value())
   313  		}
   314  	}
   315  }
   316  
   317  func parallelRandSetter(t *testing.T, m *Map[string, int], iteratinos, nodes int, wg *sync.WaitGroup) {
   318  	t.Helper()
   319  
   320  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   321  	for i := 0; i < iteratinos; i++ {
   322  		j := r.Intn(nodes)
   323  		m.Set(m.nodeManager.Create(strconv.Itoa(j), j, 0, 1))
   324  	}
   325  	wg.Done()
   326  }
   327  
   328  func parallelRandDeleter(t *testing.T, m *Map[string, int], iterations, nodes int, wg *sync.WaitGroup) {
   329  	t.Helper()
   330  
   331  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   332  	for i := 0; i < iterations; i++ {
   333  		j := r.Intn(nodes)
   334  		if v := m.Delete(strconv.Itoa(j)); v != nil && v.Value() != j {
   335  			t.Errorf("value was not expected for %d: %d", j, v.Value())
   336  		}
   337  	}
   338  	wg.Done()
   339  }
   340  
   341  func parallelGetter(t *testing.T, m *Map[string, int], iterations, nodes int, wg *sync.WaitGroup) {
   342  	t.Helper()
   343  
   344  	for i := 0; i < iterations; i++ {
   345  		for j := 0; j < nodes; j++ {
   346  			if n, ok := m.Get(strconv.Itoa(j)); ok && n.Value() != j {
   347  				t.Errorf("value was not expected for %d: %d", j, n.Value())
   348  			}
   349  		}
   350  	}
   351  	wg.Done()
   352  }
   353  
   354  func TestMap_ParallelGet(t *testing.T) {
   355  	const iterations = 100_000
   356  	const nodes = 100
   357  	nm := node.NewManager[string, int](node.Config{})
   358  	m := New(nm)
   359  
   360  	wg := &sync.WaitGroup{}
   361  	wg.Add(3)
   362  	go parallelRandSetter(t, m, iterations, nodes, wg)
   363  	go parallelRandDeleter(t, m, iterations, nodes, wg)
   364  	go parallelGetter(t, m, iterations, nodes, wg)
   365  
   366  	wg.Wait()
   367  }
   368  
   369  func TestMap_ParallelSetsAndDeletes(t *testing.T) {
   370  	const workers = 2
   371  	const iterations = 100_000
   372  	const nodes = 1000
   373  	nm := node.NewManager[string, int](node.Config{})
   374  	m := New(nm)
   375  	wg := &sync.WaitGroup{}
   376  	wg.Add(2 * workers)
   377  	for i := 0; i < workers; i++ {
   378  		go parallelRandSetter(t, m, iterations, nodes, wg)
   379  		go parallelRandDeleter(t, m, iterations, nodes, wg)
   380  	}
   381  
   382  	wg.Wait()
   383  }
   384  
   385  func parallelTypedRangeSetter(t *testing.T, m *Map[int, int], numNodes int, stopFlag *int64, cdone chan bool) {
   386  	t.Helper()
   387  
   388  	for {
   389  		for i := 0; i < numNodes; i++ {
   390  			m.Set(m.nodeManager.Create(i, i, 0, 1))
   391  		}
   392  		if atomic.LoadInt64(stopFlag) != 0 {
   393  			break
   394  		}
   395  	}
   396  	cdone <- true
   397  }
   398  
   399  func parallelTypedRangeDeleter(t *testing.T, m *Map[int, int], numNodes int, stopFlag *int64, cdone chan bool) {
   400  	t.Helper()
   401  
   402  	for {
   403  		for i := 0; i < numNodes; i++ {
   404  			m.Delete(i)
   405  		}
   406  		if atomic.LoadInt64(stopFlag) != 0 {
   407  			break
   408  		}
   409  	}
   410  	cdone <- true
   411  }
   412  
   413  func TestMap_ParallelRange(t *testing.T) {
   414  	const numNodes = 10_000
   415  	nm := node.NewManager[int, int](node.Config{})
   416  	m := New(nm)
   417  	for i := 0; i < numNodes; i++ {
   418  		m.Set(nm.Create(i, i, 0, 1))
   419  	}
   420  	// Start goroutines that would be storing and deleting items in parallel.
   421  	cdone := make(chan bool)
   422  	stopFlag := int64(0)
   423  	go parallelTypedRangeSetter(t, m, numNodes, &stopFlag, cdone)
   424  	go parallelTypedRangeDeleter(t, m, numNodes, &stopFlag, cdone)
   425  	// Iterate the map and verify that no duplicate keys were met.
   426  	met := make(map[int]int)
   427  	m.Range(func(n node.Node[int, int]) bool {
   428  		if n.Key() != n.Value() {
   429  			t.Fatalf("got unexpected value for key %d: %d", n.Key(), n.Value())
   430  			return false
   431  		}
   432  		met[n.Key()] += 1
   433  		return true
   434  	})
   435  	if len(met) == 0 {
   436  		t.Fatal("no nodes were met when iterating")
   437  	}
   438  	for k, c := range met {
   439  		if c != 1 {
   440  			t.Fatalf("met key %d multiple times: %d", k, c)
   441  		}
   442  	}
   443  	// Make sure that both goroutines finish.
   444  	atomic.StoreInt64(&stopFlag, 1)
   445  	<-cdone
   446  	<-cdone
   447  }