diff --git a/provider/zone_tag_filter_test.go b/provider/zone_tag_filter_test.go index 9574e68eb..05d435cea 100644 --- a/provider/zone_tag_filter_test.go +++ b/provider/zone_tag_filter_test.go @@ -17,46 +17,189 @@ limitations under the License. package provider import ( + "fmt" "testing" "github.com/stretchr/testify/assert" ) +var basicZoneTags = []struct { + name string + tagsFilter []string + zoneTags map[string]string + matches bool +}{ + { + "single tag no match", []string{"tag1=value1"}, map[string]string{"tag0": "value0"}, false, + }, + { + "single tag matches", []string{"tag1=value1"}, map[string]string{"tag1": "value1"}, true, + }, + { + "multiple tags no value match", []string{"tag1=value1"}, map[string]string{"tag0": "value0", "tag1": "value2"}, false, + }, + { + "multiple tags matches", []string{"tag1=value1"}, map[string]string{"tag0": "value0", "tag1": "value1"}, true, + }, + { + "tag name no match", []string{"tag1"}, map[string]string{"tag0": "value0"}, false, + }, + { + "tag name matches", []string{"tag1"}, map[string]string{"tag1": "value1"}, true, + }, + { + "multiple filter no match", []string{"tag1=value1", "tag2=value2"}, map[string]string{"tag1": "value1"}, false, + }, + { + "multiple filter matches", []string{"tag1=value1", "tag2=value2"}, map[string]string{"tag2": "value2", "tag1": "value1", "tag3": "value3"}, true, + }, + { + "empty tag filter matches all", []string{""}, map[string]string{"tag0": "value0"}, true, + }, + { + "tag filter without key and equal sign", []string{"tag1=value1", "=haha"}, map[string]string{"tag1": "value1"}, false, + }, +} + func TestZoneTagFilterMatch(t *testing.T) { - for _, tc := range []struct { - name string - zoneTagFilter []string - zoneTags map[string]string - matches bool - }{ - { - "single tag no match", []string{"tag1=value1"}, map[string]string{"tag0": "value0"}, false, - }, - { - "single tag matches", []string{"tag1=value1"}, map[string]string{"tag1": "value1"}, true, - }, - { - "multiple tags no value match", []string{"tag1=value1"}, map[string]string{"tag0": "value0", "tag1": "value2"}, false, - }, - { - "multiple tags matches", []string{"tag1=value1"}, map[string]string{"tag0": "value0", "tag1": "value1"}, true, - }, - { - "tag name no match", []string{"tag1"}, map[string]string{"tag0": "value0"}, false, - }, - { - "tag name matches", []string{"tag1"}, map[string]string{"tag1": "value1"}, true, - }, - { - "multiple filter no match", []string{"tag1=value1", "tag2=value2"}, map[string]string{"tag1": "value1"}, false, - }, - { - "multiple filter matches", []string{"tag1=value1", "tag2=value2"}, map[string]string{"tag2": "value2", "tag1": "value1", "tag3": "value3"}, true, - }, - } { - zoneTagFilter := NewZoneTagFilter(tc.zoneTagFilter) + for _, tc := range basicZoneTags { + zoneTagFilter := NewZoneTagFilter(tc.tagsFilter) t.Run(tc.name, func(t *testing.T) { assert.Equal(t, tc.matches, zoneTagFilter.Match(tc.zoneTags)) }) } } + +func TestZoneTagFilterNotSupportedFormat(t *testing.T) { + tests := []struct { + desc string + tags []string + want []string + }{ + {desc: "multiple or separate values with commas", tags: []string{"key1=val1,key2=val2"}, want: []string{"key1=val1,key2=val2"}}, + {desc: "exclude tag", tags: []string{"!key1"}, want: []string{"!key1"}}, + {desc: "exclude tags", tags: []string{"!key1=val"}, want: []string{"!key1=val"}}, + } + for _, tc := range tests { + t.Run(fmt.Sprintf("%s", tc.desc), func(t *testing.T) { + got := NewZoneTagFilter(tc.tags) + assert.Equal(t, tc.want, got.zoneTags) + }) + } +} + +func TestZoneTagFilterMatchGeneratedValues(t *testing.T) { + tests := []struct { + filters int + zones int + source filterZoneTags + }{ + {10, 30, generateTagFilterAndZoneTagsForMatch(10, 30)}, + {5, 40, generateTagFilterAndZoneTagsForMatch(5, 40)}, + {30, 50, generateTagFilterAndZoneTagsForMatch(30, 50)}, + } + for _, tc := range tests { + t.Run(fmt.Sprintf("filters:%d zones:%d", tc.filters, tc.zones), func(t *testing.T) { + assert.True(t, tc.source.ZoneTagFilter.Match(tc.source.inputTags)) + }) + } +} + +func TestZoneTagFilterNotMatchGeneratedValues(t *testing.T) { + tests := []struct { + filters int + zones int + source filterZoneTags + }{ + {10, 30, generateTagFilterAndZoneTagsForNotMatch(10, 30)}, + {5, 40, generateTagFilterAndZoneTagsForNotMatch(5, 40)}, + {30, 50, generateTagFilterAndZoneTagsForNotMatch(30, 50)}, + } + for _, tc := range tests { + t.Run(fmt.Sprintf("filters:%d zones:%d", tc.filters, tc.zones), func(t *testing.T) { + assert.False(t, tc.source.ZoneTagFilter.Match(tc.source.inputTags)) + }) + } +} + +// benchmarks +func BenchmarkZoneTagFilterMatchBasic(b *testing.B) { + for _, tc := range basicZoneTags { + zoneTagFilter := NewZoneTagFilter(tc.tagsFilter) + for range b.N { + zoneTagFilter.Match(tc.zoneTags) + } + } +} + +var benchFixtures = []struct { + source filterZoneTags +}{ + // match + {generateTagFilterAndZoneTagsForMatch(10, 30)}, + {generateTagFilterAndZoneTagsForMatch(5, 40)}, + {generateTagFilterAndZoneTagsForMatch(30, 50)}, + // no match + {generateTagFilterAndZoneTagsForNotMatch(10, 30)}, + {generateTagFilterAndZoneTagsForNotMatch(5, 40)}, + {generateTagFilterAndZoneTagsForNotMatch(30, 50)}, +} + +func BenchmarkZoneTagFilterComplex(b *testing.B) { + for _, tc := range benchFixtures { + for range b.N { + tc.source.ZoneTagFilter.Match(tc.source.inputTags) + } + } +} + +// test doubles +type filterZoneTags struct { + ZoneTagFilter + inputTags map[string]string +} + +// generateTagFilterAndZoneTagsForMatch generates filter tags and zone tags that do match. +func generateTagFilterAndZoneTagsForMatch(filter, zone int) filterZoneTags { + return generateTagFilterAndZoneTags(filter, zone, true) +} + +// generateTagFilterAndZoneTagsForNotMatch generates filter tags and zone tags that do not match. +func generateTagFilterAndZoneTagsForNotMatch(filter, zone int) filterZoneTags { + return generateTagFilterAndZoneTags(filter, zone, false) +} + +// generateTagFilterAndZoneTags generates filter tags and zone tags based on the match parameter. +func generateTagFilterAndZoneTags(filter, zone int, match bool) filterZoneTags { + validate(filter, zone) + toFilterTags := make([]string, 0, filter) + inputTags := make(map[string]string, zone) + + for i := 0; i < filter; i++ { + tagIndex := i + if !match { + tagIndex += 50 + } + toFilterTags = append(toFilterTags, fmt.Sprintf("tag-%d=value-%d", tagIndex, i)) + } + + for i := 0; i < zone; i++ { + tagIndex := i + if !match { + // Make sure the input tags are different from the filter tags + tagIndex += 2 + } + inputTags[fmt.Sprintf("tag-%d", i)] = fmt.Sprintf("value-%d", tagIndex) + } + + return filterZoneTags{NewZoneTagFilter(toFilterTags), inputTags} +} + +func validate(filter int, zone int) { + if zone > 50 { + panic("zone number is too high") + } + if filter > zone { + panic("filter number is too high") + } +}