The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/ngalert/migration/models/models_test.go

149 lines
5.5 KiB

package models
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/util"
)
func TestDeduplicator(t *testing.T) {
tc := []struct {
name string
maxLen int
caseInsensitive bool
input []string
expected []string
expectedState map[string]struct{}
}{
{
name: "when case insensitive, it deduplicates case-insensitively",
caseInsensitive: true,
input: []string{"a", "A", "B", "b", "a", "A", "b", "B"},
expected: []string{"a", "A #2", "B", "b #2", "a #3", "A #4", "b #3", "B #4"},
},
{
name: "when case sensitive, it deduplicates case-sensitively",
caseInsensitive: false,
input: []string{"a", "A", "B", "b", "a", "A", "b", "B"},
expected: []string{"a", "A", "B", "b", "a #2", "A #2", "b #2", "B #2"},
},
{
name: "when maxLen is 0, it does not truncate",
maxLen: 0,
input: []string{strings.Repeat("a", 1000), strings.Repeat("a", 1000)},
expected: []string{strings.Repeat("a", 1000), strings.Repeat("a", 1000) + " #2"},
},
{
name: "when maxLen is > 0, it truncates - caseInsensitive",
caseInsensitive: true,
maxLen: 10,
input: []string{strings.Repeat("A", 15), strings.Repeat("a", 15), strings.Repeat("A", 15)},
expected: []string{strings.Repeat("A", 10), strings.Repeat("a", 7) + " #2", strings.Repeat("A", 7) + " #3"},
},
{
name: "when maxLen is > 0, it truncates - caseSensitive",
maxLen: 10,
input: []string{strings.Repeat("A", 15), strings.Repeat("a", 15), strings.Repeat("A", 15)},
expected: []string{strings.Repeat("A", 10), strings.Repeat("a", 10), strings.Repeat("A", 7) + " #2"},
},
{
name: "when truncate causes collision, it deduplicates - caseInsensitive",
caseInsensitive: true,
maxLen: 10,
input: []string{strings.Repeat("A", 15), strings.Repeat("a", 10), strings.Repeat("b", 15), strings.Repeat("B", 10)},
expected: []string{strings.Repeat("A", 10), strings.Repeat("a", 7) + " #2", strings.Repeat("b", 10), strings.Repeat("B", 7) + " #2"},
},
{
name: "when truncate causes collision, it deduplicates - caseSensitive",
maxLen: 10,
input: []string{strings.Repeat("A", 15), strings.Repeat("A", 10)},
expected: []string{strings.Repeat("A", 10), strings.Repeat("A", 7) + " #2"},
},
{
name: "when deduplicate causes collision, it deduplicates - caseInsensitive",
caseInsensitive: true,
maxLen: 10,
input: []string{"A", "a", "a #2", "b", "B", "B #2"},
expected: []string{"A", "a #2", "a #2 #2", "b", "B #2", "B #2 #2"},
},
{
name: "when deduplicate causes collision, it deduplicates - caseSensitive",
maxLen: 10,
input: []string{"a", "a", "a #2", "b", "b", "b #2"},
expected: []string{"a", "a #2", "a #2 #2", "b", "b #2", "b #2 #2"},
},
{
name: "when deduplicate causes collision, it finds next available increment - caseInsensitive",
caseInsensitive: true,
maxLen: 10,
input: []string{"a", "A #2", "a #3", "A #4", "a #5", "A #6", "a #7", "A #8", "a #9", "A #10", "a"},
expected: []string{"a", "A #2", "a #3", "A #4", "a #5", "A #6", "a #7", "A #8", "a #9", "A #10", "a #11"},
},
{
name: "when deduplicate causes collision, it finds next available increment - caseSensitive",
maxLen: 10,
input: []string{"a", "a #2", "a #3", "a #4", "a #5", "a #6", "a #7", "a #8", "a #9", "a #10", "a"},
expected: []string{"a", "a #2", "a #3", "a #4", "a #5", "a #6", "a #7", "a #8", "a #9", "a #10", "a #11"},
},
{
name: "when deduplicate causes collision enough times, it deduplicates with uid - caseInsensitive",
caseInsensitive: true,
maxLen: 10,
input: []string{"a", "A #2", "a #3", "A #4", "a #5", "A #6", "a #7", "A #8", "a #9", "A #10", "a #11", "A"},
expected: []string{"a", "A #2", "a #3", "A #4", "a #5", "A #6", "a #7", "A #8", "a #9", "A #10", "a #11", "A_uid-1"},
},
{
name: "when deduplicate causes collision enough times, it deduplicates with uid - caseSensitive",
maxLen: 10,
input: []string{"a", "a #2", "a #3", "a #4", "a #5", "a #6", "a #7", "a #8", "a #9", "a #10", "a #11", "a"},
expected: []string{"a", "a #2", "a #3", "a #4", "a #5", "a #6", "a #7", "a #8", "a #9", "a #10", "a #11", "a_uid-1"},
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
inc := 0
mockUidGenerator := func() string {
inc++
return fmt.Sprintf("uid-%d", inc)
}
dedup := Deduplicator{
set: make(map[string]int),
caseInsensitive: tt.caseInsensitive,
maxLen: tt.maxLen,
uidGenerator: mockUidGenerator,
}
out := make([]string, 0, len(tt.input))
for _, in := range tt.input {
d, err := dedup.Deduplicate(in)
require.NoError(t, err)
out = append(out, d)
}
require.Equal(t, tt.expected, out)
})
}
}
func Test_shortUIDCaseInsensitiveConflicts(t *testing.T) {
s := Deduplicator{
set: make(map[string]int),
caseInsensitive: true,
}
// 10000 uids seems to be enough to cause a collision in almost every run if using util.GenerateShortUID directly.
for i := 0; i < 10000; i++ {
s.add(util.GenerateShortUID(), 0)
}
// check if any are case-insensitive duplicates.
deduped := make(map[string]struct{})
for k := range s.set {
deduped[strings.ToLower(k)] = struct{}{}
}
require.Equal(t, len(s.set), len(deduped))
}