Files
tidb/pkg/util/table-rule-selector/selector_test.go
2024-01-29 07:21:29 +00:00

269 lines
8.3 KiB
Go

// Copyright 2022 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selector
import (
"sort"
"testing"
"github.com/stretchr/testify/require"
)
var ts = &testSelectorSuite{
tables: map[string][]string{
"t*": {"test*"},
"schema*": {"", "test*", "abc*", "xyz"},
"?bc": {"t1_abc", "t1_ab?", "abc*"},
"a?c": {"t2_abc", "t2_ab*", "a?b"},
"ab?": {"t3_ab?", "t3_ab*", "ab?"},
"ab*": {"t4_abc", "t4_abc*", "ab*"},
"abc": {"abc"},
"abd": {"abc"},
"ik[hjkl]": {"ik[!zxc]"},
"ik[f-h]": {"ik[!a-ce-g]"},
"i[x-z][1-3]": {"i?[x-z]", "ix*"},
// [\!-\!], [a-a\--\-], [a-c\--\-f-f].
"[!]": {"[a-]", "[a-c-f]"},
// [!a-c\!-\!f-g]
"[!a-c!f-g]": {"*"},
// [] match nothing.
"[]*": {"*"},
},
matchCase: []struct {
schema, table string
matchedNum int
matchedRules []string //schema, table, schema, table...
}{
// test one level
{"dbc", "t1_abc", 2, []string{"?bc", "t1_ab?", "?bc", "t1_abc"}},
{"adc", "t2_abc", 2, []string{"a?c", "t2_ab*", "a?c", "t2_abc"}},
{"abd", "t3_abc", 2, []string{"ab?", "t3_ab*", "ab?", "t3_ab?"}},
{"abc", "t4_abc", 2, []string{"ab*", "t4_abc", "ab*", "t4_abc*"}},
{"abc", "abc", 4, []string{"?bc", "abc*", "ab*", "ab*", "ab?", "ab?", "abc", "abc"}},
// test only schema rule
{"schema1", "xxx", 1, []string{"schema*", ""}},
{"schema1", "", 1, []string{"schema*", ""}},
// test table rule
{"schema1", "test1", 2, []string{"schema*", "", "schema*", "test*"}},
{"t1", "test1", 1, []string{"t*", "test*"}},
{"schema1", "abc1", 2, []string{"schema*", "", "schema*", "abc*"}},
{"ikj", "ikb", 1, []string{"ik[hjkl]", "ik[!zxc]"}},
{"ikh", "iky", 2, []string{"ik[hjkl]", "ik[!zxc]", "ik[f-h]", "ik[!a-ce-g]"}},
{"iz3", "ixz", 2, []string{"i[x-z][1-3]", "i?[x-z]", "i[x-z][1-3]", "ix*"}},
{"!", "-", 2, []string{"[!]", "[a-]", "[!]", "[a-c-f]"}},
{"!", "c", 1, []string{"[!]", "[a-c-f]"}},
{"d", "zxcv", 1, []string{"[!a-c!f-g]", "*"}},
},
removeCases: []string{"schema*", "", "a?c", "t2_ab*", "i[x-z][1-3]", "i?[x-z]", "[!]", "[a-c-f]"},
}
type testSelectorSuite struct {
// for generate rules,
// we use dummy rule which contains schema and table pattern information to simplify test
tables map[string][]string
matchCase []struct {
schema, table string
matchedNum int
// // for generate rules,
// we use dummy rule which contains schema(matchedRules[2*i]) and table(matchedRules[2*i+1]) pattern information to simplify test
matchedRules []string //schema, table, schema, table...
}
removeCases []string //schema, table, schema, table ...
expectedSchemaRules map[string][]any
expectedTableRules map[string]map[string][]any
}
func TestSelector(t *testing.T) {
s := NewTrieSelector()
ts.expectedSchemaRules, ts.expectedTableRules = testGenerateExpectedRules()
testInsert(t, s)
testMatch(t, s)
testAppend(t, s)
testReplace(t, s)
testRemove(t, s)
}
type dummyRule struct {
description string
}
func testInsert(t *testing.T, s Selector) {
var err error
for schema, rules := range ts.expectedSchemaRules {
err = s.Insert(schema, "", rules[0], Insert)
require.NoError(t, err)
// test duplicate error
err = s.Insert(schema, "", rules[0], Insert)
require.Error(t, err)
// test simple replace
err = s.Insert(schema, "", rules[0], Replace)
require.NoError(t, err)
}
for schema, tables := range ts.expectedTableRules {
for table, rules := range tables {
err = s.Insert(schema, table, rules[0], Insert)
require.NoError(t, err)
// test duplicate error
err = s.Insert(schema, table, rules[0], Insert)
require.Error(t, err)
// test simple replace
err = s.Insert(schema, table, rules[0], Replace)
require.NoError(t, err)
}
}
// insert wrong pattern
// rule can't be nil
err = s.Insert("schema", "", nil, Replace)
require.Error(t, err)
// asterisk must be the last character of pattern
err = s.Insert("ab**", "", &dummyRule{"error"}, Replace)
require.Error(t, err)
err = s.Insert("abcd", "ab**", &dummyRule{"error"}, Replace)
require.Error(t, err)
schemas, tables := s.AllRules()
require.EqualValues(t, schemas, ts.expectedSchemaRules)
require.EqualValues(t, tables, ts.expectedTableRules)
}
func testRemove(t *testing.T, s Selector) {
for i := 0; i < len(ts.removeCases); i += 2 {
schema, table := ts.removeCases[i], ts.removeCases[i+1]
err := s.Remove(schema, table)
require.NoError(t, err)
err = s.Remove(schema, table)
require.Error(t, err)
if len(table) == 0 {
delete(ts.expectedSchemaRules, schema)
} else {
rules, ok := ts.expectedTableRules[schema]
require.True(t, ok)
delete(rules, table)
}
}
schemas, tables := s.AllRules()
require.EqualValues(t, schemas, ts.expectedSchemaRules)
require.EqualValues(t, tables, ts.expectedTableRules)
}
func testAppend(t *testing.T, s Selector) {
var (
err error
appendedRule = &dummyRule{description: "append"}
)
for schema := range ts.expectedSchemaRules {
ts.expectedSchemaRules[schema] = append(ts.expectedSchemaRules[schema], appendedRule)
err = s.Insert(schema, "", appendedRule, Append)
require.NoError(t, err)
}
schemas, tables := s.AllRules()
require.EqualValues(t, schemas, ts.expectedSchemaRules)
require.EqualValues(t, tables, ts.expectedTableRules)
}
func testReplace(t *testing.T, s Selector) {
var (
err error
replacedRule = &dummyRule{"replace"}
)
for schema := range ts.expectedSchemaRules {
ts.expectedSchemaRules[schema] = []any{replacedRule}
// to prevent it doesn't exist
err = s.Insert(schema, "", replacedRule, Replace)
require.NoError(t, err)
// test replace
err = s.Insert(schema, "", replacedRule, Replace)
require.NoError(t, err)
err = s.Insert(schema, "", replacedRule, Insert)
require.Error(t, err)
}
schemas, tables := s.AllRules()
require.EqualValues(t, schemas, ts.expectedSchemaRules)
require.EqualValues(t, tables, ts.expectedTableRules)
}
func testMatch(t *testing.T, s Selector) {
cache := make(map[string]RuleSet)
for _, mc := range ts.matchCase {
rules := s.Match(mc.schema, mc.table)
expectedRules := make(RuleSet, 0, mc.matchedNum)
for i := 0; i < mc.matchedNum; i++ {
rule := &dummyRule{quoteSchemaTable(mc.matchedRules[2*i], mc.matchedRules[2*i+1])}
expectedRules = append(expectedRules, rule)
}
sort.Slice(expectedRules, func(i, j int) bool {
return expectedRules[i].(*dummyRule).description < expectedRules[j].(*dummyRule).description
})
sort.Slice(rules, func(i, j int) bool {
return rules[i].(*dummyRule).description < rules[j].(*dummyRule).description
})
require.EqualValues(t, rules, expectedRules)
cache[quoteSchemaTable(mc.schema, mc.table)] = expectedRules
}
// test cache
trie, ok := s.(*trieSelector)
require.True(t, ok)
for _, cacheItem := range trie.cache {
sort.Slice(cacheItem, func(i, j int) bool {
return cacheItem[i].(*dummyRule).description < cacheItem[j].(*dummyRule).description
})
}
require.EqualValues(t, trie.cache, cache)
// test not mathced
rule := s.Match("t1", "")
require.Nil(t, rule)
cache[quoteSchemaTable("t1", "")] = rule
rule = s.Match("t1", "abc")
require.Nil(t, rule)
cache[quoteSchemaTable("t1", "abc")] = rule
rule = s.Match("xxx", "abc")
require.Nil(t, rule)
cache[quoteSchemaTable("xxx", "abc")] = rule
require.EqualValues(t, trie.cache, cache)
}
func testGenerateExpectedRules() (map[string][]any, map[string]map[string][]any) {
schemaRules := make(map[string][]any)
tableRules := make(map[string]map[string][]any)
for schema, tables := range ts.tables {
_, ok := tableRules[schema]
if !ok {
tableRules[schema] = make(map[string][]any)
}
for _, table := range tables {
if len(table) == 0 {
schemaRules[schema] = []any{&dummyRule{quoteSchemaTable(schema, "")}}
} else {
tableRules[schema][table] = []any{&dummyRule{quoteSchemaTable(schema, table)}}
}
}
}
return schemaRules, tableRules
}