From 49510041cbc07597b3663281b8efbab29ccf1f11 Mon Sep 17 00:00:00 2001 From: Xi Chen Date: Wed, 10 May 2017 23:50:32 -0400 Subject: [PATCH] Improve test coverage --- filters/tags_filter.go | 10 ++- filters/tags_filter_test.go | 72 ++++++++++++++- metric/id/m3/id.go | 8 +- metric/id/m3/id_test.go | 172 ++++++++++++++++++++++++++++++++++++ metric/id/tag.go | 2 +- metric/id/tag_test.go | 39 ++++++++ 6 files changed, 294 insertions(+), 9 deletions(-) create mode 100644 metric/id/m3/id_test.go create mode 100644 metric/id/tag_test.go diff --git a/filters/tags_filter.go b/filters/tags_filter.go index e680d98..22333ab 100644 --- a/filters/tags_filter.go +++ b/filters/tags_filter.go @@ -126,8 +126,14 @@ func (f *tagsFilter) Matches(id []byte) bool { if err != nil { return false } - if f.nameFilter != nil && !f.nameFilter.Matches(name) { - return false + if f.nameFilter != nil { + match := f.nameFilter.Matches(name) + if match && f.op == Disjunction { + return true + } + if !match && f.op == Conjunction { + return false + } } iter := f.opts.SortedTagIteratorFn(tags) diff --git a/filters/tags_filter_test.go b/filters/tags_filter_test.go index deef8ef..930949c 100644 --- a/filters/tags_filter_test.go +++ b/filters/tags_filter_test.go @@ -21,6 +21,8 @@ package filters import ( + "bytes" + "errors" "testing" "github.com/stretchr/testify/require" @@ -32,7 +34,7 @@ func TestEmptyTagsFilterMatches(t *testing.T) { require.True(t, f.Matches([]byte("foo"))) } -func TestTagsFilterMatches(t *testing.T) { +func TestTagsFilterMatchesNoNameTag(t *testing.T) { filters := map[string]string{ "tagName1": "tagValue1", "tagName2": "tagValue2", @@ -68,7 +70,44 @@ func TestTagsFilterMatches(t *testing.T) { } } -func TestTagsFilterString(t *testing.T) { +func TestTagsFilterMatchesWithNameTag(t *testing.T) { + filters := map[string]string{ + "name": "foo", + "tagName1": "tagValue1", + "tagName2": "tagValue2", + } + + f, err := NewTagsFilter(filters, Conjunction, testTagsFilterOptionsWithNameTag()) + require.NoError(t, err) + inputs := []mockFilterData{ + {val: "foo+tagName0=tagValue0,tagName1=tagValue1,tagName2=tagValue2", match: true}, + {val: "tagName1=tagValue1,tagName2=tagValue2", match: false}, + {val: "foo+tagName1=tagValue1", match: false}, + {val: "foo+tagName1=tagValue2,tagName2=tagValue1", match: false}, + } + for _, input := range inputs { + require.Equal(t, input.match, f.Matches([]byte(input.val))) + } + + f, err = NewTagsFilter(filters, Disjunction, testTagsFilterOptionsWithNameTag()) + require.NoError(t, err) + inputs = []mockFilterData{ + {val: "foo+tagName1=tagValue1,tagName2=tagValue2", match: true}, + {val: "foo+tagName1=tagValue2,tagName2=tagValue2", match: true}, + {val: "bar+tagName1=tagValue1", match: true}, + {val: "foo+tagName1=tagValue2", match: true}, + {val: "foo+tagName2=tagValue1", match: true}, + {val: "foo+tagName15=tagValue2,tagName3=tagValue2", match: true}, + {val: "tagName1=tagValue1,tagName2=tagValue2", match: false}, + {val: "bar+tagName1=tagValue2,tagName2=tagValue1", match: false}, + {val: "bar+tagName3=tagValue3", match: false}, + } + for _, input := range inputs { + require.Equal(t, input.match, f.Matches([]byte(input.val))) + } +} + +func TestTagsFilterStringNoNameTag(t *testing.T) { filters := map[string]string{ "tagName1": "tagValue1", "tagName2": "tagValue2", @@ -82,6 +121,21 @@ func TestTagsFilterString(t *testing.T) { require.Equal(t, `tagName1:Equals("tagValue1") || tagName2:Equals("tagValue2")`, f.String()) } +func TestTagsFilterStringWithNameTag(t *testing.T) { + filters := map[string]string{ + "name": "foo", + "tagName1": "tagValue1", + "tagName2": "tagValue2", + } + f, err := NewTagsFilter(filters, Conjunction, testTagsFilterOptionsWithNameTag()) + require.NoError(t, err) + require.Equal(t, `name:Equals("foo") && tagName1:Equals("tagValue1") && tagName2:Equals("tagValue2")`, f.String()) + + f, err = NewTagsFilter(filters, Disjunction, testTagsFilterOptionsWithNameTag()) + require.NoError(t, err) + require.Equal(t, `name:Equals("foo") || tagName1:Equals("tagValue1") || tagName2:Equals("tagValue2")`, f.String()) +} + func testTagsFilterOptions() TagsFilterOptions { return TagsFilterOptions{ NameTagKey: []byte("name"), @@ -89,3 +143,17 @@ func testTagsFilterOptions() TagsFilterOptions { SortedTagIteratorFn: NewMockSortedTagIterator, } } + +func testTagsFilterOptionsWithNameTag() TagsFilterOptions { + return TagsFilterOptions{ + NameTagKey: []byte("name"), + NameAndTagsFn: func(b []byte) ([]byte, []byte, error) { + idx := bytes.IndexByte(b, '+') + if idx == -1 { + return nil, nil, errors.New("invalid metric") + } + return b[:idx], b[idx+1:], nil + }, + SortedTagIteratorFn: NewMockSortedTagIterator, + } +} diff --git a/metric/id/m3/id.go b/metric/id/m3/id.go index ae92303..31dc4c9 100644 --- a/metric/id/m3/id.go +++ b/metric/id/m3/id.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Uber Technologies, Inc. +// Copyright (c) 2017 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -33,7 +33,7 @@ package m3 import ( "bytes" - "fmt" + "errors" "sort" "github.com/m3db/m3metrics/metric/id" @@ -46,7 +46,7 @@ const ( ) var ( - errInvalidM3Metric = fmt.Errorf("invalid m3 metric") + errInvalidM3Metric = errors.New("invalid m3 metric") m3Prefix = []byte("m3+") rollupTagPair = id.TagPair{ Name: []byte("m3_rollup"), @@ -173,7 +173,7 @@ func (it *sortedTagIterator) Next() bool { } it.tagName = it.sortedTagPairs[it.idx:nameSplitterIdx] it.tagValue = it.sortedTagPairs[nameSplitterIdx+1 : pairSplitterIdx] - it.idx = pairSplitterIdx + it.idx = pairSplitterIdx + 1 return true } diff --git a/metric/id/m3/id_test.go b/metric/id/m3/id_test.go new file mode 100644 index 0000000..c7f69ac --- /dev/null +++ b/metric/id/m3/id_test.go @@ -0,0 +1,172 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package m3 + +import ( + "testing" + + "github.com/m3db/m3metrics/metric/id" + + "github.com/stretchr/testify/require" +) + +func TestNewRollupID(t *testing.T) { + var ( + name = []byte("foo") + tagPairs = []id.TagPair{ + {Name: []byte("tagName1"), Value: []byte("tagValue1")}, + {Name: []byte("tagName2"), Value: []byte("tagValue2")}, + {Name: []byte("tagName0"), Value: []byte("tagValue0")}, + } + ) + expected := []byte("m3+foo+m3_rollup=true,tagName0=tagValue0,tagName1=tagValue1,tagName2=tagValue2") + require.Equal(t, expected, NewRollupID(name, tagPairs)) +} + +func TestMetricIDTagValue(t *testing.T) { + iterPool := id.NewSortedTagIteratorPool(nil) + iterPool.Init(func() id.SortedTagIterator { + return NewPooledSortedTagIterator(nil, iterPool) + }) + inputs := []struct { + id id.ID + tagName []byte + expectedValue []byte + expectedFound bool + }{ + { + id: NewID([]byte("m3+foo+tagName1=tagValue1,tagName2=tagValue2"), iterPool), + tagName: []byte("tagName1"), + expectedValue: []byte("tagValue1"), + expectedFound: true, + }, + { + id: NewID([]byte("m3+foo+tagName1=tagValue1,tagName2=tagValue2"), iterPool), + tagName: []byte("tagName2"), + expectedValue: []byte("tagValue2"), + expectedFound: true, + }, + { + id: NewID([]byte("m3+foo+tagName1=tagValue1,tagName2=tagValue2"), iterPool), + tagName: []byte("tagName3"), + expectedValue: nil, + expectedFound: false, + }, + { + id: NewID([]byte("illformed+tagName1=tagValue1,tagName2=tagValue2"), iterPool), + tagName: []byte("tagName1"), + expectedValue: nil, + expectedFound: false, + }, + } + for _, input := range inputs { + value, found := input.id.TagValue(input.tagName) + require.Equal(t, input.expectedValue, value) + require.Equal(t, input.expectedFound, found) + } +} + +func TestNameAndTags(t *testing.T) { + inputs := []struct { + id []byte + expectedName []byte + expectedTags []byte + expectedErr error + }{ + { + id: []byte("m3+foo+tagName1=tagValue1"), + expectedName: []byte("foo"), + expectedTags: []byte("tagName1=tagValue1"), + expectedErr: nil, + }, + { + id: []byte("m3+foo+tagName1=tagValue1,tagName2=tagValue2"), + expectedName: []byte("foo"), + expectedTags: []byte("tagName1=tagValue1,tagName2=tagValue2"), + expectedErr: nil, + }, + { + id: []byte("illformed"), + expectedName: nil, + expectedTags: nil, + expectedErr: errInvalidM3Metric, + }, + { + id: []byte("m3+illformed"), + expectedName: nil, + expectedTags: nil, + expectedErr: errInvalidM3Metric, + }, + { + id: []byte("m3+illformed+tagName1,"), + expectedName: []byte("illformed"), + expectedTags: []byte("tagName1,"), + expectedErr: nil, + }, + } + for _, input := range inputs { + name, tags, err := NameAndTags(input.id) + require.Equal(t, input.expectedName, name) + require.Equal(t, input.expectedTags, tags) + require.Equal(t, input.expectedErr, err) + } +} + +func TestSortedTagIterator(t *testing.T) { + inputs := []struct { + sortedTagPairs []byte + expectedPairs []id.TagPair + expectedErr error + }{ + { + sortedTagPairs: []byte("tagName1=tagValue1"), + expectedPairs: []id.TagPair{ + {Name: []byte("tagName1"), Value: []byte("tagValue1")}, + }, + expectedErr: nil, + }, + { + sortedTagPairs: []byte("tagName1=tagValue1,tagName2=tagValue2,tagName3=tagValue3"), + expectedPairs: []id.TagPair{ + {Name: []byte("tagName1"), Value: []byte("tagValue1")}, + {Name: []byte("tagName2"), Value: []byte("tagValue2")}, + {Name: []byte("tagName3"), Value: []byte("tagValue3")}, + }, + expectedErr: nil, + }, + { + sortedTagPairs: []byte("tagName1"), + expectedPairs: nil, + expectedErr: errInvalidM3Metric, + }, + } + + for _, input := range inputs { + it := NewSortedTagIterator(input.sortedTagPairs) + var result []id.TagPair + for it.Next() { + name, value := it.Current() + result = append(result, id.TagPair{Name: name, Value: value}) + } + require.Equal(t, input.expectedErr, it.Err()) + require.Equal(t, input.expectedPairs, result) + } +} diff --git a/metric/id/tag.go b/metric/id/tag.go index db67987..b936209 100644 --- a/metric/id/tag.go +++ b/metric/id/tag.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Uber Technologies, Inc. +// Copyright (c) 2017 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/metric/id/tag_test.go b/metric/id/tag_test.go new file mode 100644 index 0000000..fbd2700 --- /dev/null +++ b/metric/id/tag_test.go @@ -0,0 +1,39 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package id + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTagPairsByNameAsc(t *testing.T) { + inputs := []TagPair{ + {Name: []byte("baz"), Value: []byte("first")}, + {Name: []byte("foo"), Value: []byte("second")}, + {Name: []byte("bar"), Value: []byte("third")}, + } + expected := []TagPair{inputs[2], inputs[0], inputs[1]} + sort.Sort(TagPairsByNameAsc(inputs)) + require.Equal(t, expected, inputs) +}