tally_test.gno
4.22 Kb · 151 lines
1package tally
2
3import (
4 "testing"
5
6 "gno.land/p/nt/uassert/v0"
7)
8
9func keys(entries []Entry) string {
10 out := ""
11 for i, e := range entries {
12 if i > 0 {
13 out += ","
14 }
15 out += e.Key
16 }
17 return out
18}
19
20func TestSortOrdersByScoreThenKey(t *testing.T) {
21 cases := []struct {
22 name string
23 in []Entry
24 want string
25 }{
26 {"already ordered", []Entry{{"a", 3}, {"b", 2}, {"c", 1}}, "a,b,c"},
27 {"reversed", []Entry{{"c", 1}, {"b", 2}, {"a", 3}}, "a,b,c"},
28 {"all tied, key decides", []Entry{{"c", 5}, {"a", 5}, {"b", 5}}, "a,b,c"},
29 {"partial tie", []Entry{{"z", 1}, {"b", 9}, {"a", 9}}, "a,b,z"},
30 {"negatives sort below", []Entry{{"a", -1}, {"b", 0}, {"c", 1}}, "c,b,a"},
31 {"single", []Entry{{"only", 7}}, "only"},
32 {"empty", nil, ""},
33 }
34 for _, tc := range cases {
35 t.Run(tc.name, func(t *testing.T) {
36 in := make([]Entry, len(tc.in))
37 copy(in, tc.in)
38 Sort(in)
39 uassert.Equal(t, tc.want, keys(in))
40 })
41 }
42}
43
44// TestSortIsATotalOrder is the defect this package exists to remove. A
45// comparator whose last clause repeats the score (rpgroom's third clause) is
46// not a total order: two equal rows compare false both ways and their order is
47// decided by the algorithm. Sorting twice from different inputs must agree.
48func TestSortIsATotalOrder(t *testing.T) {
49 a := []Entry{{"x", 5}, {"y", 5}, {"z", 5}, {"w", 9}}
50 b := []Entry{{"z", 5}, {"w", 9}, {"y", 5}, {"x", 5}}
51 Sort(a)
52 Sort(b)
53 uassert.Equal(t, keys(a), keys(b))
54 uassert.Equal(t, "w,x,y,z", keys(a))
55}
56
57func TestTop(t *testing.T) {
58 in := []Entry{{"c", 1}, {"a", 3}, {"b", 2}}
59 uassert.Equal(t, "a,b", keys(Top(in, 2)))
60 uassert.Equal(t, "a,b,c", keys(Top(in, 10)))
61 uassert.Equal(t, "", keys(Top(in, 0)))
62 uassert.Equal(t, "", keys(Top(in, -1)))
63 uassert.Equal(t, "", keys(Top(nil, 3)))
64}
65
66// TestTopDoesNotDisturbTheCaller pins that Top copies. A realm that renders a
67// Top(3) and then iterates its own slice must not find it reordered.
68func TestTopDoesNotDisturbTheCaller(t *testing.T) {
69 in := []Entry{{"c", 1}, {"a", 3}, {"b", 2}}
70 _ = Top(in, 2)
71 uassert.Equal(t, "c,a,b", keys(in))
72}
73
74func TestRank(t *testing.T) {
75 in := []Entry{{"c", 1}, {"a", 3}, {"b", 2}}
76 uassert.Equal(t, 1, Rank(in, "a"))
77 uassert.Equal(t, 2, Rank(in, "b"))
78 uassert.Equal(t, 3, Rank(in, "c"))
79 uassert.Equal(t, 0, Rank(in, "absent"))
80}
81
82// TestRankNeverTies is what makes a rendered "#3" mean something: two equal
83// scores still get 2 and 3, never 2 and 2.
84func TestRankNeverTies(t *testing.T) {
85 in := []Entry{{"a", 5}, {"b", 5}, {"c", 5}}
86 seen := map[int]bool{}
87 for _, e := range in {
88 r := Rank(in, e.Key)
89 if seen[r] {
90 t.Errorf("rank %d claimed twice", r)
91 }
92 seen[r] = true
93 }
94 uassert.Equal(t, 3, len(seen))
95}
96
97func TestBoardAddAndScore(t *testing.T) {
98 b := NewBoard()
99 uassert.Equal(t, 0, b.Len())
100 uassert.Equal(t, int64(5), b.Add("a", 5))
101 uassert.Equal(t, int64(8), b.Add("a", 3))
102 uassert.Equal(t, int64(6), b.Add("a", -2))
103 uassert.Equal(t, int64(6), b.Score("a"))
104 uassert.Equal(t, int64(0), b.Score("never"))
105 uassert.Equal(t, 1, b.Len())
106}
107
108func TestBoardHasDistinguishesAbsentFromZero(t *testing.T) {
109 b := NewBoard()
110 b.Set("zero", 0)
111 uassert.True(t, b.Has("zero"))
112 uassert.False(t, b.Has("absent"))
113 uassert.Equal(t, int64(0), b.Score("zero"))
114 uassert.Equal(t, int64(0), b.Score("absent"))
115}
116
117func TestBoardRemove(t *testing.T) {
118 b := NewBoard()
119 b.Set("a", 1)
120 uassert.True(t, b.Remove("a"))
121 uassert.False(t, b.Remove("a"))
122 uassert.Equal(t, 0, b.Len())
123}
124
125func TestBoardEntriesAndTop(t *testing.T) {
126 b := NewBoard()
127 b.Set("c", 1)
128 b.Set("a", 3)
129 b.Set("b", 3)
130 uassert.Equal(t, "a,b,c", keys(b.Entries()))
131 uassert.Equal(t, "a,b", keys(b.Top(2)))
132 uassert.Equal(t, 1, b.Rank("a"))
133 uassert.Equal(t, 2, b.Rank("b"))
134 uassert.Equal(t, 0, b.Rank("absent"))
135}
136
137// TestBoardOrderDoesNotDependOnInsertion is the reason Board is backed by a
138// tree and not a map: the same scores inserted in a different order must
139// render identically.
140func TestBoardOrderDoesNotDependOnInsertion(t *testing.T) {
141 one := NewBoard()
142 for _, e := range []Entry{{"a", 5}, {"b", 5}, {"c", 9}} {
143 one.Set(e.Key, e.Score)
144 }
145 two := NewBoard()
146 for _, e := range []Entry{{"c", 9}, {"b", 5}, {"a", 5}} {
147 two.Set(e.Key, e.Score)
148 }
149 uassert.Equal(t, keys(one.Entries()), keys(two.Entries()))
150 uassert.Equal(t, "c,a,b", keys(one.Entries()))
151}