xdsrouting: all matchers (#3733)

This commit is contained in:
Menghan Li
2020-07-16 16:05:47 -07:00
committed by GitHub
parent 08e6bb1878
commit e2f575e56f
7 changed files with 1066 additions and 0 deletions

View File

@ -0,0 +1,20 @@
/*
*
* Copyright 2020 gRPC authors.
*
* 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 xdsrouting implements the routing balancer for xds.
package xdsrouting

View File

@ -0,0 +1,134 @@
/*
*
* Copyright 2020 gRPC authors.
*
* 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 xdsrouting
import (
"fmt"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/internal/grpcrand"
"google.golang.org/grpc/metadata"
)
// compositeMatcher.match returns true if all matchers return true.
type compositeMatcher struct {
pm pathMatcherInterface
hms []headerMatcherInterface
fm *fractionMatcher
}
func newCompositeMatcher(pm pathMatcherInterface, hms []headerMatcherInterface, fm *fractionMatcher) *compositeMatcher {
return &compositeMatcher{pm: pm, hms: hms, fm: fm}
}
func (a *compositeMatcher) match(info balancer.PickInfo) bool {
if a.pm != nil && !a.pm.match(info.FullMethodName) {
return false
}
// Call headerMatchers even if md is nil, because routes may match
// non-presence of some headers.
var md metadata.MD
if info.Ctx != nil {
md, _ = metadata.FromOutgoingContext(info.Ctx)
}
for _, m := range a.hms {
if !m.match(md) {
return false
}
}
if a.fm != nil && !a.fm.match() {
return false
}
return true
}
func (a *compositeMatcher) equal(mm *compositeMatcher) bool {
if a == mm {
return true
}
if a == nil || mm == nil {
return false
}
if (a.pm != nil || mm.pm != nil) && (a.pm == nil || !a.pm.equal(mm.pm)) {
return false
}
if len(a.hms) != len(mm.hms) {
return false
}
for i := range a.hms {
if !a.hms[i].equal(mm.hms[i]) {
return false
}
}
if (a.fm != nil || mm.fm != nil) && (a.fm == nil || !a.fm.equal(mm.fm)) {
return false
}
return true
}
func (a *compositeMatcher) String() string {
var ret string
if a.pm != nil {
ret += a.pm.String()
}
for _, m := range a.hms {
ret += m.String()
}
if a.fm != nil {
ret += a.fm.String()
}
return ret
}
type fractionMatcher struct {
fraction int64 // real fraction is fraction/1,000,000.
}
func newFractionMatcher(fraction uint32) *fractionMatcher {
return &fractionMatcher{fraction: int64(fraction)}
}
var grpcrandInt63n = grpcrand.Int63n
func (fm *fractionMatcher) match() bool {
t := grpcrandInt63n(1000000)
return t <= fm.fraction
}
func (fm *fractionMatcher) equal(m *fractionMatcher) bool {
if fm == m {
return true
}
if fm == nil || m == nil {
return false
}
return fm.fraction == m.fraction
}
func (fm *fractionMatcher) String() string {
return fmt.Sprintf("fraction:%v", fm.fraction)
}

View File

@ -0,0 +1,245 @@
/*
*
* Copyright 2020 gRPC authors.
*
* 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 xdsrouting
import (
"fmt"
"regexp"
"strconv"
"strings"
"google.golang.org/grpc/metadata"
)
type headerMatcherInterface interface {
match(metadata.MD) bool
equal(headerMatcherInterface) bool
String() string
}
// mdValuesFromOutgoingCtx retrieves metadata from context. If there are
// multiple values, the values are concatenated with "," (comma and no space).
//
// All header matchers only match against the comma-concatenated string.
func mdValuesFromOutgoingCtx(md metadata.MD, key string) (string, bool) {
vs, ok := md[key]
if !ok {
return "", false
}
return strings.Join(vs, ","), true
}
type headerExactMatcher struct {
key string
exact string
}
func newHeaderExactMatcher(key, exact string) *headerExactMatcher {
return &headerExactMatcher{key: key, exact: exact}
}
func (hem *headerExactMatcher) match(md metadata.MD) bool {
v, ok := mdValuesFromOutgoingCtx(md, hem.key)
if !ok {
return false
}
return v == hem.exact
}
func (hem *headerExactMatcher) equal(m headerMatcherInterface) bool {
mm, ok := m.(*headerExactMatcher)
if !ok {
return false
}
return hem.key == mm.key && hem.exact == mm.exact
}
func (hem *headerExactMatcher) String() string {
return fmt.Sprintf("headerExact:%v:%v", hem.key, hem.exact)
}
type headerRegexMatcher struct {
key string
re *regexp.Regexp
}
func newHeaderRegexMatcher(key string, re *regexp.Regexp) *headerRegexMatcher {
return &headerRegexMatcher{key: key, re: re}
}
func (hrm *headerRegexMatcher) match(md metadata.MD) bool {
v, ok := mdValuesFromOutgoingCtx(md, hrm.key)
if !ok {
return false
}
return hrm.re.MatchString(v)
}
func (hrm *headerRegexMatcher) equal(m headerMatcherInterface) bool {
mm, ok := m.(*headerRegexMatcher)
if !ok {
return false
}
return hrm.key == mm.key && hrm.re.String() == mm.re.String()
}
func (hrm *headerRegexMatcher) String() string {
return fmt.Sprintf("headerRegex:%v:%v", hrm.key, hrm.re.String())
}
type headerRangeMatcher struct {
key string
start, end int64 // represents [start, end).
}
func newHeaderRangeMatcher(key string, start, end int64) *headerRangeMatcher {
return &headerRangeMatcher{key: key, start: start, end: end}
}
func (hrm *headerRangeMatcher) match(md metadata.MD) bool {
v, ok := mdValuesFromOutgoingCtx(md, hrm.key)
if !ok {
return false
}
if i, err := strconv.ParseInt(v, 10, 64); err == nil && i >= hrm.start && i < hrm.end {
return true
}
return false
}
func (hrm *headerRangeMatcher) equal(m headerMatcherInterface) bool {
mm, ok := m.(*headerRangeMatcher)
if !ok {
return false
}
return hrm.key == mm.key && hrm.start == mm.start && hrm.end == mm.end
}
func (hrm *headerRangeMatcher) String() string {
return fmt.Sprintf("headerRange:%v:[%d,%d)", hrm.key, hrm.start, hrm.end)
}
type headerPresentMatcher struct {
key string
present bool
}
func newHeaderPresentMatcher(key string, present bool) *headerPresentMatcher {
return &headerPresentMatcher{key: key, present: present}
}
func (hpm *headerPresentMatcher) match(md metadata.MD) bool {
vs, ok := mdValuesFromOutgoingCtx(md, hpm.key)
present := ok && len(vs) > 0
return present == hpm.present
}
func (hpm *headerPresentMatcher) equal(m headerMatcherInterface) bool {
mm, ok := m.(*headerPresentMatcher)
if !ok {
return false
}
return hpm.key == mm.key && hpm.present == mm.present
}
func (hpm *headerPresentMatcher) String() string {
return fmt.Sprintf("headerPresent:%v:%v", hpm.key, hpm.present)
}
type headerPrefixMatcher struct {
key string
prefix string
}
func newHeaderPrefixMatcher(key string, prefix string) *headerPrefixMatcher {
return &headerPrefixMatcher{key: key, prefix: prefix}
}
func (hpm *headerPrefixMatcher) match(md metadata.MD) bool {
v, ok := mdValuesFromOutgoingCtx(md, hpm.key)
if !ok {
return false
}
return strings.HasPrefix(v, hpm.prefix)
}
func (hpm *headerPrefixMatcher) equal(m headerMatcherInterface) bool {
mm, ok := m.(*headerPrefixMatcher)
if !ok {
return false
}
return hpm.key == mm.key && hpm.prefix == mm.prefix
}
func (hpm *headerPrefixMatcher) String() string {
return fmt.Sprintf("headerPrefix:%v:%v", hpm.key, hpm.prefix)
}
type headerSuffixMatcher struct {
key string
suffix string
}
func newHeaderSuffixMatcher(key string, suffix string) *headerSuffixMatcher {
return &headerSuffixMatcher{key: key, suffix: suffix}
}
func (hsm *headerSuffixMatcher) match(md metadata.MD) bool {
v, ok := mdValuesFromOutgoingCtx(md, hsm.key)
if !ok {
return false
}
return strings.HasSuffix(v, hsm.suffix)
}
func (hsm *headerSuffixMatcher) equal(m headerMatcherInterface) bool {
mm, ok := m.(*headerSuffixMatcher)
if !ok {
return false
}
return hsm.key == mm.key && hsm.suffix == mm.suffix
}
func (hsm *headerSuffixMatcher) String() string {
return fmt.Sprintf("headerSuffix:%v:%v", hsm.key, hsm.suffix)
}
type invertMatcher struct {
m headerMatcherInterface
}
func newInvertMatcher(m headerMatcherInterface) *invertMatcher {
return &invertMatcher{m: m}
}
func (i *invertMatcher) match(md metadata.MD) bool {
return !i.m.match(md)
}
func (i *invertMatcher) equal(m headerMatcherInterface) bool {
mm, ok := m.(*invertMatcher)
if !ok {
return false
}
return i.m.equal(mm.m)
}
func (i *invertMatcher) String() string {
return fmt.Sprintf("invert{%s}", i.m)
}

View File

@ -0,0 +1,333 @@
/*
*
* Copyright 2020 gRPC authors.
*
* 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 xdsrouting
import (
"regexp"
"testing"
"google.golang.org/grpc/metadata"
)
func TestHeaderExactMatcherMatch(t *testing.T) {
tests := []struct {
name string
key, exact string
md metadata.MD
want bool
}{
{
name: "one value one match",
key: "th",
exact: "tv",
md: metadata.Pairs("th", "tv"),
want: true,
},
{
name: "two value one match",
key: "th",
exact: "tv",
md: metadata.Pairs("th", "abc", "th", "tv"),
// Doesn't match comma-concatenated string.
want: false,
},
{
name: "two value match concatenated",
key: "th",
exact: "abc,tv",
md: metadata.Pairs("th", "abc", "th", "tv"),
want: true,
},
{
name: "not match",
key: "th",
exact: "tv",
md: metadata.Pairs("th", "abc"),
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hem := newHeaderExactMatcher(tt.key, tt.exact)
if got := hem.match(tt.md); got != tt.want {
t.Errorf("match() = %v, want %v", got, tt.want)
}
})
}
}
func TestHeaderRegexMatcherMatch(t *testing.T) {
tests := []struct {
name string
key, regexStr string
md metadata.MD
want bool
}{
{
name: "one value one match",
key: "th",
regexStr: "^t+v*$",
md: metadata.Pairs("th", "tttvv"),
want: true,
},
{
name: "two value one match",
key: "th",
regexStr: "^t+v*$",
md: metadata.Pairs("th", "abc", "th", "tttvv"),
want: false,
},
{
name: "two value match concatenated",
key: "th",
regexStr: "^[abc]*,t+v*$",
md: metadata.Pairs("th", "abc", "th", "tttvv"),
want: true,
},
{
name: "no match",
key: "th",
regexStr: "^t+v*$",
md: metadata.Pairs("th", "abc"),
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hrm := newHeaderRegexMatcher(tt.key, regexp.MustCompile(tt.regexStr))
if got := hrm.match(tt.md); got != tt.want {
t.Errorf("match() = %v, want %v", got, tt.want)
}
})
}
}
func TestHeaderRangeMatcherMatch(t *testing.T) {
tests := []struct {
name string
key string
start, end int64
md metadata.MD
want bool
}{
{
name: "match",
key: "th",
start: 1, end: 10,
md: metadata.Pairs("th", "5"),
want: true,
},
{
name: "equal to start",
key: "th",
start: 1, end: 10,
md: metadata.Pairs("th", "1"),
want: true,
},
{
name: "equal to end",
key: "th",
start: 1, end: 10,
md: metadata.Pairs("th", "10"),
want: false,
},
{
name: "negative",
key: "th",
start: -10, end: 10,
md: metadata.Pairs("th", "-5"),
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hrm := newHeaderRangeMatcher(tt.key, tt.start, tt.end)
if got := hrm.match(tt.md); got != tt.want {
t.Errorf("match() = %v, want %v", got, tt.want)
}
})
}
}
func TestHeaderPresentMatcherMatch(t *testing.T) {
tests := []struct {
name string
key string
present bool
md metadata.MD
want bool
}{
{
name: "want present is present",
key: "th",
present: true,
md: metadata.Pairs("th", "tv"),
want: true,
},
{
name: "want present not present",
key: "th",
present: true,
md: metadata.Pairs("abc", "tv"),
want: false,
},
{
name: "want not present is present",
key: "th",
present: false,
md: metadata.Pairs("th", "tv"),
want: false,
},
{
name: "want not present is not present",
key: "th",
present: false,
md: metadata.Pairs("abc", "tv"),
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hpm := newHeaderPresentMatcher(tt.key, tt.present)
if got := hpm.match(tt.md); got != tt.want {
t.Errorf("match() = %v, want %v", got, tt.want)
}
})
}
}
func TestHeaderPrefixMatcherMatch(t *testing.T) {
tests := []struct {
name string
key, prefix string
md metadata.MD
want bool
}{
{
name: "one value one match",
key: "th",
prefix: "tv",
md: metadata.Pairs("th", "tv123"),
want: true,
},
{
name: "two value one match",
key: "th",
prefix: "tv",
md: metadata.Pairs("th", "abc", "th", "tv123"),
want: false,
},
{
name: "two value match concatenated",
key: "th",
prefix: "tv",
md: metadata.Pairs("th", "tv123", "th", "abc"),
want: true,
},
{
name: "not match",
key: "th",
prefix: "tv",
md: metadata.Pairs("th", "abc"),
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hpm := newHeaderPrefixMatcher(tt.key, tt.prefix)
if got := hpm.match(tt.md); got != tt.want {
t.Errorf("match() = %v, want %v", got, tt.want)
}
})
}
}
func TestHeaderSuffixMatcherMatch(t *testing.T) {
tests := []struct {
name string
key, suffix string
md metadata.MD
want bool
}{
{
name: "one value one match",
key: "th",
suffix: "tv",
md: metadata.Pairs("th", "123tv"),
want: true,
},
{
name: "two value one match",
key: "th",
suffix: "tv",
md: metadata.Pairs("th", "123tv", "th", "abc"),
want: false,
},
{
name: "two value match concatenated",
key: "th",
suffix: "tv",
md: metadata.Pairs("th", "abc", "th", "123tv"),
want: true,
},
{
name: "not match",
key: "th",
suffix: "tv",
md: metadata.Pairs("th", "abc"),
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hsm := newHeaderSuffixMatcher(tt.key, tt.suffix)
if got := hsm.match(tt.md); got != tt.want {
t.Errorf("match() = %v, want %v", got, tt.want)
}
})
}
}
func TestInvertMatcherMatch(t *testing.T) {
tests := []struct {
name string
m headerMatcherInterface
md metadata.MD
}{
{
name: "true->false",
m: newHeaderExactMatcher("th", "tv"),
md: metadata.Pairs("th", "tv"),
},
{
name: "false->true",
m: newHeaderExactMatcher("th", "abc"),
md: metadata.Pairs("th", "tv"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := newInvertMatcher(tt.m).match(tt.md)
want := !tt.m.match(tt.md)
if got != want {
t.Errorf("match() = %v, want %v", got, want)
}
})
}
}

View File

@ -0,0 +1,102 @@
/*
*
* Copyright 2020 gRPC authors.
*
* 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 xdsrouting
import (
"regexp"
"strings"
)
type pathMatcherInterface interface {
match(path string) bool
equal(pathMatcherInterface) bool
String() string
}
type pathExactMatcher struct {
fullPath string
}
func newPathExactMatcher(p string) *pathExactMatcher {
return &pathExactMatcher{fullPath: p}
}
func (pem *pathExactMatcher) match(path string) bool {
return pem.fullPath == path
}
func (pem *pathExactMatcher) equal(m pathMatcherInterface) bool {
mm, ok := m.(*pathExactMatcher)
if !ok {
return false
}
return pem.fullPath == mm.fullPath
}
func (pem *pathExactMatcher) String() string {
return "pathExact:" + pem.fullPath
}
type pathPrefixMatcher struct {
prefix string
}
func newPathPrefixMatcher(p string) *pathPrefixMatcher {
return &pathPrefixMatcher{prefix: p}
}
func (ppm *pathPrefixMatcher) match(path string) bool {
return strings.HasPrefix(path, ppm.prefix)
}
func (ppm *pathPrefixMatcher) equal(m pathMatcherInterface) bool {
mm, ok := m.(*pathPrefixMatcher)
if !ok {
return false
}
return ppm.prefix == mm.prefix
}
func (ppm *pathPrefixMatcher) String() string {
return "pathPrefix:" + ppm.prefix
}
type pathRegexMatcher struct {
re *regexp.Regexp
}
func newPathRegexMatcher(re *regexp.Regexp) *pathRegexMatcher {
return &pathRegexMatcher{re: re}
}
func (prm *pathRegexMatcher) match(path string) bool {
return prm.re.MatchString(path)
}
func (prm *pathRegexMatcher) equal(m pathMatcherInterface) bool {
mm, ok := m.(*pathRegexMatcher)
if !ok {
return false
}
return prm.re.String() == mm.re.String()
}
func (prm *pathRegexMatcher) String() string {
return "pathRegex:" + prm.re.String()
}

View File

@ -0,0 +1,84 @@
/*
*
* Copyright 2020 gRPC authors.
*
* 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 xdsrouting
import (
"regexp"
"testing"
)
func TestPathFullMatcherMatch(t *testing.T) {
tests := []struct {
name string
fullPath string
path string
want bool
}{
{name: "match", fullPath: "/s/m", path: "/s/m", want: true},
{name: "not match", fullPath: "/s/m", path: "/a/b", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fpm := newPathExactMatcher(tt.fullPath)
if got := fpm.match(tt.path); got != tt.want {
t.Errorf("{%q}.match(%q) = %v, want %v", tt.fullPath, tt.path, got, tt.want)
}
})
}
}
func TestPathPrefixMatcherMatch(t *testing.T) {
tests := []struct {
name string
prefix string
path string
want bool
}{
{name: "match", prefix: "/s/", path: "/s/m", want: true},
{name: "not match", prefix: "/s/", path: "/a/b", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fpm := newPathPrefixMatcher(tt.prefix)
if got := fpm.match(tt.path); got != tt.want {
t.Errorf("{%q}.match(%q) = %v, want %v", tt.prefix, tt.path, got, tt.want)
}
})
}
}
func TestPathRegexMatcherMatch(t *testing.T) {
tests := []struct {
name string
regexPath string
path string
want bool
}{
{name: "match", regexPath: "^/s+/m.*$", path: "/sss/me", want: true},
{name: "not match", regexPath: "^/s+/m*$", path: "/sss/b", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fpm := newPathRegexMatcher(regexp.MustCompile(tt.regexPath))
if got := fpm.match(tt.path); got != tt.want {
t.Errorf("{%q}.match(%q) = %v, want %v", tt.regexPath, tt.path, got, tt.want)
}
})
}
}

View File

@ -0,0 +1,148 @@
/*
*
* Copyright 2020 gRPC authors.
*
* 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 xdsrouting
import (
"context"
"testing"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/internal/grpcrand"
"google.golang.org/grpc/metadata"
)
func TestAndMatcherMatch(t *testing.T) {
tests := []struct {
name string
pm pathMatcherInterface
hm headerMatcherInterface
info balancer.PickInfo
want bool
}{
{
name: "both match",
pm: newPathExactMatcher("/a/b"),
hm: newHeaderExactMatcher("th", "tv"),
info: balancer.PickInfo{
FullMethodName: "/a/b",
Ctx: metadata.NewOutgoingContext(context.Background(), metadata.Pairs("th", "tv")),
},
want: true,
},
{
name: "only one match",
pm: newPathExactMatcher("/a/b"),
hm: newHeaderExactMatcher("th", "tv"),
info: balancer.PickInfo{
FullMethodName: "/z/y",
Ctx: metadata.NewOutgoingContext(context.Background(), metadata.Pairs("th", "tv")),
},
want: false,
},
{
name: "both not match",
pm: newPathExactMatcher("/z/y"),
hm: newHeaderExactMatcher("th", "abc"),
info: balancer.PickInfo{
FullMethodName: "/a/b",
Ctx: metadata.NewOutgoingContext(context.Background(), metadata.Pairs("th", "tv")),
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := newCompositeMatcher(tt.pm, []headerMatcherInterface{tt.hm}, nil)
if got := a.match(tt.info); got != tt.want {
t.Errorf("match() = %v, want %v", got, tt.want)
}
})
}
}
func TestFractionMatcherMatch(t *testing.T) {
const fraction = 500000
fm := newFractionMatcher(fraction)
defer func() {
grpcrandInt63n = grpcrand.Int63n
}()
// rand > fraction, should return false.
grpcrandInt63n = func(n int64) int64 {
return fraction + 1
}
if matched := fm.match(); matched {
t.Errorf("match() = %v, want not match", matched)
}
// rand == fraction, should return true.
grpcrandInt63n = func(n int64) int64 {
return fraction
}
if matched := fm.match(); !matched {
t.Errorf("match() = %v, want match", matched)
}
// rand < fraction, should return true.
grpcrandInt63n = func(n int64) int64 {
return fraction - 1
}
if matched := fm.match(); !matched {
t.Errorf("match() = %v, want match", matched)
}
}
func TestCompositeMatcherEqual(t *testing.T) {
tests := []struct {
name string
pm pathMatcherInterface
hms []headerMatcherInterface
fm *fractionMatcher
mm *compositeMatcher
want bool
}{
{
name: "equal",
pm: newPathExactMatcher("/a/b"),
mm: newCompositeMatcher(newPathExactMatcher("/a/b"), nil, nil),
want: true,
},
{
name: "no path matcher",
pm: nil,
mm: newCompositeMatcher(nil, nil, nil),
want: true,
},
{
name: "not equal",
pm: newPathExactMatcher("/a/b"),
fm: newFractionMatcher(123),
mm: newCompositeMatcher(newPathExactMatcher("/a/b"), nil, nil),
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := newCompositeMatcher(tt.pm, tt.hms, tt.fm)
if got := a.equal(tt.mm); got != tt.want {
t.Errorf("equal() = %v, want %v", got, tt.want)
}
})
}
}