github.com/lingyao2333/mo-zero@v1.4.1/core/search/tree_test.go (about) 1 package search 2 3 import ( 4 "math/rand" 5 "strings" 6 "testing" 7 8 "github.com/lingyao2333/mo-zero/core/stringx" 9 "github.com/stretchr/testify/assert" 10 ) 11 12 type mockedRoute struct { 13 route string 14 value int 15 } 16 17 func TestSearch(t *testing.T) { 18 routes := []mockedRoute{ 19 {"/", 1}, 20 {"/api", 2}, 21 {"/img", 3}, 22 {"/:layer1", 4}, 23 {"/api/users", 5}, 24 {"/img/jpgs", 6}, 25 {"/img/jpgs", 7}, 26 {"/api/:layer2", 8}, 27 {"/:layer1/:layer2", 9}, 28 {"/:layer1/:layer2/users", 10}, 29 } 30 31 tests := []struct { 32 query string 33 expect int 34 params map[string]string 35 contains bool 36 }{ 37 { 38 query: "", 39 contains: false, 40 }, 41 { 42 query: "/", 43 expect: 1, 44 contains: true, 45 }, 46 { 47 query: "/wildcard", 48 expect: 4, 49 params: map[string]string{ 50 "layer1": "wildcard", 51 }, 52 contains: true, 53 }, 54 { 55 query: "/wildcard/", 56 expect: 4, 57 params: map[string]string{ 58 "layer1": "wildcard", 59 }, 60 contains: true, 61 }, 62 { 63 query: "/a/b/c", 64 contains: false, 65 }, 66 { 67 query: "/a/b", 68 expect: 9, 69 params: map[string]string{ 70 "layer1": "a", 71 "layer2": "b", 72 }, 73 contains: true, 74 }, 75 { 76 query: "/a/b/", 77 expect: 9, 78 params: map[string]string{ 79 "layer1": "a", 80 "layer2": "b", 81 }, 82 contains: true, 83 }, 84 { 85 query: "/a/b/users", 86 expect: 10, 87 params: map[string]string{ 88 "layer1": "a", 89 "layer2": "b", 90 }, 91 contains: true, 92 }, 93 } 94 95 for _, test := range tests { 96 t.Run(test.query, func(t *testing.T) { 97 tree := NewTree() 98 for _, r := range routes { 99 tree.Add(r.route, r.value) 100 } 101 result, ok := tree.Search(test.query) 102 assert.Equal(t, test.contains, ok) 103 if ok { 104 actual := result.Item.(int) 105 assert.EqualValues(t, test.params, result.Params) 106 assert.Equal(t, test.expect, actual) 107 } 108 }) 109 } 110 } 111 112 func TestStrictSearch(t *testing.T) { 113 routes := []mockedRoute{ 114 {"/api/users", 1}, 115 {"/api/:layer", 2}, 116 } 117 query := "/api/users" 118 119 tree := NewTree() 120 for _, r := range routes { 121 tree.Add(r.route, r.value) 122 } 123 124 for i := 0; i < 1000; i++ { 125 result, ok := tree.Search(query) 126 assert.True(t, ok) 127 assert.Equal(t, 1, result.Item.(int)) 128 } 129 } 130 131 func TestStrictSearchSibling(t *testing.T) { 132 routes := []mockedRoute{ 133 {"/api/:user/profile/name", 1}, 134 {"/api/:user/profile", 2}, 135 {"/api/:user/name", 3}, 136 {"/api/:layer", 4}, 137 } 138 query := "/api/123/name" 139 140 tree := NewTree() 141 for _, r := range routes { 142 tree.Add(r.route, r.value) 143 } 144 145 result, ok := tree.Search(query) 146 assert.True(t, ok) 147 assert.Equal(t, 3, result.Item.(int)) 148 } 149 150 func TestAddDuplicate(t *testing.T) { 151 tree := NewTree() 152 err := tree.Add("/a/b", 1) 153 assert.Nil(t, err) 154 err = tree.Add("/a/b", 2) 155 assert.Error(t, errDupItem, err) 156 err = tree.Add("/a/b/", 2) 157 assert.Error(t, errDupItem, err) 158 } 159 160 func TestPlain(t *testing.T) { 161 tree := NewTree() 162 err := tree.Add("/a/b", 1) 163 assert.Nil(t, err) 164 err = tree.Add("/a/c", 2) 165 assert.Nil(t, err) 166 _, ok := tree.Search("/a/d") 167 assert.False(t, ok) 168 } 169 170 func TestSearchWithDoubleSlashes(t *testing.T) { 171 tree := NewTree() 172 err := tree.Add("//a", 1) 173 assert.Error(t, errDupSlash, err) 174 } 175 176 func TestSearchInvalidRoute(t *testing.T) { 177 tree := NewTree() 178 err := tree.Add("", 1) 179 assert.Equal(t, errNotFromRoot, err) 180 err = tree.Add("bad", 1) 181 assert.Equal(t, errNotFromRoot, err) 182 } 183 184 func TestSearchInvalidItem(t *testing.T) { 185 tree := NewTree() 186 err := tree.Add("/", nil) 187 assert.Equal(t, errEmptyItem, err) 188 } 189 190 func BenchmarkSearchTree(b *testing.B) { 191 const ( 192 avgLen = 1000 193 entries = 10000 194 ) 195 196 tree := NewTree() 197 generate := func() string { 198 var buf strings.Builder 199 size := rand.Intn(avgLen) + avgLen/2 200 val := stringx.Randn(size) 201 prev := 0 202 for j := rand.Intn(9) + 1; j < size; j += rand.Intn(9) + 1 { 203 buf.WriteRune('/') 204 buf.WriteString(val[prev:j]) 205 prev = j 206 } 207 if prev < size { 208 buf.WriteRune('/') 209 buf.WriteString(val[prev:]) 210 } 211 return buf.String() 212 } 213 index := rand.Intn(entries) 214 var query string 215 for i := 0; i < entries; i++ { 216 val := generate() 217 if i == index { 218 query = val 219 } 220 tree.Add(val, i) 221 } 222 223 for i := 0; i < b.N; i++ { 224 tree.Search(query) 225 } 226 }