tree_test.go 4.1 KB

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