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  }