github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/key/map_test.go (about)

     1  // Copyright (c) 2019 Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  package key
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"math/rand"
    11  	"strings"
    12  	"testing"
    13  )
    14  
    15  func (m *Map) debug() string {
    16  	var buf strings.Builder
    17  	for hash, entry := range m.custom {
    18  		fmt.Fprintf(&buf, "%d: ", hash)
    19  		first := true
    20  		_ = entryIter(entry, func(k, v interface{}) error {
    21  			if !first {
    22  				buf.WriteString(" -> ")
    23  			}
    24  			first = false
    25  			fmt.Fprintf(&buf, "{%v:%v}", k, v)
    26  			return nil
    27  		})
    28  		buf.WriteByte('\n')
    29  	}
    30  	return buf.String()
    31  }
    32  
    33  func TestMapEqual(t *testing.T) {
    34  	tests := []struct {
    35  		a      *Map
    36  		b      *Map
    37  		result bool
    38  	}{{ // empty
    39  		a:      &Map{},
    40  		b:      &Map{normal: map[interface{}]interface{}{}, custom: map[uint64]entry{}, length: 0},
    41  		result: true,
    42  	}, { // length check
    43  		a:      &Map{},
    44  		b:      &Map{normal: map[interface{}]interface{}{}, custom: map[uint64]entry{}, length: 1},
    45  		result: false,
    46  	}, { // map[string]interface{}
    47  		a:      &Map{normal: map[interface{}]interface{}{"a": 1}, length: 1},
    48  		b:      NewMap("a", 1),
    49  		result: true,
    50  	}, { // differing keys in normal
    51  		a:      &Map{normal: map[interface{}]interface{}{"a": "b"}, length: 1},
    52  		b:      NewMap("b", "b"),
    53  		result: false,
    54  	}, { // differing values in normal
    55  		a:      &Map{normal: map[interface{}]interface{}{"a": "b"}, length: 1},
    56  		b:      NewMap("a", false),
    57  		result: false,
    58  	}, { // multiple entries
    59  		a:      &Map{normal: map[interface{}]interface{}{"a": 1, "b": true}, length: 2},
    60  		b:      NewMap("a", 1, "b", true),
    61  		result: true,
    62  	}, { // nested maps in values
    63  		a: &Map{
    64  			normal: map[interface{}]interface{}{"a": map[string]interface{}{"b": 3}},
    65  			length: 1},
    66  		b:      NewMap("a", map[string]interface{}{"b": 3}),
    67  		result: true,
    68  	}, { // differing nested maps in values
    69  		a: &Map{
    70  			normal: map[interface{}]interface{}{"a": map[string]interface{}{"b": 3}},
    71  			length: 1},
    72  		b:      NewMap("a", map[string]interface{}{"b": 4}),
    73  		result: false,
    74  	}, { // map with map as key
    75  		a:      NewMap(New(map[string]interface{}{"a": 123}), "b"),
    76  		b:      NewMap(New(map[string]interface{}{"a": 123}), "b"),
    77  		result: true,
    78  	}, {
    79  		a:      NewMap(New(map[string]interface{}{"a": 123}), "a"),
    80  		b:      NewMap(New(map[string]interface{}{"a": 123}), "b"),
    81  		result: false,
    82  	}, {
    83  		a:      NewMap(New(map[string]interface{}{"a": 123}), "b"),
    84  		b:      NewMap(New(map[string]interface{}{"b": 123}), "b"),
    85  		result: false,
    86  	}, {
    87  		a:      NewMap(New(map[string]interface{}{"a": 1, "b": 2}), "c"),
    88  		b:      NewMap(New(map[string]interface{}{"a": 1, "b": 2}), "c"),
    89  		result: true,
    90  	}, { // maps with keys that hash to same buckets in different order
    91  		a: NewMap(
    92  			dumbHashable{dumb: "hashable1"}, 1,
    93  			dumbHashable{dumb: "hashable2"}, 2,
    94  			dumbHashable{dumb: "hashable3"}, 3),
    95  		b: NewMap(
    96  			dumbHashable{dumb: "hashable3"}, 3,
    97  			dumbHashable{dumb: "hashable2"}, 2,
    98  			dumbHashable{dumb: "hashable1"}, 1),
    99  		result: true,
   100  	}, { // maps with map as value
   101  		a: &Map{normal: map[interface{}]interface{}{
   102  			"foo": &Map{normal: map[interface{}]interface{}{"a": 1}, length: 1}}, length: 1},
   103  		b: &Map{normal: map[interface{}]interface{}{
   104  			"foo": &Map{normal: map[interface{}]interface{}{"a": 1}, length: 1}}, length: 1},
   105  		result: true,
   106  	}}
   107  
   108  	for _, tcase := range tests {
   109  		if tcase.a.Equal(tcase.b) != tcase.result {
   110  			t.Errorf("%v and %v are not equal", tcase.a, tcase.b)
   111  		}
   112  	}
   113  }
   114  
   115  type dumbHashable struct {
   116  	dumb interface{}
   117  }
   118  
   119  func (d dumbHashable) Equal(other interface{}) bool {
   120  	if o, ok := other.(dumbHashable); ok {
   121  		return d.dumb == o.dumb
   122  	}
   123  	return false
   124  }
   125  
   126  func (d dumbHashable) Hash() uint64 {
   127  	return 1234567890
   128  }
   129  
   130  func TestMapEntry(t *testing.T) {
   131  	m := NewMap()
   132  	verifyPresent := func(k, v interface{}) {
   133  		t.Helper()
   134  		if got, ok := m.Get(k); !ok || got != v {
   135  			t.Errorf("Get(%v): expected %v, got %v", k, v, got)
   136  		}
   137  	}
   138  	verifyAbsent := func(k interface{}) {
   139  		t.Helper()
   140  		if got, ok := m.Get(k); ok {
   141  			t.Errorf("Get(%v): expected not found, got %v", k, got)
   142  		}
   143  	}
   144  
   145  	// create entry list 1 -> 2 -> 3
   146  	for i := 1; i <= 3; i++ {
   147  		m.Set(dumbHashable{i}, 0)
   148  		if m.Len() != i {
   149  			t.Errorf("expected len %d, got %d", i, m.Len())
   150  		}
   151  		verifyPresent(dumbHashable{i}, 0)
   152  	}
   153  	if len(m.custom) != 1 {
   154  		t.Errorf("expected custom map to have 1 entry list, got %d", len(m.custom))
   155  	}
   156  	if m.Len() != 3 {
   157  		t.Errorf("expected len of 3, got %d", m.Len())
   158  	}
   159  
   160  	// overwrite list members
   161  	for i := 1; i <= 3; i++ {
   162  		m.Set(dumbHashable{i}, i)
   163  		verifyPresent(dumbHashable{i}, i)
   164  	}
   165  	if m.Len() != 3 {
   166  		t.Errorf("expected len of 3, got %d", m.Len())
   167  	}
   168  	t.Log(m.debug())
   169  
   170  	// delete nonexistant member
   171  	m.Del(dumbHashable{4})
   172  	if m.Len() != 3 {
   173  		t.Errorf("expected len of 3, got %d", m.Len())
   174  	}
   175  
   176  	// Check that iter works
   177  	i := 1
   178  	_ = m.Iter(func(k, v interface{}) error {
   179  		exp := dumbHashable{i}
   180  		if k != exp {
   181  			t.Errorf("expected key %v got %v", exp, k)
   182  		}
   183  		if v != i {
   184  			t.Errorf("expected val %d got %v", i, v)
   185  		}
   186  		i++
   187  		return nil
   188  	})
   189  
   190  	// delete middle of list
   191  	m.Del(dumbHashable{2})
   192  	verifyPresent(dumbHashable{1}, 1)
   193  	verifyAbsent(dumbHashable{2})
   194  	verifyPresent(dumbHashable{3}, 3)
   195  	if m.Len() != 2 {
   196  		t.Errorf("expected len of 2, got %d", m.Len())
   197  	}
   198  
   199  	// delete end of list
   200  	m.Del(dumbHashable{3})
   201  	verifyPresent(dumbHashable{1}, 1)
   202  	verifyAbsent(dumbHashable{3})
   203  	if m.Len() != 1 {
   204  		t.Errorf("expected len of 1, got %d", m.Len())
   205  	}
   206  
   207  	m.Set(dumbHashable{2}, 2)
   208  	// delete head of list with next member
   209  	m.Del(dumbHashable{1})
   210  	verifyAbsent(dumbHashable{1})
   211  	verifyPresent(dumbHashable{2}, 2)
   212  	if m.Len() != 1 {
   213  		t.Errorf("expected len of 1, got %d", m.Len())
   214  	}
   215  
   216  	// delete final list member
   217  	m.Del(dumbHashable{2})
   218  	verifyAbsent(dumbHashable{2})
   219  	if m.Len() != 0 {
   220  		t.Errorf("expected len of 0, got %d", m.Len())
   221  	}
   222  
   223  	if len(m.custom) != 0 {
   224  		t.Errorf("expected m.custom to be empty, but got len %d", len(m.custom))
   225  	}
   226  }
   227  
   228  func TestMapSetGet(t *testing.T) {
   229  	m := Map{}
   230  	tests := []struct {
   231  		setkey interface{}
   232  		getkey interface{}
   233  		val    interface{}
   234  		found  bool
   235  	}{{
   236  		setkey: "a",
   237  		getkey: "a",
   238  		val:    1,
   239  		found:  true,
   240  	}, {
   241  		setkey: "b",
   242  		getkey: "b",
   243  		val:    1,
   244  		found:  true,
   245  	}, {
   246  		setkey: 42,
   247  		getkey: 42,
   248  		val:    "foobar",
   249  		found:  true,
   250  	}, {
   251  		setkey: dumbHashable{dumb: "hashable1"},
   252  		getkey: dumbHashable{dumb: "hashable1"},
   253  		val:    1,
   254  		found:  true,
   255  	}, {
   256  		getkey: dumbHashable{dumb: "hashable2"},
   257  		val:    nil,
   258  		found:  false,
   259  	}, {
   260  		setkey: dumbHashable{dumb: "hashable2"},
   261  		getkey: dumbHashable{dumb: "hashable2"},
   262  		val:    2,
   263  		found:  true,
   264  	}, {
   265  		getkey: dumbHashable{dumb: "hashable42"},
   266  		val:    nil,
   267  		found:  false,
   268  	}, {
   269  		setkey: New(map[string]interface{}{"a": 1}),
   270  		getkey: New(map[string]interface{}{"a": 1}),
   271  		val:    "foo",
   272  		found:  true,
   273  	}, {
   274  		getkey: New(map[string]interface{}{"a": 2}),
   275  		val:    nil,
   276  		found:  false,
   277  	}, {
   278  		setkey: New(map[string]interface{}{"a": 2}),
   279  		getkey: New(map[string]interface{}{"a": 2}),
   280  		val:    "bar",
   281  		found:  true,
   282  	}}
   283  	for _, tcase := range tests {
   284  		if tcase.setkey != nil {
   285  			m.Set(tcase.setkey, tcase.val)
   286  		}
   287  		val, found := m.Get(tcase.getkey)
   288  		if found != tcase.found {
   289  			t.Errorf("found is %t, but expected found %t", found, tcase.found)
   290  		}
   291  		if val != tcase.val {
   292  			t.Errorf("val is %v for key %v, but expected val %v", val, tcase.getkey, tcase.val)
   293  		}
   294  	}
   295  
   296  }
   297  
   298  func TestMapDel(t *testing.T) {
   299  	tests := []struct {
   300  		m   *Map
   301  		del interface{}
   302  		exp *Map
   303  	}{{
   304  		m:   NewMap(),
   305  		del: "a",
   306  		exp: NewMap(),
   307  	}, {
   308  		m:   NewMap(),
   309  		del: New(map[string]interface{}{"a": 1}),
   310  		exp: NewMap(),
   311  	}, {
   312  		m:   NewMap("a", true),
   313  		del: "a",
   314  		exp: NewMap(),
   315  	}, {
   316  		m:   NewMap(dumbHashable{dumb: "hashable1"}, 42),
   317  		del: dumbHashable{dumb: "hashable1"},
   318  		exp: NewMap(),
   319  	}}
   320  
   321  	for _, tcase := range tests {
   322  		tcase.m.Del(tcase.del)
   323  		if !tcase.m.Equal(tcase.exp) {
   324  			t.Errorf("map %#v after del of element %v does not equal expected %#v",
   325  				tcase.m, tcase.del, tcase.exp)
   326  		}
   327  	}
   328  }
   329  
   330  func contains(elementlist []interface{}, element interface{}) bool {
   331  	equal := func(v interface{}) bool { return element == v }
   332  	if comp, ok := element.(Comparable); ok {
   333  		equal = func(v interface{}) bool { return comp.Equal(v) }
   334  	}
   335  	for _, el := range elementlist {
   336  		if equal(el) {
   337  			return true
   338  		}
   339  	}
   340  	return false
   341  }
   342  
   343  func TestMapIter(t *testing.T) {
   344  	tests := []struct {
   345  		m     *Map
   346  		elems []interface{}
   347  	}{{
   348  		m:     NewMap(),
   349  		elems: []interface{}{},
   350  	}, {
   351  		m:     NewMap("a", true),
   352  		elems: []interface{}{"a"},
   353  	}, {
   354  		m:     NewMap(dumbHashable{dumb: "hashable1"}, 42),
   355  		elems: []interface{}{dumbHashable{dumb: "hashable1"}},
   356  	}, {
   357  		m: NewMap(dumbHashable{dumb: "hashable2"}, 42,
   358  			dumbHashable{dumb: "hashable3"}, 42,
   359  			dumbHashable{dumb: "hashable4"}, 42),
   360  		elems: []interface{}{dumbHashable{dumb: "hashable2"},
   361  			dumbHashable{dumb: "hashable3"}, dumbHashable{dumb: "hashable4"}},
   362  	}, {
   363  		m: NewMap(
   364  			New(map[string]interface{}{"a": 123}), "b",
   365  			New(map[string]interface{}{"c": 456}), "d",
   366  			dumbHashable{dumb: "hashable1"}, 1,
   367  			dumbHashable{dumb: "hashable2"}, 2,
   368  			dumbHashable{dumb: "hashable3"}, 3,
   369  			"x", true,
   370  			"y", false,
   371  			"z", nil,
   372  		),
   373  		elems: []interface{}{
   374  			New(map[string]interface{}{"a": 123}), New(map[string]interface{}{"c": 456}),
   375  			dumbHashable{dumb: "hashable1"}, dumbHashable{dumb: "hashable2"},
   376  			dumbHashable{dumb: "hashable3"}, "x", "y", "z"},
   377  	}}
   378  	for _, tcase := range tests {
   379  		count := 0
   380  		iterfunc := func(k, v interface{}) error {
   381  			if !contains(tcase.elems, k) {
   382  				return fmt.Errorf("map %#v should not contain element %v", tcase.m, k)
   383  			}
   384  			count++
   385  			return nil
   386  		}
   387  		if err := tcase.m.Iter(iterfunc); err != nil {
   388  			t.Errorf("unexpected error %v", err)
   389  		}
   390  
   391  		expectedcount := len(tcase.elems)
   392  		if count != expectedcount || tcase.m.length != expectedcount {
   393  			t.Errorf("found %d elements in map %#v when expected %d", count, tcase.m, expectedcount)
   394  		}
   395  	}
   396  }
   397  
   398  func TestMapIterDel(t *testing.T) {
   399  	// Deleting from standard go maps while iterating is safe. Since a Map contains maps,
   400  	// deleting from a Map while iterating is also safe.
   401  	m := NewMap(
   402  		"1", "2",
   403  		New("1"), "keyVal",
   404  		New(map[string]interface{}{"key1": "val1", "key2": 2}), "mapVal",
   405  		dumbHashable{dumb: "dumbkey"}, "dumbHashVal",
   406  	)
   407  	if err := m.Iter(func(k, v interface{}) error {
   408  		m.Del(k)
   409  		if _, ok := m.Get(k); ok {
   410  			t.Errorf("key %v should not exist", k)
   411  		}
   412  		return nil
   413  	}); err != nil {
   414  		t.Error(err)
   415  	}
   416  	if m.Len() != 0 {
   417  		t.Errorf("map elements should all be deleted, but found %d elements", m.Len())
   418  	}
   419  }
   420  
   421  func TestMapKeys(t *testing.T) {
   422  	m := NewMap(
   423  		"1", "2",
   424  		New("1"), "keyVal",
   425  		New(map[string]interface{}{"key1": "val1", "key2": 2}), "mapVal",
   426  		dumbHashable{dumb: "dumbkey"}, "dumbHashVal",
   427  	)
   428  	if len(m.Keys()) != m.Len() {
   429  		t.Errorf("len(m.Keys()) %d != expected len(m) %d", len(m.Keys()), m.Len())
   430  	}
   431  	for _, key := range m.Keys() {
   432  		if _, ok := m.Get(key); !ok {
   433  			t.Errorf("could not find key %s in map m %s", key, m)
   434  		}
   435  	}
   436  }
   437  
   438  func TestMapValues(t *testing.T) {
   439  	m := NewMap(
   440  		"1", "2",
   441  		New("1"), "keyVal",
   442  		New(map[string]interface{}{"key1": "val1", "key2": 2}), "mapVal",
   443  		dumbHashable{dumb: "dumbkey"}, "dumbHashVal",
   444  	)
   445  	if len(m.Values()) != m.Len() {
   446  		t.Errorf("len(m.Values()) %d != expected len(m) %d", len(m.Values()), m.Len())
   447  	}
   448  	for _, value := range m.Values() {
   449  		found := false
   450  		if err := m.Iter(func(k, v interface{}) error {
   451  			if v == value {
   452  				found = true
   453  				return errors.New("found")
   454  			}
   455  			return nil
   456  		}); err != nil {
   457  			if err.Error() == "found" {
   458  				found = true
   459  			}
   460  		}
   461  		if !found {
   462  			t.Errorf("could not find value %s in map m %s", value, m)
   463  		}
   464  	}
   465  }
   466  
   467  func TestMapString(t *testing.T) {
   468  	for _, tc := range []struct {
   469  		m *Map
   470  		s string
   471  	}{{
   472  		m: NewMap(),
   473  		s: "key.Map[]",
   474  	}, {
   475  		m: NewMap("1", "2"),
   476  		s: "key.Map[1:2]",
   477  	}, {
   478  		m: NewMap(
   479  			"3", "4",
   480  			"1", "2",
   481  		),
   482  		s: "key.Map[1:2 3:4]",
   483  	}, {
   484  		m: NewMap(
   485  			New(map[string]interface{}{"key1": uint32(1), "key2": uint32(2)}), "foobar",
   486  			New(map[string]interface{}{"key1": uint32(3), "key2": uint32(4)}), "bazquux",
   487  		),
   488  		s: "key.Map[map[key1:1 key2:2]:foobar map[key1:3 key2:4]:bazquux]",
   489  	}} {
   490  		t.Run(tc.s, func(t *testing.T) {
   491  			out := tc.m.String()
   492  			if out != tc.s {
   493  				t.Errorf("expected %q got %q", tc.s, out)
   494  			}
   495  		})
   496  	}
   497  }
   498  
   499  func TestMapKeyString(t *testing.T) {
   500  	for _, tc := range []struct {
   501  		m *Map
   502  		s string
   503  	}{{
   504  		m: NewMap(
   505  			New(uint32(42)), true,
   506  			New("foo"), "bar",
   507  			New(map[string]interface{}{"hello": "world"}), "yolo",
   508  			New(map[string]interface{}{"key1": uint32(1), "key2": uint32(2)}), "foobar"),
   509  
   510  		s: "1_2=foobar_42=true_foo=bar_world=yolo",
   511  	}} {
   512  		t.Run(tc.s, func(t *testing.T) {
   513  			out := tc.m.KeyString()
   514  			if out != tc.s {
   515  				t.Errorf("expected %q got %q", tc.s, out)
   516  			}
   517  		})
   518  	}
   519  }
   520  
   521  func BenchmarkMapGrow(b *testing.B) {
   522  	keys := make([]Key, 150)
   523  	for j := 0; j < len(keys); j++ {
   524  		keys[j] = New(map[string]interface{}{
   525  			"foobar": 100,
   526  			"baz":    j,
   527  		})
   528  	}
   529  	b.Run("key.Map", func(b *testing.B) {
   530  		b.ReportAllocs()
   531  		for i := 0; i < b.N; i++ {
   532  			m := NewMap()
   533  			for j := 0; j < len(keys); j++ {
   534  				m.Set(keys[j], "foobar")
   535  			}
   536  			if m.Len() != len(keys) {
   537  				b.Fatal(m)
   538  			}
   539  		}
   540  	})
   541  }
   542  
   543  func BenchmarkMapGet(b *testing.B) {
   544  	keys := make([]Key, 150)
   545  	for j := 0; j < len(keys); j++ {
   546  		keys[j] = New(map[string]interface{}{
   547  			"foobar": 100,
   548  			"baz":    j,
   549  		})
   550  	}
   551  	keysRandomOrder := make([]Key, len(keys))
   552  	copy(keysRandomOrder, keys)
   553  	rand.Shuffle(len(keysRandomOrder), func(i, j int) {
   554  		keysRandomOrder[i], keysRandomOrder[j] = keysRandomOrder[j], keysRandomOrder[i]
   555  	})
   556  	b.Run("key.Map", func(b *testing.B) {
   557  		m := NewMap()
   558  		for j := 0; j < len(keys); j++ {
   559  			m.Set(keys[j], "foobar")
   560  		}
   561  		b.ReportAllocs()
   562  		b.ResetTimer()
   563  		for i := 0; i < b.N; i++ {
   564  			for _, k := range keysRandomOrder {
   565  				_, ok := m.Get(k)
   566  				if !ok {
   567  					b.Fatal("didn't find key")
   568  				}
   569  			}
   570  		}
   571  	})
   572  }