github.com/aristanetworks/gomap@v0.0.0-20240103001659-f6b0e31fb1a7/map_test.go (about)

     1  // Modifications copyright (c) Arista Networks, Inc. 2022
     2  // Underlying
     3  // Copyright 2014 The Go Authors. All rights reserved.
     4  // Use of this source code is governed by a BSD-style
     5  // license that can be found in the LICENSE file.
     6  
     7  package gomap
     8  
     9  import (
    10  	"encoding/binary"
    11  	"fmt"
    12  	"hash/maphash"
    13  	"strings"
    14  	"sync"
    15  	"testing"
    16  
    17  	"golang.org/x/exp/slices"
    18  )
    19  
    20  func (m *Map[K, E]) debugString() string {
    21  	var buf strings.Builder
    22  	fmt.Fprintf(&buf, "count: %d, buckets: %d, overflows: %d growing: %t\n",
    23  		m.count, len(m.buckets), m.noverflow, m.growing())
    24  
    25  	for i, b := range m.buckets {
    26  		fmt.Fprintf(&buf, "bucket: %d\n", i)
    27  		b := &b
    28  		for {
    29  			for i := uintptr(0); i < bucketCnt; i++ {
    30  				seen := map[uint8]struct{}{}
    31  				switch b.tophash[i] {
    32  				case emptyRest:
    33  					buf.WriteString("  emptyRest\n")
    34  				case emptyOne:
    35  					buf.WriteString("  emptyOne\n")
    36  				case evacuatedX:
    37  					buf.WriteString("  evacuatedX?\n")
    38  				case evacuatedY:
    39  					buf.WriteString("  evacuatedY?\n")
    40  				case evacuatedEmpty:
    41  					buf.WriteString("  evacuatedEmpty?\n")
    42  				default:
    43  					var s string
    44  					if _, ok := seen[b.tophash[i]]; ok {
    45  						s = " duplicate"
    46  					} else {
    47  						seen[b.tophash[i]] = struct{}{}
    48  					}
    49  					fmt.Fprintf(&buf, "  0x%02x"+s+"\n", b.tophash[i])
    50  				}
    51  			}
    52  			if b.overflow == nil {
    53  				break
    54  			}
    55  			buf.WriteString("overflow->\n")
    56  			b = b.overflow
    57  		}
    58  	}
    59  
    60  	return buf.String()
    61  }
    62  
    63  func intHash(seed maphash.Seed, a int) uint64 {
    64  	var buf [8]byte
    65  	binary.LittleEndian.PutUint64(buf[:], uint64(a))
    66  	return maphash.Bytes(seed, buf[:])
    67  }
    68  
    69  func TestSetGetDelete(t *testing.T) {
    70  	const count = 1000
    71  	t.Run("nohint", func(t *testing.T) {
    72  		m := New[int, int](func(a int, b int) bool { return a == b }, intHash)
    73  		t.Logf("Buckets: %d Unused-overflow: %d", len(m.buckets), cap(m.buckets)-len(m.buckets))
    74  		for i := 0; i < count; i++ {
    75  			m.Set(i, i)
    76  			if v, ok := m.Get(i); !ok {
    77  				t.Errorf("got not ok for %d", i)
    78  			} else if v != i {
    79  				t.Errorf("unexpected value for %d: %d", i, v)
    80  			}
    81  			if m.Len() != i+1 {
    82  				t.Errorf("expected len: %d got: %d", i+1, m.Len())
    83  			}
    84  		}
    85  		t.Logf("Buckets: %d Unused-overflow: %d", len(m.buckets), cap(m.buckets)-len(m.buckets))
    86  		t.Log("Overflow:", m.noverflow)
    87  		for i := 0; i < count; i++ {
    88  			if v, ok := m.Get(i); !ok {
    89  				t.Errorf("got not ok for %d", i)
    90  			} else if v != i {
    91  				t.Errorf("unexpected value for %d: %d", i, v)
    92  			}
    93  			if m.Len() != count {
    94  				t.Errorf("expected len: %d got: %d", count, m.Len())
    95  			}
    96  
    97  		}
    98  		for i := 0; i < count; i++ {
    99  			if v, ok := m.Get(i); !ok {
   100  				t.Errorf("got not ok for %d", i)
   101  			} else if v != i {
   102  				t.Errorf("unexpected value for %d: %d", i, v)
   103  			}
   104  
   105  			m.Delete(i)
   106  
   107  			if v, ok := m.Get(i); ok {
   108  				t.Errorf("found %d: %d, but it should have been deleted", i, v)
   109  			}
   110  			if m.Len() != count-i-1 {
   111  				t.Errorf("expected len: %d got: %d", count, m.Len())
   112  			}
   113  		}
   114  	})
   115  	t.Run("hint", func(t *testing.T) {
   116  		m := NewHint[int, int](count, func(a int, b int) bool { return a == b }, intHash)
   117  		t.Logf("Buckets: %d Unused-overflow: %d", len(m.buckets), cap(m.buckets)-len(m.buckets))
   118  		for i := 0; i < count; i++ {
   119  			m.Set(i, i)
   120  			if v, ok := m.Get(i); !ok {
   121  				t.Errorf("got not ok for %d", i)
   122  			} else if v != i {
   123  				t.Errorf("unexpected value for %d: %d", i, v)
   124  			}
   125  			if m.Len() != i+1 {
   126  				t.Errorf("expected len: %d got: %d", i+1, m.Len())
   127  			}
   128  		}
   129  		t.Logf("Buckets: %d Unused-overflow: %d", len(m.buckets), cap(m.buckets)-len(m.buckets))
   130  		t.Log("Overflow:", m.noverflow)
   131  		for i := 0; i < count; i++ {
   132  			if v, ok := m.Get(i); !ok {
   133  				t.Errorf("got not ok for %d", i)
   134  			} else if v != i {
   135  				t.Errorf("unexpected value for %d: %d", i, v)
   136  			}
   137  			if m.Len() != count {
   138  				t.Errorf("expected len: %d got: %d", count, m.Len())
   139  			}
   140  
   141  		}
   142  		for i := 0; i < count; i++ {
   143  			if v, ok := m.Get(i); !ok {
   144  				t.Errorf("got not ok for %d", i)
   145  			} else if v != i {
   146  				t.Errorf("unexpected value for %d: %d", i, v)
   147  			}
   148  
   149  			m.Delete(i)
   150  
   151  			if v, ok := m.Get(i); ok {
   152  				t.Errorf("found %d: %d, but it should have been deleted", i, v)
   153  			}
   154  			if m.Len() != count-i-1 {
   155  				t.Errorf("expected len: %d got: %d", count, m.Len())
   156  			}
   157  		}
   158  	})
   159  }
   160  
   161  func BenchmarkGrow(b *testing.B) {
   162  	b.Run("hint", func(b *testing.B) {
   163  		b.ReportAllocs()
   164  		m := NewHint[int, int](b.N, func(a int, b int) bool { return a == b }, intHash)
   165  		for i := 0; i < b.N; i++ {
   166  			m.Set(i, i)
   167  		}
   168  	})
   169  	b.Run("nohint", func(b *testing.B) {
   170  		b.ReportAllocs()
   171  		m := New[int, int](func(a int, b int) bool { return a == b }, intHash)
   172  		for i := 0; i < b.N; i++ {
   173  			m.Set(i, i)
   174  		}
   175  	})
   176  
   177  	b.Run("std:hint", func(b *testing.B) {
   178  		b.ReportAllocs()
   179  		m := make(map[int]int, b.N)
   180  		for i := 0; i < b.N; i++ {
   181  			m[i] = i
   182  		}
   183  	})
   184  	b.Run("std:nohint", func(b *testing.B) {
   185  		b.ReportAllocs()
   186  		m := map[int]int{}
   187  		for i := 0; i < b.N; i++ {
   188  			m[i] = i
   189  		}
   190  	})
   191  }
   192  
   193  func TestGetIterateRace(t *testing.T) {
   194  	m := NewHint[int, int](100, func(a int, b int) bool { return a == b }, intHash)
   195  	for i := 0; i < 100; i++ {
   196  		m.Set(i, i)
   197  	}
   198  	var wg sync.WaitGroup
   199  	wg.Add(1)
   200  	go func() {
   201  		for i := 0; i < 100; i++ {
   202  			v, ok := m.Get(i)
   203  			if !ok || v != i {
   204  				t.Errorf("expected: %d got: %d, %t", i, v, ok)
   205  			}
   206  		}
   207  		wg.Done()
   208  	}()
   209  	wg.Add(1)
   210  	go func() {
   211  		for i := 0; i < 100; i++ {
   212  			v, ok := m.Get(i)
   213  			if !ok || v != i {
   214  				t.Errorf("expected: %d got: %d, %t", i, v, ok)
   215  			}
   216  		}
   217  		wg.Done()
   218  	}()
   219  
   220  	wg.Add(1)
   221  	go func() {
   222  		for i := 0; i < 100; i++ {
   223  			iter := m.Iter()
   224  			if !iter.Next() {
   225  				t.Error("unexpected end of iter")
   226  			}
   227  		}
   228  		wg.Done()
   229  	}()
   230  	wg.Add(1)
   231  	go func() {
   232  		for i := 0; i < 100; i++ {
   233  			iter := m.Iter()
   234  			if !iter.Next() {
   235  				t.Error("unexpected end of iter")
   236  			}
   237  		}
   238  		wg.Done()
   239  	}()
   240  	wg.Wait()
   241  }
   242  
   243  // badIntHash is a bad hash function that gives simple deterministic
   244  // hash to give control over which bucket a key lands in.
   245  func badIntHash(seed maphash.Seed, a uint64) uint64 {
   246  	return uint64(a)
   247  }
   248  
   249  func TestIter(t *testing.T) {
   250  	m := New[uint64, uint64](
   251  		func(a, b uint64) bool { return a == b },
   252  		badIntHash,
   253  	)
   254  	expected := make(map[uint64]uint64, 9)
   255  	for i := uint64(0); i < 9; i++ {
   256  		expected[i] = i
   257  		m.Set(i, i)
   258  	}
   259  	for i := m.Iter(); i.Next(); {
   260  		e, ok := expected[i.Key()]
   261  		if !ok {
   262  			t.Errorf("unexpected value in m: [%d: %d]", i.Key(), i.Elem())
   263  			continue
   264  		}
   265  		if e != i.Elem() {
   266  			t.Errorf("wrong value for key %d. Expected: %d Got: %d", i.Key(), e, i.Elem())
   267  			continue
   268  		}
   269  		delete(expected, i.Key())
   270  	}
   271  	if len(expected) > 0 {
   272  		t.Errorf("Values not found in m: %v", expected)
   273  	}
   274  }
   275  
   276  func TestClear(t *testing.T) {
   277  	m := New(
   278  		func(a, b string) bool { return a == b },
   279  		maphash.String,
   280  		KeyElem[string, string]{"a", "a"},
   281  		KeyElem[string, string]{"b", "b"},
   282  		KeyElem[string, string]{"c", "c"},
   283  		KeyElem[string, string]{"d", "d"},
   284  	)
   285  	if m.Len() != 4 {
   286  		t.Fatalf("Unexpected size after New (%d): %s", m.Len(), m.debugString())
   287  	}
   288  	m.Clear()
   289  	if m.Len() != 0 {
   290  		t.Errorf("expected empty map: %s", m.debugString())
   291  	}
   292  	for i := m.Iter(); i.Next(); {
   293  		t.Errorf("unexpected entry in map: [%s: %s]", i.Key(), i.Elem())
   294  	}
   295  }
   296  
   297  func TestIterResize(t *testing.T) {
   298  	m := New[uint64, uint64](
   299  		func(a, b uint64) bool { return a == b },
   300  		badIntHash,
   301  	)
   302  
   303  	// insert numbers that initially hash to the same bucket, but will
   304  	// be split into different buckets on resize.
   305  	initial := map[uint64]uint64{0: 0, 1: 1, 2: 2, 3: 3}
   306  	for k, e := range initial {
   307  		m.Set(k, e)
   308  	}
   309  	// create the iter
   310  	i := m.Iter()
   311  	// Add some additional data to cause a resize
   312  	additional := map[uint64]uint64{100: 100, 101: 101, 102: 102, 103: 103, 104: 104}
   313  	for k, e := range additional {
   314  		m.Set(k, e)
   315  	}
   316  
   317  	// Remove 1 value that in each of the initial and split buckets
   318  	m.Delete(0)
   319  	delete(initial, 0)
   320  	m.Delete(1)
   321  	delete(initial, 1)
   322  	for i.Next() {
   323  		if i.Key() != i.Elem() {
   324  			t.Errorf("expected key == elem, but got: %d != %d", i.Key(), i.Elem())
   325  			t.Error(m.debugString())
   326  		}
   327  		if _, ok := initial[i.Key()]; ok {
   328  			delete(initial, i.Key())
   329  			continue
   330  		}
   331  		if _, ok := additional[i.Key()]; ok {
   332  			t.Logf("Saw key from additional: %d", i.Key())
   333  			continue
   334  		}
   335  		t.Errorf("Unexpected value from iter: %d", i.Key())
   336  	}
   337  	for k := range initial {
   338  		t.Errorf("iter missing key: %d", k)
   339  	}
   340  }
   341  
   342  func TestIterDuringGrow(t *testing.T) {
   343  	m := New[uint64, uint64](
   344  		func(a, b uint64) bool { return a == b },
   345  		badIntHash,
   346  	)
   347  
   348  	// Insert exactly 27 numbers so we end up in the middle of a grow.
   349  	expected := make(map[uint64]uint64, 27)
   350  	for i := uint64(0); i < 27; i++ {
   351  		expected[i] = i
   352  		m.Set(i, i)
   353  	}
   354  	// create the iter while Map is growing
   355  	i := m.Iter()
   356  
   357  	for i.Next() {
   358  		t.Logf("Key: %d", i.Key())
   359  		if i.Key() != i.Elem() {
   360  			t.Errorf("expected key == elem, but got: %d != %d", i.Key(), i.Elem())
   361  			t.Error(m.debugString())
   362  		}
   363  
   364  		if _, ok := expected[i.Key()]; ok {
   365  			delete(expected, i.Key())
   366  			continue
   367  		}
   368  		t.Errorf("Unexpected value from iter: %d", i.Key())
   369  	}
   370  	for k := range expected {
   371  		t.Errorf("iter missing key: %d", k)
   372  	}
   373  }
   374  
   375  func TestUpdate(t *testing.T) {
   376  	m := New[int, []int](
   377  		func(a, b int) bool { return a == b },
   378  		intHash)
   379  	for key := 0; key < 10; key++ {
   380  		var expected []int
   381  		for i := 0; i < 3; i++ {
   382  			m.Update(key, func(cur []int) []int { return append(cur, 1) })
   383  			expected = append(expected, 1)
   384  			got, ok := m.Get(key)
   385  			if !ok {
   386  				t.Errorf("m missing key: %v", key)
   387  			} else if !slices.Equal(got, expected) {
   388  				t.Errorf("Got: %v Expected: %v", got, expected)
   389  			}
   390  		}
   391  	}
   392  }
   393  
   394  func BenchmarkIter(b *testing.B) {
   395  	m := New[string, int](
   396  		func(a, b string) bool { return a == b },
   397  		maphash.String,
   398  		KeyElem[string, int]{"one", 1},
   399  		KeyElem[string, int]{"two", 2},
   400  		KeyElem[string, int]{"three", 3},
   401  	)
   402  	b.ReportAllocs()
   403  	b.ResetTimer()
   404  	for i := 0; i < b.N; i++ {
   405  		for it := m.Iter(); it.Next(); {
   406  		}
   407  	}
   408  }
   409  
   410  func BenchmarkRand(b *testing.B) {
   411  	for i := 0; i < b.N; i++ {
   412  		rand64()
   413  	}
   414  }