Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

bidimap_test.gno

6.68 Kb · 254 lines
  1package bidimap
  2
  3import (
  4	"strconv"
  5	"strings"
  6	"testing"
  7
  8	"gno.land/p/nt/uassert/v0"
  9)
 10
 11func TestEmpty(t *testing.T) {
 12	m := New()
 13	uassert.Equal(t, 0, m.Len())
 14	uassert.False(t, m.Has("a"))
 15	uassert.False(t, m.HasValue("1"))
 16	uassert.True(t, m.Consistent(), "an empty map is consistent")
 17}
 18
 19func TestPutAndBothLookups(t *testing.T) {
 20	m := New()
 21	_, ok := m.Put("alice", "admin")
 22	uassert.True(t, ok)
 23
 24	v, found := m.Get("alice")
 25	uassert.True(t, found)
 26	uassert.Equal(t, "admin", v)
 27
 28	k, found := m.GetKey("admin")
 29	uassert.True(t, found)
 30	uassert.Equal(t, "alice", k, "the reverse lookup is the point of the type")
 31
 32	uassert.True(t, m.Has("alice"))
 33	uassert.True(t, m.HasValue("admin"))
 34	uassert.True(t, m.Consistent())
 35}
 36
 37func TestMissingLookups(t *testing.T) {
 38	m := New()
 39	m.Put("a", "1")
 40
 41	_, ok := m.Get("nope")
 42	uassert.False(t, ok)
 43	_, ok = m.GetKey("nope")
 44	uassert.False(t, ok)
 45}
 46
 47// TestPutReplacesOnKeySide is the invariant that a naive pair-of-maps breaks:
 48// rebinding a key must drop the old value from the reverse index too.
 49func TestPutReplacesOnKeySide(t *testing.T) {
 50	m := New()
 51	m.Put("alice", "admin")
 52
 53	evicted, ok := m.Put("alice", "auditor")
 54	uassert.True(t, ok)
 55	uassert.Equal(t, 1, len(evicted))
 56	uassert.Equal(t, "alice", evicted[0][0])
 57	uassert.Equal(t, "admin", evicted[0][1])
 58
 59	uassert.False(t, m.HasValue("admin"), "the stale value must leave the reverse index")
 60	uassert.Equal(t, 1, m.Len())
 61	uassert.True(t, m.Consistent())
 62}
 63
 64// TestPutReplacesOnValueSide is the mirror case: stealing a value must unbind
 65// whoever held it.
 66func TestPutReplacesOnValueSide(t *testing.T) {
 67	m := New()
 68	m.Put("alice", "admin")
 69
 70	evicted, ok := m.Put("bob", "admin")
 71	uassert.True(t, ok)
 72	uassert.Equal(t, 1, len(evicted))
 73	uassert.Equal(t, "alice", evicted[0][0])
 74
 75	uassert.False(t, m.Has("alice"), "alice lost her binding to bob")
 76	k, _ := m.GetKey("admin")
 77	uassert.Equal(t, "bob", k)
 78	uassert.Equal(t, 1, m.Len())
 79	uassert.True(t, m.Consistent())
 80}
 81
 82// TestPutEvictsBothSides covers the case where the new pair displaces two
 83// different existing pairs at once.
 84func TestPutEvictsBothSides(t *testing.T) {
 85	m := New()
 86	m.Put("alice", "admin")
 87	m.Put("bob", "auditor")
 88
 89	evicted, ok := m.Put("alice", "auditor")
 90	uassert.True(t, ok)
 91	uassert.Equal(t, 2, len(evicted), "displaces alice->admin and bob->auditor")
 92
 93	uassert.Equal(t, 1, m.Len())
 94	uassert.False(t, m.Has("bob"))
 95	uassert.False(t, m.HasValue("admin"))
 96	uassert.True(t, m.Consistent())
 97}
 98
 99func TestPutIdenticalPairIsNoop(t *testing.T) {
100	m := New()
101	m.Put("a", "1")
102	evicted, ok := m.Put("a", "1")
103	uassert.True(t, ok)
104	uassert.Equal(t, 0, len(evicted), "re-putting the same pair evicts nothing")
105	uassert.Equal(t, 1, m.Len())
106}
107
108func TestPutUniqueRefuses(t *testing.T) {
109	m := New()
110	uassert.True(t, m.PutUnique("alice", "admin"))
111
112	uassert.False(t, m.PutUnique("alice", "auditor"), "key already bound")
113	uassert.False(t, m.PutUnique("bob", "admin"), "value already bound")
114	uassert.True(t, m.PutUnique("alice", "admin"), "identical pair is idempotent")
115
116	// Nothing changed.
117	uassert.Equal(t, 1, m.Len())
118	v, _ := m.Get("alice")
119	uassert.Equal(t, "admin", v)
120	uassert.True(t, m.Consistent())
121}
122
123func TestDelete(t *testing.T) {
124	m := New()
125	m.Put("a", "1")
126
127	uassert.True(t, m.Delete("a"))
128	uassert.Equal(t, 0, m.Len())
129	uassert.False(t, m.HasValue("1"), "delete clears the reverse index too")
130	uassert.False(t, m.Delete("a"), "deleting twice reports false")
131	uassert.True(t, m.Consistent())
132}
133
134func TestDeleteValue(t *testing.T) {
135	m := New()
136	m.Put("a", "1")
137
138	uassert.True(t, m.DeleteValue("1"))
139	uassert.False(t, m.Has("a"), "delete-by-value clears the forward index too")
140	uassert.False(t, m.DeleteValue("1"))
141	uassert.True(t, m.Consistent())
142}
143
144// TestKeysAndValuesAreSorted is the determinism guarantee.
145func TestKeysAndValuesAreSorted(t *testing.T) {
146	m := New()
147	m.Put("delta", "4")
148	m.Put("alpha", "1")
149	m.Put("charlie", "3")
150
151	uassert.Equal(t, "alpha,charlie,delta", strings.Join(m.Keys(), ","))
152	uassert.Equal(t, "1,3,4", strings.Join(m.Values(), ","))
153}
154
155func TestIterateSortedAndStops(t *testing.T) {
156	m := New()
157	m.Put("b", "2")
158	m.Put("a", "1")
159	m.Put("c", "3")
160
161	var seen []string
162	m.Iterate(func(k, v string) bool {
163		seen = append(seen, k+"="+v)
164		return false
165	})
166	uassert.Equal(t, "a=1,b=2,c=3", strings.Join(seen, ","))
167
168	var first []string
169	m.Iterate(func(k, v string) bool {
170		first = append(first, k)
171		return true // stop immediately
172	})
173	uassert.Equal(t, 1, len(first))
174	uassert.Equal(t, "a", first[0])
175}
176
177func TestInvert(t *testing.T) {
178	m := New()
179	m.Put("alice", "admin")
180	m.Put("bob", "auditor")
181
182	inv := m.Invert()
183	v, ok := inv.Get("admin")
184	uassert.True(t, ok)
185	uassert.Equal(t, "alice", v)
186	uassert.True(t, inv.Consistent())
187
188	// Independent of the original.
189	inv.Delete("admin")
190	uassert.Equal(t, 2, m.Len())
191}
192
193func TestClone(t *testing.T) {
194	m := New()
195	m.Put("a", "1")
196
197	cp := m.Clone()
198	cp.Put("b", "2")
199
200	uassert.Equal(t, 1, m.Len(), "clone must not share storage")
201	uassert.Equal(t, 2, cp.Len())
202	uassert.True(t, cp.Consistent())
203}
204
205func TestMaxPairs(t *testing.T) {
206	m := New()
207	for i := 0; i < MaxPairs; i++ {
208		s := strconv.Itoa(i)
209		_, ok := m.Put("k"+s, "v"+s)
210		uassert.True(t, ok, "should accept up to MaxPairs")
211	}
212	uassert.Equal(t, MaxPairs, m.Len())
213
214	_, ok := m.Put("overflow", "value")
215	uassert.False(t, ok, "a full map refuses a wholly new pair")
216	uassert.False(t, m.PutUnique("overflow", "value"))
217	uassert.Equal(t, MaxPairs, m.Len())
218}
219
220// TestFullMapStillRebinds pins the subtler half of the capacity rule: a full
221// map still accepts a Put that rebinds an existing key or steals an existing
222// value, because that reuses a slot rather than growing the map. Only a pair
223// new on BOTH sides is refused.
224func TestFullMapStillRebinds(t *testing.T) {
225	m := New()
226	for i := 0; i < MaxPairs; i++ {
227		s := strconv.Itoa(i)
228		m.Put("k"+s, "v"+s)
229	}
230
231	// New value, existing key -> reuses k0's slot, so it is allowed.
232	evicted, ok := m.Put("k0", "brand-new-value")
233	uassert.True(t, ok, "rebinding an existing key does not grow a full map")
234	uassert.Equal(t, 1, len(evicted))
235	uassert.Equal(t, "v0", evicted[0][1])
236
237	v, _ := m.Get("k0")
238	uassert.Equal(t, "brand-new-value", v)
239	uassert.Equal(t, MaxPairs, m.Len(), "size is unchanged")
240	uassert.True(t, m.Consistent())
241
242	// New key, existing value -> reuses that value's slot, also allowed.
243	_, ok = m.Put("brand-new-key", "v1")
244	uassert.True(t, ok, "stealing an existing value does not grow a full map")
245	uassert.Equal(t, MaxPairs, m.Len())
246	uassert.False(t, m.Has("k1"), "k1 lost its binding")
247	uassert.True(t, m.Consistent())
248
249	// New on both sides -> refused, and nothing changes.
250	_, ok = m.Put("wholly", "new")
251	uassert.False(t, ok, "a full map refuses a pair new on both sides")
252	uassert.Equal(t, MaxPairs, m.Len())
253	uassert.True(t, m.Consistent())
254}