532 lines
13 KiB
Go
532 lines
13 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package helper
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/go-set"
|
|
"github.com/shoenig/test/must"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/exp/maps"
|
|
)
|
|
|
|
func Test_Min(t *testing.T) {
|
|
t.Run("int", func(t *testing.T) {
|
|
a := 1
|
|
b := 2
|
|
must.Eq(t, 1, Min(a, b))
|
|
must.Eq(t, 1, Min(b, a))
|
|
})
|
|
|
|
t.Run("float64", func(t *testing.T) {
|
|
a := 1.1
|
|
b := 2.2
|
|
must.Eq(t, 1.1, Min(a, b))
|
|
must.Eq(t, 1.1, Min(b, a))
|
|
})
|
|
|
|
t.Run("string", func(t *testing.T) {
|
|
a := "cat"
|
|
b := "dog"
|
|
must.Eq(t, "cat", Min(a, b))
|
|
must.Eq(t, "cat", Min(b, a))
|
|
})
|
|
}
|
|
|
|
func Test_Max(t *testing.T) {
|
|
t.Run("int", func(t *testing.T) {
|
|
a := 1
|
|
b := 2
|
|
must.Eq(t, 2, Max(a, b))
|
|
must.Eq(t, 2, Max(b, a))
|
|
})
|
|
|
|
t.Run("float64", func(t *testing.T) {
|
|
a := 1.1
|
|
b := 2.2
|
|
must.Eq(t, 2.2, Max(a, b))
|
|
must.Eq(t, 2.2, Max(b, a))
|
|
})
|
|
|
|
t.Run("string", func(t *testing.T) {
|
|
a := "cat"
|
|
b := "dog"
|
|
must.Eq(t, "dog", Max(a, b))
|
|
must.Eq(t, "dog", Max(b, a))
|
|
})
|
|
}
|
|
|
|
func TestIsSubset(t *testing.T) {
|
|
l := []string{"a", "b", "c"}
|
|
s := []string{"d"}
|
|
|
|
sub, offending := IsSubset(l, l[:1])
|
|
must.True(t, sub)
|
|
must.SliceEmpty(t, offending)
|
|
|
|
sub, offending = IsSubset(l, s)
|
|
must.False(t, sub)
|
|
must.Eq(t, []string{"d"}, offending)
|
|
}
|
|
|
|
func TestIsDisjoint(t *testing.T) {
|
|
t.Run("yes", func(t *testing.T) {
|
|
a := []string{"a", "b", "c"}
|
|
b := []string{"d", "f"}
|
|
dis, offending := IsDisjoint(a, b)
|
|
must.True(t, dis)
|
|
must.SliceEmpty(t, offending)
|
|
})
|
|
|
|
t.Run("no", func(t *testing.T) {
|
|
a := []string{"a", "b", "c", "d", "e"}
|
|
b := []string{"b", "c", "f", "g"}
|
|
dis, offending := IsDisjoint(a, b)
|
|
must.False(t, dis)
|
|
must.True(t, set.From(offending).EqualSlice(offending))
|
|
})
|
|
}
|
|
|
|
func TestStringHasPrefixInSlice(t *testing.T) {
|
|
prefixes := []string{"a", "b", "c", "definitely", "most definitely"}
|
|
// The following strings all start with at least one prefix in the slice above
|
|
require.True(t, StringHasPrefixInSlice("alpha", prefixes))
|
|
require.True(t, StringHasPrefixInSlice("bravo", prefixes))
|
|
require.True(t, StringHasPrefixInSlice("charlie", prefixes))
|
|
require.True(t, StringHasPrefixInSlice("definitely", prefixes))
|
|
require.True(t, StringHasPrefixInSlice("most definitely", prefixes))
|
|
|
|
require.False(t, StringHasPrefixInSlice("mos", prefixes))
|
|
require.False(t, StringHasPrefixInSlice("def", prefixes))
|
|
require.False(t, StringHasPrefixInSlice("delta", prefixes))
|
|
|
|
}
|
|
|
|
func TestCompareSliceSetString(t *testing.T) {
|
|
cases := []struct {
|
|
A []string
|
|
B []string
|
|
Result bool
|
|
}{
|
|
{
|
|
A: []string{},
|
|
B: []string{},
|
|
Result: true,
|
|
},
|
|
{
|
|
A: []string{},
|
|
B: []string{"a"},
|
|
Result: false,
|
|
},
|
|
{
|
|
A: []string{"a"},
|
|
B: []string{"a"},
|
|
Result: true,
|
|
},
|
|
{
|
|
A: []string{"a"},
|
|
B: []string{"b"},
|
|
Result: false,
|
|
},
|
|
{
|
|
A: []string{"a", "b"},
|
|
B: []string{"b"},
|
|
Result: false,
|
|
},
|
|
{
|
|
A: []string{"a", "b"},
|
|
B: []string{"a"},
|
|
Result: false,
|
|
},
|
|
{
|
|
A: []string{"a", "b"},
|
|
B: []string{"a", "b"},
|
|
Result: true,
|
|
},
|
|
{
|
|
A: []string{"a", "b"},
|
|
B: []string{"b", "a"},
|
|
Result: true,
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
tc := tc
|
|
t.Run(fmt.Sprintf("case-%da", i), func(t *testing.T) {
|
|
if res := SliceSetEq(tc.A, tc.B); res != tc.Result {
|
|
t.Fatalf("expected %t but CompareSliceSetString(%v, %v) -> %t",
|
|
tc.Result, tc.A, tc.B, res,
|
|
)
|
|
}
|
|
})
|
|
|
|
// Function is commutative so compare B and A
|
|
t.Run(fmt.Sprintf("case-%db", i), func(t *testing.T) {
|
|
if res := SliceSetEq(tc.B, tc.A); res != tc.Result {
|
|
t.Fatalf("expected %t but CompareSliceSetString(%v, %v) -> %t",
|
|
tc.Result, tc.B, tc.A, res,
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUniqueMapSliceValues(t *testing.T) {
|
|
m := map[string][]string{
|
|
"foo": {"1", "2"},
|
|
"bar": {"3"},
|
|
"baz": nil,
|
|
}
|
|
|
|
act := UniqueMapSliceValues(m)
|
|
exp := []string{"1", "2", "3"}
|
|
sort.Strings(act)
|
|
must.Eq(t, exp, act)
|
|
}
|
|
|
|
func TestCopyMapStringSliceString(t *testing.T) {
|
|
m := map[string][]string{
|
|
"x": {"a", "b", "c"},
|
|
"y": {"1", "2", "3"},
|
|
"z": nil,
|
|
}
|
|
|
|
c := CopyMapOfSlice(m)
|
|
if !reflect.DeepEqual(c, m) {
|
|
t.Fatalf("%#v != %#v", m, c)
|
|
}
|
|
|
|
c["x"][1] = "---"
|
|
if reflect.DeepEqual(c, m) {
|
|
t.Fatalf("Shared slices: %#v == %#v", m["x"], c["x"])
|
|
}
|
|
}
|
|
|
|
func TestMergeMapStringString(t *testing.T) {
|
|
type testCase struct {
|
|
map1 map[string]string
|
|
map2 map[string]string
|
|
expected map[string]string
|
|
}
|
|
|
|
cases := []testCase{
|
|
{map[string]string{"foo": "bar"}, map[string]string{"baz": "qux"}, map[string]string{"foo": "bar", "baz": "qux"}},
|
|
{map[string]string{"foo": "bar"}, nil, map[string]string{"foo": "bar"}},
|
|
{nil, map[string]string{"baz": "qux"}, map[string]string{"baz": "qux"}},
|
|
{nil, nil, map[string]string{}},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
if output := MergeMapStringString(c.map1, c.map2); !maps.Equal(output, c.expected) {
|
|
t.Errorf("MergeMapStringString(%q, %q) -> %q != %q", c.map1, c.map2, output, c.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCleanEnvVar(t *testing.T) {
|
|
type testCase struct {
|
|
input string
|
|
expected string
|
|
}
|
|
cases := []testCase{
|
|
{"asdf", "asdf"},
|
|
{"ASDF", "ASDF"},
|
|
{"0sdf", "_sdf"},
|
|
{"asd0", "asd0"},
|
|
{"_asd", "_asd"},
|
|
{"-asd", "_asd"},
|
|
{"asd.fgh", "asd.fgh"},
|
|
{"A~!@#$%^&*()_+-={}[]|\\;:'\"<,>?/Z", "A______________________________Z"},
|
|
{"A\U0001f4a9Z", "A____Z"},
|
|
}
|
|
for _, c := range cases {
|
|
if output := CleanEnvVar(c.input, '_'); output != c.expected {
|
|
t.Errorf("CleanEnvVar(%q, '_') -> %q != %q", c.input, output, c.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkCleanEnvVar(b *testing.B) {
|
|
in := "NOMAD_ADDR_redis-cache"
|
|
replacement := byte('_')
|
|
b.SetBytes(int64(len(in)))
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
CleanEnvVar(in, replacement)
|
|
}
|
|
}
|
|
|
|
type testCase struct {
|
|
input string
|
|
expected string
|
|
}
|
|
|
|
func commonCleanFilenameCases() (cases []testCase) {
|
|
// Common set of test cases for all 3 TestCleanFilenameX functions
|
|
cases = []testCase{
|
|
{"asdf", "asdf"},
|
|
{"ASDF", "ASDF"},
|
|
{"0sdf", "0sdf"},
|
|
{"asd0", "asd0"},
|
|
{"_asd", "_asd"},
|
|
{"-asd", "-asd"},
|
|
{"asd.fgh", "asd.fgh"},
|
|
{"Linux/Forbidden", "Linux_Forbidden"},
|
|
{"Windows<>:\"/\\|?*Forbidden", "Windows_________Forbidden"},
|
|
{`Windows<>:"/\|?*Forbidden_StringLiteral`, "Windows_________Forbidden_StringLiteral"},
|
|
}
|
|
return cases
|
|
}
|
|
|
|
func TestCleanFilename(t *testing.T) {
|
|
cases := append(
|
|
[]testCase{
|
|
{"A\U0001f4a9Z", "A💩Z"}, // CleanFilename allows unicode
|
|
{"A💩Z", "A💩Z"},
|
|
{"A~!@#$%^&*()_+-={}[]|\\;:'\"<,>?/Z", "A~!@#$%^&_()_+-={}[]__;_'__,___Z"},
|
|
}, commonCleanFilenameCases()...)
|
|
|
|
for i, c := range cases {
|
|
t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
|
|
output := CleanFilename(c.input, "_")
|
|
failMsg := fmt.Sprintf("CleanFilename(%q, '_') -> %q != %q", c.input, output, c.expected)
|
|
require.Equal(t, c.expected, output, failMsg)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCleanFilenameASCIIOnly(t *testing.T) {
|
|
ASCIIOnlyCases := append(
|
|
[]testCase{
|
|
{"A\U0001f4a9Z", "A_Z"}, // CleanFilenameASCIIOnly does not allow unicode
|
|
{"A💩Z", "A_Z"},
|
|
{"A~!@#$%^&*()_+-={}[]|\\;:'\"<,>?/Z", "A~!@#$%^&_()_+-={}[]__;_'__,___Z"},
|
|
}, commonCleanFilenameCases()...)
|
|
|
|
for i, c := range ASCIIOnlyCases {
|
|
t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
|
|
output := CleanFilenameASCIIOnly(c.input, "_")
|
|
failMsg := fmt.Sprintf("CleanFilenameASCIIOnly(%q, '_') -> %q != %q", c.input, output, c.expected)
|
|
require.Equal(t, c.expected, output, failMsg)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCleanFilenameStrict(t *testing.T) {
|
|
strictCases := append(
|
|
[]testCase{
|
|
{"A\U0001f4a9Z", "A💩Z"}, // CleanFilenameStrict allows unicode
|
|
{"A💩Z", "A💩Z"},
|
|
{"A~!@#$%^&*()_+-={}[]|\\;:'\"<,>?/Z", "A_!___%^______-_{}_____________Z"},
|
|
}, commonCleanFilenameCases()...)
|
|
|
|
for i, c := range strictCases {
|
|
t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
|
|
output := CleanFilenameStrict(c.input, "_")
|
|
failMsg := fmt.Sprintf("CleanFilenameStrict(%q, '_') -> %q != %q", c.input, output, c.expected)
|
|
require.Equal(t, c.expected, output, failMsg)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckNamespaceScope(t *testing.T) {
|
|
cases := []struct {
|
|
desc string
|
|
provided string
|
|
requested []string
|
|
offending []string
|
|
}{
|
|
{
|
|
desc: "root ns requesting namespace",
|
|
provided: "",
|
|
requested: []string{"engineering"},
|
|
},
|
|
{
|
|
desc: "matching parent ns with child",
|
|
provided: "engineering",
|
|
requested: []string{"engineering", "engineering/sub-team"},
|
|
},
|
|
{
|
|
desc: "mismatch ns",
|
|
provided: "engineering",
|
|
requested: []string{"finance", "engineering/sub-team", "eng"},
|
|
offending: []string{"finance", "eng"},
|
|
},
|
|
{
|
|
desc: "mismatch child",
|
|
provided: "engineering/sub-team",
|
|
requested: []string{"engineering/new-team", "engineering/sub-team", "engineering/sub-team/child"},
|
|
offending: []string{"engineering/new-team"},
|
|
},
|
|
{
|
|
desc: "matching prefix",
|
|
provided: "engineering",
|
|
requested: []string{"engineering/new-team", "engineering/new-team/sub-team"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
offending := CheckNamespaceScope(tc.provided, tc.requested)
|
|
require.Equal(t, offending, tc.offending)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTimer_NewSafeTimer(t *testing.T) {
|
|
t.Run("zero", func(t *testing.T) {
|
|
timer, stop := NewSafeTimer(0)
|
|
defer stop()
|
|
<-timer.C
|
|
})
|
|
|
|
t.Run("positive", func(t *testing.T) {
|
|
timer, stop := NewSafeTimer(1)
|
|
defer stop()
|
|
<-timer.C
|
|
})
|
|
}
|
|
|
|
func TestTimer_NewStoppedTimer(t *testing.T) {
|
|
timer, stop := NewStoppedTimer()
|
|
defer stop()
|
|
|
|
select {
|
|
case <-timer.C:
|
|
must.Unreachable(t)
|
|
default:
|
|
}
|
|
}
|
|
|
|
func Test_ConvertSlice(t *testing.T) {
|
|
t.Run("string wrapper", func(t *testing.T) {
|
|
|
|
type wrapper struct{ id string }
|
|
input := []string{"foo", "bar", "bad", "had"}
|
|
cFn := func(id string) *wrapper { return &wrapper{id: id} }
|
|
|
|
expectedOutput := []*wrapper{{id: "foo"}, {id: "bar"}, {id: "bad"}, {id: "had"}}
|
|
actualOutput := ConvertSlice(input, cFn)
|
|
require.ElementsMatch(t, expectedOutput, actualOutput)
|
|
})
|
|
|
|
t.Run("int wrapper", func(t *testing.T) {
|
|
|
|
type wrapper struct{ id int }
|
|
input := []int{10, 13, 1987, 2020}
|
|
cFn := func(id int) *wrapper { return &wrapper{id: id} }
|
|
|
|
expectedOutput := []*wrapper{{id: 10}, {id: 13}, {id: 1987}, {id: 2020}}
|
|
actualOutput := ConvertSlice(input, cFn)
|
|
require.ElementsMatch(t, expectedOutput, actualOutput)
|
|
|
|
})
|
|
}
|
|
|
|
func Test_IsMethodHTTP(t *testing.T) {
|
|
t.Run("is method", func(t *testing.T) {
|
|
cases := []string{
|
|
"GET", "Get", "get",
|
|
"HEAD", "Head", "head",
|
|
"POST", "Post", "post",
|
|
"PUT", "Put", "put",
|
|
"PATCH", "Patch", "patch",
|
|
"DELETE", "Delete", "delete",
|
|
"CONNECT", "Connect", "connect",
|
|
"OPTIONS", "Options", "options",
|
|
"TRACE", "Trace", "trace",
|
|
}
|
|
for _, tc := range cases {
|
|
result := IsMethodHTTP(tc)
|
|
must.True(t, result)
|
|
}
|
|
})
|
|
|
|
t.Run("is not method", func(t *testing.T) {
|
|
not := []string{"GETTER", "!GET", ""}
|
|
for _, tc := range not {
|
|
result := IsMethodHTTP(tc)
|
|
must.False(t, result)
|
|
}
|
|
})
|
|
}
|
|
|
|
type employee struct {
|
|
id int
|
|
name string
|
|
}
|
|
|
|
func (e *employee) Equal(o *employee) bool {
|
|
return e.id == o.id // name can be different
|
|
}
|
|
|
|
func Test_ElementsEquals(t *testing.T) {
|
|
t.Run("empty", func(t *testing.T) {
|
|
a := []*employee(nil)
|
|
var b []*employee
|
|
must.True(t, ElementsEqual(a, b))
|
|
must.True(t, ElementsEqual(b, a))
|
|
})
|
|
|
|
t.Run("different sizes", func(t *testing.T) {
|
|
a := []*employee{{1, "mitchell"}, {2, "armon"}, {3, "jack"}}
|
|
b := []*employee{{1, "mitchell"}, {2, "armon"}}
|
|
must.False(t, ElementsEqual(a, b))
|
|
must.False(t, ElementsEqual(b, a))
|
|
})
|
|
|
|
t.Run("equal", func(t *testing.T) {
|
|
a := []*employee{{1, "mitchell"}, {2, "armon"}, {3, "jack"}}
|
|
b := []*employee{{1, "M.H."}, {2, "A.D."}, {3, "J.P."}}
|
|
must.True(t, ElementsEqual(a, b))
|
|
must.True(t, ElementsEqual(b, a))
|
|
})
|
|
|
|
t.Run("different", func(t *testing.T) {
|
|
a := []*employee{{1, "mitchell"}, {2, "armon"}, {3, "jack"}}
|
|
b := []*employee{{0, "mitchell."}, {2, "armon"}, {3, "jack"}}
|
|
must.False(t, ElementsEqual(a, b))
|
|
must.False(t, ElementsEqual(b, a))
|
|
})
|
|
}
|
|
|
|
func Test_SliceSetEq(t *testing.T) {
|
|
t.Run("empty", func(t *testing.T) {
|
|
a := make([]int, 0)
|
|
b := make([]int, 0)
|
|
must.True(t, SliceSetEq(a, b))
|
|
})
|
|
|
|
t.Run("subset small", func(t *testing.T) {
|
|
a := []int{1, 2, 3, 4, 5}
|
|
b := []int{1, 2, 3}
|
|
must.False(t, SliceSetEq(a, b))
|
|
must.False(t, SliceSetEq(b, a))
|
|
})
|
|
|
|
t.Run("subset large", func(t *testing.T) {
|
|
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
|
|
b := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
|
|
must.False(t, SliceSetEq(a, b))
|
|
must.False(t, SliceSetEq(b, a))
|
|
})
|
|
|
|
t.Run("same small", func(t *testing.T) {
|
|
a := []int{1, 2, 3, 4, 5}
|
|
b := []int{1, 2, 3, 4, 5}
|
|
must.True(t, SliceSetEq(a, b))
|
|
})
|
|
|
|
t.Run("same large", func(t *testing.T) {
|
|
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
|
|
b := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
|
|
must.True(t, SliceSetEq(a, b))
|
|
})
|
|
}
|